/*
 * |-----------------------------------------------------------|
 * | Copyright (c) 1991, 1990 MIPS Computer Systems, Inc.      |
 * | All Rights Reserved                                       |
 * |-----------------------------------------------------------|
 * |          Restricted Rights Legend                         |
 * | Use, duplication, or disclosure by the Government is      |
 * | subject to restrictions as set forth in                   |
 * | subparagraph (c)(1)(ii) of the Rights in Technical        |
 * | Data and Computer Software Clause of DFARS 252.227-7013.  |
 * |         MIPS Computer Systems, Inc.                       |
 * |         950 DeGuigne Avenue                               |
 * |         Sunnyvale, California 94088-3650, USA             |
 * |-----------------------------------------------------------|
 */
#ident	"$Header: rcsfnms.c,v 1.7.2.1 92/06/24 13:54:29 marker Exp $"

/*
 *                     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).
 */


#include "rcsbase.h"
extern char * rindex();
extern char * mktemp();
extern char * malloc();
extern FILE * fopen();
extern char * getwd();         /* get working directory; forward decl       */

extern FILE * finptr;          /* RCS input file descriptor                 */
extern FILE * frewrite;        /* New RCS file descriptor                   */
extern char * RCSfilename, * workfilename; /* filenames                     */



char tempfilename [NCPFN+10];  /* used for derived file names               */
char subfilename  [NCPFN+14];  /* used for files 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           */
        "h",   " * ",   /* C-header    */
        "p",   " * ",   /* pascal      */
        "sh",  "# ",    /* shell       */
        "csh", "# ",    /* shell       */
#ifdef sun
        "s",   "| ",    /* assembler   */
#endif
#ifdef pdp11
        "s",   "/ ",    /* assembler   */
#endif
#ifdef vax
          "s",   "# ",    /* assembler   */
#endif
#ifdef mips
        "s",   "# ",    /* assembler   */
#endif
        "sl",  "% ",    /* psl         */
        "red", "% ",    /* psl/rlisp   */
        "cl", ";;; ",    /* common lisp   */
        "r",   "# ",    /* ratfor      */
        "e",   "# ",    /* efl         */
        "l",   " * ",   /* lex         NOTE: conflict between lex and franzlisp*/
        "y",   " * ",   /* yacc        */
        "yr",  " * ",   /* yacc-ratfor */
        "ye",  " * ",   /* yacc-efl    */
        "ml",  "; ",    /* mocklisp    */
        "el",  "; ",    /* gnulisp     */
        "mac", "; ",    /* macro       vms or dec-20 or pdp-11 macro */
        "f",   "c ",    /* fortran     */
        "ms",  "\\\" ", /* ms-macros   t/nroff*/
        "me",  "\\\" ", /* me-macros   t/nroff*/
        "mm",  "\\\" ", /* mm-macros   t/nroff*/
        "",    "# ",    /* 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=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 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),
 * and -1 returned.
 *
 * 0 is returned on all errors.
 * Also calls InitCleanup();
 */
{
        register char * sp, * tp;
        char * lastsep, * purefname, * pureRCSname;
        int opened, returncode;
        char * RCS1;

        if (*argv == nil) return 0; /* already paired filename */

        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  */

        if (pureRCSname!=RCS1) {
                /* a path for RCSfile is given; single RCS file to look for */
                finptr=fopen(RCSfilename=RCS1, "r");
                if (finptr!=NULL) {
                    Lexinit(); getadmin();
                    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 */
                        InitAdmin();
                        returncode = -1;
                    }
                }
        } else {
                /* build second RCS file name by prefixing it with RCSDIR*/
                /* then try to open one of them */
                VOID strcpy(subfilename,RCSDIR); strcat(subfilename,RCS1);
                opened=(
                ((finptr=fopen(RCSfilename=subfilename, "r"))!=NULL) ||
                ((finptr=fopen(RCSfilename=RCS1,"r"))!=NULL) );

                if (opened) {
                        /* open succeeded */
                        Lexinit(); getadmin();
                        returncode=1;
                } else {
                        /* open failed; may be read protected */
                        if ((access(RCSfilename=subfilename,0)==0) ||
                            (access(RCSfilename=RCS1,0)==0)) {
                                error("Can't open existing %s",RCSfilename);
                                return 0;
                        }
                        if (mustread) {
                                error("Can't find %s nor %s",subfilename,RCS1);
                                return 0;
                        } else {
                                /* initialize new file. Put into ./RCS if possible, strip off suffix*/
                                RCSfilename= (access(RCSDIR,0)==0)?subfilename:RCS1;
                                InitAdmin();
                                returncode= -1;
                                }
                        }
                }
                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 (getwd(pathbuf)==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 V4_XBSD
/* rename() and getwd() will be provided in bsd 4.2 */

#ifdef mips
/*
 * We have rename() and can simulate getwd() with getcwd().
 */

char *
getwd(name)
	char *name;
{
	extern char *getcwd();

	return getcwd(name, NCPPN);
}

#else


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));
}



#include        <sys/types.h>
#include        <sys/stat.h>
#include        <sys/dir.h>
#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;

        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


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

main(argc, argv)
int argc; char *argv[];
{
        int result;
        int initflag,tostdout;
        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;
                }
        }

        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 exists already");
                                    continue;
                                } else {
                                    diagnose("RCS file exists");
                                }
                                break;

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

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

}
#endif
