/*
 * $Copyright:	$
 * Copyright (c) 1984, 1985, 1986, 1987, 1988, 1989, 1990 
 * Sequent Computer Systems, Inc.   All rights reserved.
 *  
 * This software is furnished under a license and may be used
 * only in accordance with the terms of that license and with the
 * inclusion of the above copyright notice.   This software may not
 * be provided or otherwise made available to, or used by, any
 * other person.  No title to or ownership of the software is
 * hereby transferred.
 *
 */
/* rcsfnms.c */

#ident "$Header: rcsfnms.c 1.5 89/10/26 $"

/*
 *                     RCS file name handling
 */
/****************************************************************************
 *                     creation and deletion of semaphorefile,
 *                     creation of temporary filenames and cleanup()
 *                     pairing of RCS file names and working file names.
 *                     Testprogram: define PAIRTEST
 ****************************************************************************
 *
 * Copyright (C) 1982 by Walter F. Tichy
 *                       Purdue University
 *                       Computer Science Department
 *                       West Lafayette, IN 47907
 *
 * All rights reserved. No part of this software may be sold or distributed
 * in any form or by any means without the prior written permission of the
 * author.
 * Report problems and direct all inquiries to Tichy@purdue (ARPA net).
 */


/* $Log:	rcsfnms.c,v $
 */


#include "rcsbase.h"
#include <sys/types.h>
#include <sys/stat.h>
/* #include <sys/dir.h>		   XXX */
#include <dirent.h>		/* XXX */

extern char * rindex();
extern char * mktemp();
extern char * malloc();
extern FILE * fopen();
/* extern char * getwd();         /* get working directory; forward decl  XXX    */
extern char * getcwd();         /* get working directory; forward decl       */
extern int    stat(), fstat();

extern FILE * finptr;          /* RCS input file descriptor                 */
extern FILE * frewrite;        /* New RCS file descriptor                   */
extern char * RCSfilename, * workfilename; /* filenames                     */
struct stat RCSstat, workstat; /* file status for RCS file and working file */
int    haveRCSstat,  haveworkstat; /* indicators if status availalble       */


char tempfilename [NCPFN+10];  /* used for derived file names               */
char sub1filename [NCPPN];     /* used for files path/file.sfx,v            */
char sub2filename [NCPPN];     /* used for files path/RCS/file.sfx,v        */
char semafilename [NCPPN];     /* name of semaphore file                    */
int  madesema;                 /* indicates whether a semaphore file has been set */
char * tfnames[10] =           /* temp. file names to be unlinked when finished   */
       {nil,nil,nil,nil,nil,nil,nil,nil,nil,nil};
int  lastfilename         = -1;/* index of last file name in tfnames[]      */


struct compair {
        char * suffix, * comlead;
};

struct compair comtable[] = {
/* comtable pairs each filename suffix with a comment leader. The comment   */
/* leader is placed before each line generated by the $Log keyword. This    */
/* table is used to guess the proper comment leader from the working file's */
/* suffix during initial ci (see InitAdmin()). Comment leaders are needed   */
/* for languages without multiline comments; for others they are optional.  */
        "c",   " * ",   /* C           */
	"csh", "# ",    /* shell       */
        "e",   "# ",    /* efl         */
        "f",   "c ",    /* fortran     */
        "h",   " * ",   /* C-header    */
        "l",   " * ",   /* lex         NOTE: conflict between lex and franzlisp*/
        "mac", "; ",    /* macro       vms or dec-20 or pdp-11 macro */
        "me",  "\\\" ", /* me-macros   t/nroff*/
	"mm",  "\\\" ", /* mm-macros   t/nroff*/
        "ms",  "\\\" ", /* ms-macros   t/nroff*/
        "p",   " * ",   /* pascal      */
        "r",   "# ",    /* ratfor      */
        "red", "% ",    /* psl/rlisp   */

#ifdef DYNIX_ptx
        "s",   "/ ",    /* assembler   */
#else
#ifdef sparc
        "s",   "! ",    /* assembler   */
#endif
#ifdef mc68000
        "s",   "| ",    /* assembler   */
#endif
#ifdef pdp11
        "s",   "/ ",    /* assembler   */
#endif
#ifdef vax
        "s",   "# ",    /* assembler   */
#endif
#endif /* DYNIX_ptx */

        "sh",  "# ",    /* shell       */
        "sl",  "% ",    /* psl         */
        "red", "% ",    /* psl/rlisp   */
        "cl",  ";;; ",  /* common lisp   */
        "ml",  "; ",    /* mocklisp    */
        "el",  "; ",    /* gnulisp     */
	"tex", "% ",	/* tex	       */
        "y",   " * ",   /* yacc        */
        "ye",  " * ",   /* yacc-efl    */
        "yr",  " * ",   /* yacc-ratfor */
        "",    "# ",    /* default for empty suffix */
        nil,   ""       /* default for unknown suffix; must always be last */
};


ffclose(fptr)
FILE * fptr;
/* Function: checks ferror(fptr) and aborts the program if there were
 * errors; otherwise closes fptr.
 */
{       if (ferror(fptr) || fclose(fptr)==EOF)
                faterror("File read or write error; file system full?");
}



int trysema(RCSfilename,makesema)
char * RCSfilename; int makesema;
/* Function: Checks whether a semaphore file exists for RCSfilename. If yes,
 * returns false. If not, creates one if makesema==true and returns true
 * if successful. If a semaphore file was created, madesema is set to true.
 * The name of the semaphore file is put into variable semafilename.
 */
{
        register char * tp, *sp, *lp;
        int fdesc;

        sp=RCSfilename;
        lp = rindex(sp,'/');
        if (lp==0) {
                semafilename[0]='.'; semafilename[1]='/';
                tp= &semafilename[2];
        } else {
                /* copy path */
                tp=semafilename;
                do *tp++ = *sp++; while (sp<=lp);
        }
        /*now insert `,' and append file name */
        *tp++ = ',';
        lp = rindex(sp, RCSSEP);
        while (sp<lp) *tp++ = *sp++;
        *tp++ = ','; *tp++ = '\0'; /* will be the same length as RCSfilename*/

        madesema = false;
        if (access(semafilename, 0) == 0) {
                error("RCS file %s is in use",RCSfilename);
                return false;
        }
        if (makesema) {
                if ((fdesc=creat(semafilename, 000)) == -1) {
                     error("Can't create semaphore file for RCS file %s",RCSfilename);
                     return false;
                } else
                     VOID close(fdesc);
                     madesema=true;
        }
        return true;
}


int rmsema()
/* Function: delete the semaphore file if madeseam==true;
 * sets madesema to false.
 */
{
        if (madesema) {
                madesema=false;
                if (unlink(semafilename) == -1) {
                        error("Can't find semaphore file %s",semafilename);
                        return false;
                }
        }
        return true;
}



InitCleanup()
{       lastfilename =  -1;  /* initialize pointer */
}


cleanup()
/* Function: closes input file and rewrite file.
 * Unlinks files in tfnames[], deletes semaphore file.
 */
{
        register int i;

        if (finptr!=NULL)   VOID fclose(finptr);
        if (frewrite!=NULL) VOID fclose(frewrite);
        for (i=0; i<=lastfilename; i++) {
            if (tfnames[i][0]!='\0')  VOID unlink(tfnames[i]);
        }
        InitCleanup();
        return (rmsema());
}


char * mktempfile(fullpath,filename)
register char * fullpath, * filename;
/* Function: Creates a unique filename using the process id and stores it
 * into a free slot in tfnames. The filename consists of the path contained
 * in fullpath concatenated with filename. filename should end in "XXXXXX".
 * Because of storage in tfnames, cleanup() can unlink the file later.
 * lastfilename indicates the highest occupied slot in tfnames.
 * Returns a pointer to the filename created.
 * Example use: mktempfile("/tmp/", somefilename)
 */
{
        register char * lastslash, *tp;
        lastfilename++;
        if ((tp=tfnames[lastfilename])==nil)
              tp=tfnames[lastfilename] = malloc(NCPPN);
        if (fullpath!=nil && (lastslash=rindex(fullpath,'/'))!=0) {
                /* copy path */
                while (fullpath<=lastslash) *tp++ = *fullpath++;
        }
        while (*tp++ = *filename++);
        return (mktemp(tfnames[lastfilename]));
}




char * bindex(sp,c)
register char * sp, c;
/* Function: Finds the last occurrence of character c in string sp
 * and returns a pointer to the character just beyond it. If the
 * character doesn't occur in the string, sp is returned.
 */
{       register char * r;
        r = sp;
        while (*sp) {
                if (*sp++ == c) r=sp;
        }
        return r;
}





InitAdmin()
/* function: initializes an admin node */
{       register char * Suffix;
        register int i;

        Head=Dbranch=nil; AccessList=nil; Symbols=nil; Locks=nil;
        StrictLocks=STRICT_LOCKING;

        /* guess the comment leader from the suffix*/
        Suffix=bindex(workfilename, '.');
        if (Suffix==workfilename) Suffix= ""; /* empty suffix; will get default*/
        for (i=0;;i++) {
                if (comtable[i].suffix==nil) {
                        Comment=comtable[i].comlead; /*default*/
                        break;
                } elsif (strcmp(Suffix,comtable[i].suffix)==0) {
                        Comment=comtable[i].comlead; /*default*/
                        break;
                }
        }
        Lexinit(); /* Note: if finptr==NULL, reads nothing; only initializes*/
}



char * findpairfile(argc, argv, fname)
int argc; char * argv[], *fname;
/* Function: Given a filename fname, findpairfile scans argv for a pathname
 * ending in fname. If found, returns a pointer to the pathname, and sets
 * the corresponding pointer in argv to nil. Otherwise returns fname.
 * argc indicates the number of entries in argv. Some of them may be nil.
 */
{
        register char * * next, * match;
        register int count;

        for (next = argv, count = argc; count>0; next++,count--) {
                if ((*next != nil) && strcmp(bindex(*next,'/'),fname)==0) {
                        /* bindex finds the beginning of the file name stem */
                        match= *next;
                        *next=nil;
                        return match;
                }
        }
        return fname;
}


int pairfilenames(argc, argv, mustread, tostdout)
int argc; char ** argv; int mustread, tostdout;
/* Function: Pairs the filenames pointed to by argv; argc indicates
 * how many there are.
 * Places a pointer to the RCS filename into RCSfilename,
 * and a pointer to the name of the working file into workfilename.
 * If both the workfilename and the RCS filename are given, and tostdout
 * is true, a warning is printed.
 *
 * If the working file exists, places its status into workstat and
 * sets haveworkstat to 0; otherwise, haveworkstat is set to -1;
 * Similarly for the RCS file and the variables RCSstat and haveRCSstat.
 *
 * If the RCS file exists, it is opened for reading, the file pointer
 * is placed into finptr, and the admin-node is read in; returns 1.
 * If the RCS file does not exist and mustread==true, an error is printed
 * and 0 returned.
 * If the RCS file does not exist and mustread==false, the admin node
 * is initialized to empty (Head, AccessList, Locks, Symbols, StrictLocks, Dbranch)
 * and -1 returned.
 *
 * 0 is returned on all errors. Files that are directories are errors.
 * Also calls InitCleanup();
 */
{
        register char * sp, * tp;
        char * lastsep, * purefname, * pureRCSname;
        int opened, returncode;
        char * RCS1;
	char prefdir[NCPPN];

        if (*argv == nil) return 0; /* already paired filename */
	if (rindex(*argv,KDELIM)!=0) {
		/* KDELIM causes havoc in keyword expansion    */
		error("RCS file name may not contain %c",KDELIM);
		return 0;
	}
        InitCleanup();

        /* first check suffix to see whether it is an RCS file or not */
        purefname=bindex(*argv, '/'); /* skip path */
        lastsep=rindex(purefname, RCSSEP);
        if (lastsep!= 0 && *(lastsep+1)==RCSSUF && *(lastsep+2)=='\0') {
                /* RCS file name given*/
                RCS1=(*argv); pureRCSname=purefname;
                /* derive workfilename*/
                sp = purefname; tp=tempfilename;
                while (sp<lastsep) *tp++ = *sp++; *tp='\0';
                /* try to find workfile name among arguments */
                workfilename=findpairfile(argc-1,argv+1,tempfilename);
                if (strlen(pureRCSname)>NCPFN) {
                        error("RCS file name %s too long",RCS1);
                        return 0;
                }
        } else {
                /* working file given; now try to find RCS file */
                workfilename= *argv;
                /* derive RCS file name*/
                sp=purefname; tp=tempfilename;
                while (*tp++ = *sp++);
                *(tp-1)=RCSSEP; *tp++=RCSSUF; *tp++='\0';
                /* Try to find RCS file name among arguments*/
                RCS1=findpairfile(argc-1,argv+1,tempfilename);
                pureRCSname=bindex(RCS1, '/');
                if (strlen(pureRCSname)>NCPFN) {
                        error("working file name %s too long",workfilename);
                        return 0;
                }
        }
        /* now we have a (tentative) RCS filename in RCS1 and workfilename  */
        /* First, get status of workfilename */
        haveworkstat=stat(workfilename, &workstat);
        if ((haveworkstat==0) && ((workstat.st_mode & S_IFDIR) == S_IFDIR)) {
                diagnose("Directory %s ignored",workfilename);
                return 0;
        }
        /* Second, try to find the right RCS file */
        if (pureRCSname!=RCS1) {
                /* a path for RCSfile is given; single RCS file to look for */
                finptr=fopen(RCSfilename=RCS1, "r");
                if (finptr!=NULL) {
                    returncode=1;
                } else { /* could not open */
                    if (access(RCSfilename,0)==0) {
                        error("Can't open existing %s", RCSfilename);
                        return 0;
                    }
                    if (mustread) {
                        error("Can't find %s", RCSfilename);
                        return 0;
                    } else {
                        /* initialize if not mustread */
                        returncode = -1;
                    }
                }
        } else {
		/* no path for RCS file name. Prefix it with path of work */
		/* file if RCS file omitted. Make a second name including */
		/* RCSDIR and try to open that one first.                 */
		sub1filename[0]=sub2filename[0]= '\0';
		if (RCS1==tempfilename) {
			/* RCS file name not given; prepend work path */
			sp= *argv; tp= sub1filename;
			while (sp<purefname) *tp++ = *sp ++;
			*tp='\0';
			VOID strcpy(sub2filename,sub1filename); /* second one */
		}
		VOID strcat(sub1filename,RCSDIR);
		VOID strcpy(prefdir,sub1filename); /* preferred directory for RCS file*/
		VOID strcat(sub1filename,RCS1); VOID strcat(sub2filename,RCS1);


                opened=(
		((finptr=fopen(RCSfilename=sub1filename, "r"))!=NULL) ||
		((finptr=fopen(RCSfilename=sub2filename,"r"))!=NULL) );

                if (opened) {
                        /* open succeeded */
                        returncode=1;
                } else {
                        /* open failed; may be read protected */
			if ((access(RCSfilename=sub1filename,0)==0) ||
			    (access(RCSfilename=sub2filename,0)==0)) {
                                error("Can't open existing %s",RCSfilename);
                                return 0;
                        }
                        if (mustread) {
				error("Can't find %s nor %s",sub1filename,sub2filename);
                                return 0;
                        } else {
                                /* initialize new file. Put into ./RCS if possible, strip off suffix*/
				RCSfilename= (access(prefdir,0)==0)?sub1filename:sub2filename;
                                returncode= -1;
                        }
                }
        }

        if (returncode == 1) { /* RCS file open */
                haveRCSstat=fstat(fileno(finptr),&RCSstat);
                if ((haveRCSstat== 0) && ((RCSstat.st_mode & S_IFDIR) == S_IFDIR)) {
                        diagnose("Directory %s ignored",RCSfilename);
                        return 0;
                }
                Lexinit(); getadmin();
        } else {  /* returncode == -1; RCS file nonexisting */
                haveRCSstat = -1;
                InitAdmin();
        };

        if (tostdout&&
            !(RCS1==tempfilename||workfilename==tempfilename))
                /*The last term determines whether a pair of        */
                /* file names was given in the argument list        */
                warn("Option -p is set; ignoring output file %s",workfilename);

        return returncode;
}


char * getfullRCSname()
/* Function: returns a pointer to the full path name of the RCS file.
 * Calls getwd(), but only once.
 * removes leading "../" and "./".
 */
{       static char pathbuf[NCPPN];
        static char namebuf[NCPPN];
        static int  pathlength =0;

        register char * realname, * lastpathchar;
        register int  dotdotcounter, realpathlength;

        if (*RCSfilename=='/') {
                return(RCSfilename);
        } else {
                if (pathlength==0) { /*call curdir for the first time*/
                    if (getcwd(pathbuf, NCPPN)==NULL)
                        faterror("Can't build current directory path");
                    pathlength=strlen(pathbuf);
                    if (!((pathlength==1) && (pathbuf[0]=='/'))) {
                        pathbuf[pathlength++]='/';
                        /* Check needed because some getwd implementations */
                        /* generate "/" for the root.                      */
                    }
                }
                /*the following must be redone since RCSfilename may change*/
                /* find how many ../ to remvove from RCSfilename */
                dotdotcounter =0;
                realname = RCSfilename;
                while( realname[0]=='.' &&
                      (realname[1]=='/'||(realname[1]=='.'&&realname[2]=='/'))){
                        if (realname[1]=='/') {
                            /* drop leading ./ */
                            realname += 2;
                        } else {
                            /* drop leading ../ and remember */
                            dotdotcounter++;
                            realname += 3;
                        }
                }
                /* now remove dotdotcounter trailing directories from pathbuf*/
                lastpathchar=pathbuf + pathlength-1;
                while (dotdotcounter>0 && lastpathchar>pathbuf) {
                    /* move pointer backwards over trailing directory */
                    lastpathchar--;
                    if (*lastpathchar=='/') {
                        dotdotcounter--;
                    }
                }
                if (dotdotcounter>0) {
                    error("Can't generate full path name for RCS file");
                    return RCSfilename;
                } else {
                    /* build full path name */
                    realpathlength=lastpathchar-pathbuf+1;
                    VOID strncpy(namebuf,pathbuf,realpathlength);
                    VOID strcpy(&namebuf[realpathlength],realname);
                    return(namebuf);
                }
        }
}



int trydiraccess(filename)
char * filename;
/* checks write permission in directory of filename and returns
 * true if writable, false otherwise
 */
{
        char pathname[NCPPN];
        register char * tp, *sp, *lp;
        lp = rindex(filename,'/');
        if (lp==0) {
                /* check current directory */
                if (access(".",2)==0)
                        return true;
                else {
                        error("Current directory not writable");
                        return false;
                }
        }
        /* copy path */
        sp=filename;
        tp=pathname;
        do *tp++ = *sp++; while (sp<=lp);
        *tp='\0';
        if (access(pathname,2)==0)
                return true;
        else {
                error("Directory %s not writable", pathname);
                return false;
        }
}



#ifndef DYNIX_ptx

#ifndef V4_2BSD
/* rename() and getwd() will be provided in bsd 4.2 */


int rename(from, to)
char * from, *to;
/* Function: renames a file with the name given by from to the name given by to.
 * unlinks the to-file if it already exists. returns -1 on error, 0 otherwise.
 */
{       VOID unlink(to);      /* no need to check return code; will be caught by link*/
                         /* no harm done if file "to" does not exist            */
        if (link(from,to)<0) return -1;
        return(unlink(from));
}



#define dot     "."
#define dotdot  ".."



char * getwd(name)
char * name;
/* Function: places full pathname of current working directory into name and
 * returns name on success, NULL on failure.
 * getwd is an adaptation of pwd. May not return to the current directory on
 * failure.
 */
{
        FILE    *file;
        struct  stat    d, dd;
        char buf[2];    /* to NUL-terminate dir.d_name */
   /*   struct  direct  dir;		   XXX */
        struct  dirent  dir;		/* XXX */

        int rdev, rino;
        int off;
        register i,j;

        name[off= 0] = '/';
        name[1] = '\0';
        buf[0] = '\0';
        stat("/", &d);
        rdev = d.st_dev;
        rino = d.st_ino;
        for (;;) {
                if (stat(dot, &d)<0) return NULL;
                if (d.st_ino==rino && d.st_dev==rdev) {
                        if (name[off] == '/') name[off] = '\0';
                        chdir(name); /*change back to current directory*/
                        return name;
                }
                if ((file = fopen(dotdot,"r")) == NULL) return NULL;
                if (fstat(fileno(file), &dd)<0) goto fail;
                chdir(dotdot);
                if(d.st_dev == dd.st_dev) {
                        if(d.st_ino == dd.st_ino) {
                            if (name[off] == '/') name[off] = '\0';
                            chdir(name); /*change back to current directory*/
                            VOID fclose(file);
                            return name;
                        }
                        do {
                            if (fread((char *)&dir, sizeof(dir), 1, file) !=1)
                                goto fail;
                        } while (dir.d_ino != d.st_ino);
                }
                else do {
                        if(fread((char *)&dir, sizeof(dir), 1, file) != 1) {
                            goto fail;
                        }
                        stat(dir.d_name, &dd);
                } while(dd.st_ino != d.st_ino || dd.st_dev != d.st_dev);
                VOID fclose(file);

                /* concatenate file name */
                i = -1;
                while (dir.d_name[++i] != 0);
                for(j=off+1; j>0; --j)
                        name[j+i+1] = name[j];
                off=i+off+1;
                name[i+1] = '/';
                for(--i; i>=0; --i)
                        name[i+1] = dir.d_name[i];
        } /* end for */

fail:   VOID fclose(file);
        return NULL;
}


#endif

#endif /* DYNIX_ptx */


#ifdef PAIRTEST
/* test program for pairfilenames() and getfullRCSname() */
char * workfilename, *RCSfilename;
extern int quietflag;

main(argc, argv)
int argc; char *argv[];
{
        int result;
        int initflag,tostdout;
        quietflag=tostdout=initflag=false;
        cmdid="pair";

        while(--argc, ++argv, argc>=1 && ((*argv)[0] == '-')) {
                switch ((*argv)[1]) {

                case 'p':       tostdout=true;
                                break;
                case 'i':       initflag=true;
                                break;
                case 'q':       quietflag=true;
                                break;
                default:        error("unknown option: %s", *argv);
                                break;
                }
        }

        do {
                RCSfilename=workfilename=nil;
                result=pairfilenames(argc,argv,!initflag,tostdout);
                if (result!=0) {
                     diagnose("RCSfile: %s; working file: %s",RCSfilename,workfilename);
                     diagnose("Full RCS file name: %s", getfullRCSname());
                }
                switch (result) {
                        case 0: continue; /* already paired file */

                        case 1: if (initflag) {
                                    error("RCS file %s exists already",RCSfilename);
                                } else {
                                    diagnose("RCS file %s exists",RCSfilename);
                                }
                                VOID fclose(finptr);
                                break;

                        case -1:diagnose("RCS file does not exist");
                                break;
                }

        } while (++argv, --argc>=1);

}
#endif
