/*
 *   Copyright (C) 1988-2006 Lucent Technologies Inc. and others.
 *   All Rights Reserved.
 */

/* cache management functions */

#include "global.h"
#include <errno.h>	/* errno */
#include <stdio.h>
#if !defined(__BORLANDC__) && !defined(FOR_WIN32)

#if USEVFS
#include <sys/statvfs.h>/* statvfs */
#elif sparc || hppa
#include <sys/vfs.h>	/* statfs */
#elif darwin
#include <sys/param.h>
#include <sys/mount.h>	/* statfs */
#else
#include <sys/statfs.h>	/* statfs */
#endif

#if sgi
#define F_BAVAIL f_bfree
#else
#define F_BAVAIL f_bavail
#endif

#if USEVFS
#define STATVFS(_dir,_inf) statvfs((_dir),(_inf))
#define STRUCT_STATVFS statvfs
#elif sgi
#define STATVFS(_dir,_inf) statfs((_dir),(_inf),sizeof(struct statfs),0)
#define STRUCT_STATVFS statfs
#else
#define STATVFS(_dir,_inf) statfs((_dir),(_inf))
#define STRUCT_STATVFS statfs
#endif

#include <sys/stat.h>   /* stat */
#include <dirent.h>
#endif

#define ETCCACHECONF "/etc/cscopemaxcache"
#define HOMECACHECONF ".cscopemaxcache"
#define EXTPATHLEN (PATHLEN + 10)
#define SYSCMDLEN (15 + (EXTPATHLEN*2))
#define DIRNAMELEN (PATHLEN/2)

static char	*cachedir;		/* uncompressed cache directory */
static char	*cachesubdir;		/* uncompressed cache subdirectory */
static char	tempext[EXTPATHLEN+1];	/* temporary file name + zip extensions */
static char	ntempext[EXTPATHLEN+1];	/* temporary file name + zip extensions */
static char	syscmd[SYSCMDLEN];	/* general buffer for system commands */
static char	srctemp[PATHLEN + 1];	/* src dir for generating temp dir */
static BOOL	limitcheck = 1;		/* cache limitations - on by default */

#if __STDC__
void	mknewloc_path(char **);
int	limitcache(char *,char *,char *);
int	cacheuptodate(char *,char *);
#else
void	mknewloc_path();
int	limitcache();
int	cacheuptodate();
#endif

void uncompressdb()
{
#if !defined(__BORLANDC__) && !defined(FOR_WIN32)
    char *tool;		/* Set to the tool that should be used for zipping */
    char *base;		/* Used for pointing to the path base */
    char *alt; 		/* used for .in.out/.out.in alternates */
    char *ext,lext[5];	/* The zip extension, in use */
    char zipoption;	/* The option to use with the zip tool */
#ifdef HOMECONFFILE
    char *homeconffile;	/* Check home dir for config file */
#endif
    int defaultdb;	/* Indicates if this is named cscope.out */
    int namelen;	/* Generic variable for name lengths */

    /* Check if database exists */
    if(vpaccess(reffile,READ) != 0)
    {
#if GZIPDEBUG
	fprintf(stderr,"%s: DEBUG #1: no reffile: %s\n",argv0,reffile);
#endif
	/* No database - it may be compressed */
	strcpy(tempext,reffile);
	namelen = strlen(tempext);

	/* Look to see if there is a zipped cscope.out file */
	tool = NULL;
	ext = &tempext[namelen];
	strcpy(ext,".gz");
	if(vpls(tempext,READ,ntempext) == 0) {
	    tool = "gunzip";
	    zipoption = 'c';
	} else  {
	    strcpy(ext,".bz2");
	    if(vpls(tempext,READ,ntempext) == 0) {
 	        tool = "bunzip2";
	        zipoption = 'c';
	    } else {
	        strcpy(ext,".zip");
	        if(vpls(tempext,READ,ntempext) == 0) {
	            tool = "unzip";
	            zipoption = 'p';
	        }
	    }
	}
	strcpy(lext,ext);
#if GZIPDEBUG
	fprintf(stderr,"%s: DEBUG #2: zipped database exists: %s\n",argv0,ntempext);
#endif

	/* Set the source dir to be used for generating a temporary
	 * directory name which contains the uncompressed database.
	 */
	base = mybasename(ntempext);
	if(base == ntempext)
	{
	    /* There is no path so use currentdir as the source
	     * for generating the temporary uncompressed dir name
	     */
	    strcpy(srctemp,currentdir);
	} else {
	    strcpy(srctemp,ntempext);
	    srctemp[base - ntempext - 1] = '\0'; /* Strip filename */
	}

	/*
	 * If we found a compressed cscope.out above, it needs
	 * uncompressing, and we need to look for inverted indices too.
	 */
	if(tool)
	{
#if GZIPDEBUG
	    fprintf(stderr,"%s: DEBUG #3: unzipping using %s\n",argv0,tool);
#endif
	    /* For builds we need to uncompress in the current directory */
	    if (isuptodate == YES)
	    {
	 	/* Uncompress to a common dir based on the pathname */
		mknewloc_path(&reffile);
#if GZIPDEBUG
		fprintf(stderr,"%s: DEBUG #4: re-set reffile to %s\n",argv0,reffile);
#endif
	    }

	    /* Check the cache to limit number of databases cached
	     * Do this before we uncompress so we make room first,
	     * but mknewloc_path sets up cachedir for us...
	     */
#ifndef HOMECONFFILE
	    limitcache(ETCCACHECONF,cachedir,cachesubdir);
#else
	    if(!limitcache(ETCCACHECONF,cachedir,cachesubdir))
	    {
		homeconffile = pathjoin(home,HOMECACHECONF);
		if(access(homeconffile,READ) == 0)
		{
		     limitcache(homeconffile,cachedir,cachesubdir);
		}
		free(homeconffile);
	    }
#endif

	    /* Check if temporary database exists */
#if 0
	    if(access(reffile,READ) != 0)
#endif
	    if(cacheuptodate(reffile,ntempext) != 0)
	    {
		/* uncompress all the db files */
		sprintf(syscmd,"%s -%c %s >%s",tool,zipoption,ntempext,reffile);
#if GZIPDEBUG
		fprintf(stderr,"%s: DEBUG #5: uncompressing: %s\n",argv0,syscmd);
#endif
		if (system(syscmd) != 0)
		{
			fprintf(stderr,"%s: failed to uncompress database\n",argv0);
			myexit(1);
		}
	    }

	    /* Check if inverted index exists */
	    if(vpaccess(invname,READ) != 0)
	    {
#if GZIPDEBUG
		fprintf(stderr,"%s: DEBUG #6: no inverted index: %s\n",argv0,invname);
#endif
		/* No inverted index - it may be compressed */
		strcpy(tempext,invname);
		namelen = strlen(tempext) - 1;
		strcat(&tempext[namelen],lext);

		/* 
		 * Determine if this is cscope.in.out so that
		 * we can default to cscope.out.in if needed.
		 *
		 * This check is not really necessary, we could be
		 * really nice and check for any .in.out/.out.in
		 * switch, but the user would really have had to
		 * move files around to switch a non cscope.out.in!
		 */
		if(!strcmp(mybasename(invname),INVNAME))
			defaultdb = 1;
		else
			defaultdb = 0;

		/* Check to see if the cscope.in.out.xxx exists */
		if (defaultdb && vpaccess(tempext,READ) != 0)
                {
			/* If not, and this is cscope.in.out,
			 * try looking for cscope.out.in.xxx
			 */
			alt = stralloc(tempext);
                        strcpy(&alt[strlen(alt) - 7 - strlen(lext)],".out.in");
                        strcat(alt,lext);
			/* This vpaccess is a little redundant, but
			 * we really only want to change tempext
			 * if something was found...
			 */
			if (vpaccess(alt,READ) == 0)
			{
				strcpy(tempext,alt);
			}
			free(alt);
		}
		if (vpaccess(tempext,READ) == 0)
		{
		    FILE *oldrefs;
		    BOOL invindex;
		    int c;

#if GZIPDEBUG
		    fprintf(stderr,"%s: DEBUG #7: zipped inverted index exists: %s\n",argv0,tempext);
#endif
		    /* Check for mismatch with inverted indexes */
		    if ((oldrefs = vpfopen(reffile, "r")) == NULL) {
			cannotopen(reffile);
			myexit(1);
		    }
		    /* get the crossref file version */
		    if (fscanf(oldrefs, "cscope %d %*s", &fileversion) != 1) {
			(void) fprintf(stderr, "%s: cannot read file version from file %s\n", argv0, reffile);
			myexit(1);
		    }
		    if (fileversion >= 8) {
			invindex = NO;

			/* see if there are options in the database */
			for (;;) {
				/* no -q leaves multiple blanks */
				while ((c = getc(oldrefs)) == ' ') {
					;
				}
				if (c != '-') {
					(void) ungetc(c, oldrefs);
					break;
				}
				switch (c = getc(oldrefs)) {
				case 'q':	/* quick search */
					invindex = YES;
					break;
				}
			}
		    }
		    (void) fclose(oldrefs);

		    if (invindex == NO) {
#if GZIPDEBUG
			fprintf(stderr,"%s: DEBUG #8: reffile not -q: %s\n",argv0,reffile);
#endif
			/* We have a mismatch - copy reffile again */
			strcpy(tempext,mybasename(reffile));
			namelen = strlen(tempext) - 1;
			strcat(&tempext[namelen],lext);
			/* Already have reffile correct */
			if (vpls(tempext,READ,ntempext) == 0)
			{
			    /* Only different if no cscope.out.gz in CWD */
			    if (strcmp(tempext,ntempext) != 0)
			    {
				sprintf(syscmd,"%s -c %s >%s",tool,ntempext,reffile);
#if GZIPDEBUG
				fprintf(stderr,"%s: DEBUG #9: re-copy reffile: %s\n",argv0,syscmd);
#endif
				system(syscmd);
			    } else {
				/* can't copy reffile so don't copy indexes */
				tool = NULL;
#if GZIPDEBUG
				fprintf(stderr,"%s: DEBUG #10: cannot copy reffile: %s\n",argv0,tempext);
#endif
			    }
			}

			/* restore tempext */
			strcpy(tempext,invname);
			namelen = strlen(tempext) - 1;
			strcat(&tempext[namelen],lext);
#if GZIPDEBUG
		    } else {
			fprintf(stderr,"%s: DEBUG #11: reffile has -q: %s\n",argv0,reffile);
#endif
		    }
		}
		if (tool) {
#if GZIPDEBUG
		    fprintf(stderr,"%s: DEBUG #12: unzipping using %s\n",argv0,tool);
#endif
		    if (isuptodate == YES)
		    {
			mknewloc_path(&invname);
#if GZIPDEBUG
			fprintf(stderr,"%s: DEBUG #13: re-set invname to %s\n",argv0,invname);
#endif
		    }
	       	    if(vpls(tempext,READ,ntempext) == 0)
		    {
                        if(cacheuptodate(invname,ntempext)) {
		   	    sprintf(syscmd,"%s -%c %s >%s",tool,zipoption,ntempext,invname);
#if GZIPDEBUG
			    fprintf(stderr,"%s: DEBUG #14: uncompress: %s\n",argv0,syscmd);
#endif
			    system(syscmd);
                        } 

			strcpy(tempext,invpost);
			namelen = strlen(tempext) - 1;
			strcat(&tempext[namelen],lext);
			if (isuptodate == YES)
			{
			    mknewloc_path(&invpost);
#if GZIPDEBUG
			    fprintf(stderr,"%s: DEBUG #15: re-set invpost to %s\n",argv0,invpost);
#endif
			}
		        /* Check to see if the cscope.po.out.xxx exists */
			if (defaultdb && vpls(tempext,READ,ntempext) != 0)
                	{
			    /* If not, and this is cscope.po.out,
			     * try looking for cscope.out.po.xxx
			     */
			    alt = stralloc(tempext);
                            strcpy(&alt[strlen(alt) - 7 - strlen(lext)],".out.po");
                            strcat(alt,lext);
			    /* This vpls is a little redundant, but
			     * we really only want to change tempext
			     * if something was found...
			     */
			    if (vpls(alt,READ,ntempext) == 0)
			    {
				strcpy(tempext,alt);
			    }
			    free(alt);
			}
			if (vpls(tempext,READ,ntempext) == 0)
			{
                            if(cacheuptodate(invpost,ntempext)) {
		       	        sprintf(syscmd,"%s -%c %s >%s",tool,zipoption,ntempext,invpost);
#if GZIPDEBUG
			        fprintf(stderr,"%s: DEBUG #16: uncompress: %s\n",argv0,syscmd);
#endif
			        system(syscmd);
                            }
			}
		    } /* vpls() */
		}
	    }
#if 0
	    else if (isuptodate == YES)
	    {
		mknewloc_path(&invname);

#if GZIPDEBUG
		fprintf(stderr,"%s: DEBUG #17: re-set invname to %s\n",argv0,invname);
#endif
		mknewloc_path(&invpost);
#if GZIPDEBUG
		fprintf(stderr,"%s: DEBUG #18: re-set invpost to %s\n",argv0,invpost);
#endif
	    }
#endif
	}
    }
#endif
}

/* Determine the location of the semi-permenant uncompressed database
 * This will update the reffile, invname and invpost to the new location
 */
void mknewloc_path(fname)
char **fname;
{
    /* Too many assumptions for borland right now. */
#if !defined(__BORLANDC__) && !defined(FOR_WIN32)
    int offset1,offset2;
    char dirname[DIRNAMELEN],*dir1,*dir2,*file,*dbcache,*fullpath,*end;

    if(**fname != '/')
    {
	/* Merge current dir and fname so its a full path... */
	fullpath = mymalloc(strlen(currentdir) + strlen(*fname) + 2);
	strcpy(fullpath,srctemp);
	if(strcmp(fullpath,"/")) strcat(fullpath,"/");
	strcat(fullpath,*fname);
    }
    else
	fullpath = stralloc(*fname);

    /* Process last two dirs of the db path (or current dir if its a
     * relative db path) to use as common dirname.
     *
     * dir1 is the first dirname to be saved
     * dir2 is the second dirname to be saved
     *
     * /blah/rel/ss/file would be  dir1 == rel, dir2 == ss
     *
     * relss is copied into dirname
     */
    end = &fullpath[strlen(fullpath)];
    file = mybasename_end(fullpath,end);
    if(file != fullpath)
    {
	dir2 = mybasename_end(fullpath,file - 1);
	if(dir2 != fullpath)
	{
	    offset2 = file - dir2 - 1;
	    dir1 = mybasename_end(fullpath,dir2 - 1);
	    if(dir1 != fullpath)
	    {
		/* Have 2 dirs */
		offset1 = dir2 - dir1 - 1;
		strncpy(dirname,dir1,offset1);
		strncpy(&dirname[offset1],dir2,offset2);
		dirname[offset1 + offset2] = '\0';
	    }
	    else
	    {
		/* no name probably root or pc */
		strcpy(dirname,"root");
		strncpy(&dirname[4],dir2,offset2);
		dirname[offset2 + 4] = '\0';
	    }
	}
	else
	{
	    /* no name probably root or pc */
	    strcpy(dirname,"root");
	}
    }
    else
    {
	/* no / at the start - PC handling needed... */
	strcpy(dirname,"pc");
    }

    /* dirname contains the dirname to be used in the temp dir */
    dbcache = mygetenv("CSCOPECACHE", NULL);
    if(dbcache == NULL)
    {
	dbcache = tmpdir;
    } else {
	/*
	 * Cache limitation checks not needed if path is not same as tmp!!
	 * User could still add '/' or '/.' to var to avoid fschk
	 */
	if(strcmp(dbcache,tmpdir))
	    limitcheck = 0;
    }
    /* Set the mask so everyone can remove the dirs/files created */
    umask(0);

    if(access(dbcache,READ) != 0)
    {
	mkdir(dbcache,S_IRWXU|S_IRWXG|S_IRWXO);
    }

    /* Construct the real name and return it */
    /* +7 for cscope/ and +8 for extension and / and \0 */
    *fname = mymalloc(strlen(dbcache) + strlen(dirname) + strlen(file) + 7 + 8);
    sprintf(*fname,"%s/cscope",dbcache);
    if(access(*fname,READ) != 0)
    {
	mkdir(*fname,S_IRWXU|S_IRWXG|S_IRWXO);
    }
    if(cachedir == NULL)
	cachedir = stralloc(*fname);
    cachesubdir = stralloc(dirname);
    sprintf(*fname,"%s/cscope/%s",dbcache,dirname);
    if(access(*fname,READ) != 0)
    {
	mkdir(*fname,S_IRWXU|S_IRWXG|S_IRWXO);
    }
    else
    {
	/* touch this databases dir to ensure its not removed as old */
	sprintf(syscmd,"touch %s",*fname);    
	system(syscmd);
    }
    strcat(*fname,"/");
    strcat(*fname,file);
    if(fullpath != *fname) free(fullpath);
#endif
}

/* Func to check the cache size returns 1 if the conffile exists */
int limitcache(conffile,gencachedir,cachedir)
char *conffile,*gencachedir,*cachedir;
{
#if !defined(__BORLANDC__) && !defined(FOR_WIN32)
   struct STRUCT_STATVFS fsinf;
   struct dirent *dptr;
   DIR *d;
#endif
   FILE *f;
   unsigned long count = 0;
   int num2remove = 0,cfgd;
   int ret = 0;
   static int fschk = 0;

   /* Check to see if limitation is required */
   if(limitcheck == 0) {
       return ret;
   }

#if !defined(__BORLANDC__) && !defined(FOR_WIN32)
   /* Check the conf file */
   if(f = fopen(conffile,"r"))
   {
       if(fgets(syscmd,20,f))
       {
	   cfgd = atoi(syscmd);
	   d = opendir(gencachedir);
	   if(d)
	   {
	       while((dptr = readdir(d)) != NULL)
	       {
		   count++;
	       }
	       closedir(d);
    
	       /* Remove . and .. from the count */
	       count -= 2;
	   }

	   /* calculate number of dirs to remove */
	   if(cfgd < count)
	       num2remove = count - cfgd;
       }
       fclose(f);
       ret = 1;
   }
   /* Only do the FS check once */
   if((!fschk) && (count > 1))
   {
       fschk = 1;
       if(!STATVFS(gencachedir,&fsinf))
       {
	   if(fsinf.F_BAVAIL < fsinf.f_blocks/2)
	   {
	       fprintf(stderr,"Warning tmp filesystem >50%% full (blocks %ld:avail %ld) - removing an old db\n",fsinf.f_blocks,fsinf.F_BAVAIL);
	       num2remove = 1;
	   }
       }
    }
    /* There must be at least our dir */
    if(num2remove == count)
    {
	--num2remove;
    }
    if(num2remove > 0)
    {
	sprintf(syscmd,"rm -rf `ls -1tr %s | grep -v '%s' | head -%d | sed 's,^,%s/,'`",gencachedir,cachedir,num2remove,gencachedir);
	system(syscmd);
    }
#endif
    return ret;
}


int cacheuptodate(cache,zip)
char *cache,*zip;
{
    struct stat statp[2];

    /* Check the files exist and store the data */
    if (!vpstat(cache, &statp[0])) {
        if (!vpstat(zip, &statp[1])) {
           /* Compare modification times, zip should be older */
           return (statp[1].st_mtime > statp[0].st_mtime);
        } else return 1;
    } else return 1;
}
