2 * RCS file name handling
6 rcsid
[]= "$Id: rcsfnms.c,v 1.1 1993/03/21 09:58:08 cgd Exp $ Purdue CS";
8 /****************************************************************************
9 * creation and deletion of semaphorefile,
10 * creation of temporary filenames and cleanup()
11 * pairing of RCS file names and working file names.
12 * Testprogram: define PAIRTEST
13 ****************************************************************************
16 /* Copyright (C) 1982, 1988, 1989 Walter Tichy
17 * All rights reserved.
19 * Redistribution and use in source and binary forms are permitted
20 * provided that the above copyright notice and this paragraph are
21 * duplicated in all such forms and that any documentation,
22 * advertising materials, and other materials related to such
23 * distribution and use acknowledge that the software was developed
25 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
26 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
27 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
29 * Report all problems and direct all questions to:
30 * rcs-bugs@cs.purdue.edu
44 /* $Log: rcsfnms.c,v $
45 * Revision 3.12 89/08/15 21:38:10 bostic
46 * Version 4 from Tom Narten at Purdue
48 * Revision 4.8 89/05/01 15:09:41 narten
49 * changed getwd to not stat empty directories.
51 * Revision 4.7 88/11/08 12:01:22 narten
52 * changes from eggert@sm.unisys.com (Paul Eggert)
54 * Revision 4.7 88/08/09 19:12:53 eggert
55 * Fix troff macro comment leader bug; add Prolog; allow cc -R; remove lint.
57 * Revision 4.6 87/12/18 11:40:23 narten
58 * additional file types added from 4.3 BSD version, and SPARC assembler
59 * comment character added. Also, more lint cleanups. (Guy Harris)
61 * Revision 4.5 87/10/18 10:34:16 narten
62 * Updating version numbers. Changes relative to 1.1 actually relative
65 * Revision 1.3 87/03/27 14:22:21 jenkins
68 * Revision 1.2 85/06/26 07:34:28 svb
69 * Comment leader '% ' for '*.tex' files added.
71 * Revision 1.1 84/01/23 14:50:24 kcs
74 * Revision 4.3 83/12/15 12:26:48 wft
75 * Added check for KDELIM in file names to pairfilenames().
77 * Revision 4.2 83/12/02 22:47:45 wft
78 * Added csh, red, and sl file name suffixes.
80 * Revision 4.1 83/05/11 16:23:39 wft
81 * Added initialization of Dbranch to InitAdmin(). Canged pairfilenames():
82 * 1. added copying of path from workfile to RCS file, if RCS file is omitted;
83 * 2. added getting the file status of RCS and working files;
84 * 3. added ignoring of directories.
86 * Revision 3.7 83/05/11 15:01:58 wft
87 * Added comtable[] which pairs file name suffixes with comment leaders;
88 * updated InitAdmin() accordingly.
90 * Revision 3.6 83/04/05 14:47:36 wft
91 * fixed Suffix in InitAdmin().
93 * Revision 3.5 83/01/17 18:01:04 wft
94 * Added getwd() and rename(); these can be removed by defining
95 * V4_2BSD, since they are not needed in 4.2 bsd.
96 * Changed sys/param.h to sys/types.h.
98 * Revision 3.4 82/12/08 21:55:20 wft
99 * removed unused variable.
101 * Revision 3.3 82/11/28 20:31:37 wft
102 * Changed mktempfile() to store the generated file names.
103 * Changed getfullRCSname() to store the file and pathname, and to
104 * delete leading "../" and "./".
106 * Revision 3.2 82/11/12 14:29:40 wft
107 * changed pairfilenames() to handle file.sfx,v; also deleted checkpathnosfx(),
108 * checksuffix(), checkfullpath(). Semaphore name generation updated.
109 * mktempfile() now checks for nil path; freefilename initialized properly.
110 * Added Suffix .h to InitAdmin. Added testprogram PAIRTEST.
111 * Moved rmsema, trysema, trydiraccess, getfullRCSname from rcsutil.c to here.
113 * Revision 3.1 82/10/18 14:51:28 wft
114 * InitAdmin() now initializes StrictLocks=STRICT_LOCKING (def. in rcsbase.h).
115 * renamed checkpath() to checkfullpath().
120 #include <sys/types.h>
121 #include <sys/stat.h>
124 extern char * rindex();
125 extern char * mktemp();
126 extern FILE * fopen();
127 extern char * getwd(); /* get working directory; forward decl */
128 extern int stat(), fstat();
130 extern FILE * finptr
; /* RCS input file descriptor */
131 extern FILE * frewrite
; /* New RCS file descriptor */
132 extern char * RCSfilename
, * workfilename
; /* filenames */
133 struct stat RCSstat
, workstat
; /* file status for RCS file and working file */
134 int haveRCSstat
, haveworkstat
; /* indicators if status availalble */
137 char tempfilename
[NCPFN
+10]; /* used for derived file names */
138 char sub1filename
[NCPPN
]; /* used for files path/file.sfx,v */
139 char sub2filename
[NCPPN
]; /* used for files path/RCS/file.sfx,v */
140 char semafilename
[NCPPN
]; /* name of semaphore file */
141 int madesema
; /* indicates whether a semaphore file has been set */
142 char * tfnames
[10]; /* temp. file names to be unlinked when finished */
143 int freefilename
; /* index of next free file name in tfnames[] */
147 char * suffix
, * comlead
;
150 struct compair comtable
[] = {
151 /* comtable pairs each filename suffix with a comment leader. The comment */
152 /* leader is placed before each line generated by the $Log keyword. This */
153 /* table is used to guess the proper comment leader from the working file's */
154 /* suffix during initial ci (see InitAdmin()). Comment leaders are needed */
155 /* for languages without multiline comments; for others they are optional. */
157 "csh", "# ", /* shell */
159 "f", "c ", /* fortran */
160 "h", " * ", /* C-header */
161 "l", " * ", /* lex NOTE: conflict between lex and franzlisp*/
162 "mac", "; ", /* macro vms or dec-20 or pdp-11 macro */
163 "me", ".\\\" ",/* me-macros t/nroff*/
164 "mm", ".\\\" ",/* mm-macros t/nroff*/
165 "ms", ".\\\" ",/* ms-macros t/nroff*/
166 "p", " * ", /* pascal */
167 "pl", "% ", /* prolog */
168 "r", "# ", /* ratfor */
169 "red", "% ", /* psl/rlisp */
172 "s", "! ", /* assembler */
175 "s", "| ", /* assembler */
178 "s", "/ ", /* assembler */
181 "s", "# ", /* assembler */
184 "sh", "# ", /* shell */
185 "sl", "% ", /* psl */
186 "red", "% ", /* psl/rlisp */
187 "cl", ";;; ", /* common lisp */
188 "ml", "; ", /* mocklisp */
189 "el", "; ", /* gnulisp */
190 "tex", "% ", /* tex */
191 "y", " * ", /* yacc */
192 "ye", " * ", /* yacc-efl */
193 "yr", " * ", /* yacc-ratfor */
194 "", "# ", /* default for empty suffix */
195 nil
, "" /* default for unknown suffix; must always be last */
201 /* Function: checks ferror(fptr) and aborts the program if there were
202 * errors; otherwise closes fptr.
204 { if (ferror(fptr
) || fclose(fptr
)==EOF
)
205 faterror("File read or write error; file system full?");
210 int trysema(RCSname
,makesema
)
211 char * RCSname
; int makesema
;
212 /* Function: Checks whether a semaphore file exists for RCSname. If yes,
213 * returns false. If not, creates one if makesema==true and returns true
214 * if successful. If a semaphore file was created, madesema is set to true.
215 * The name of the semaphore file is put into variable semafilename.
218 register char * tp
, *sp
, *lp
;
224 semafilename
[0]='.'; semafilename
[1]='/';
225 tp
= &semafilename
[2];
229 do *tp
++ = *sp
++; while (sp
<=lp
);
231 /*now insert `,' and append file name */
233 lp
= rindex(sp
, RCSSEP
);
234 while (sp
<lp
) *tp
++ = *sp
++;
235 *tp
++ = ','; *tp
++ = '\0'; /* will be the same length as RCSname*/
238 if (access(semafilename
, 0) == 0) {
239 error("RCS file %s is in use",RCSname
);
243 if ((fdesc
=creat(semafilename
, 000)) == -1) {
244 error("Can't create semaphore file for RCS file %s",RCSname
);
255 /* Function: delete the semaphore file if madeseam==true;
256 * sets madesema to false.
261 if (unlink(semafilename
) == -1) {
262 error("Can't find semaphore file %s",semafilename
);
270 { freefilename
= 0; /* initialize pointer */
275 /* Function: closes input file and rewrite file.
276 * Unlinks files in tfnames[], deletes semaphore file.
281 if (finptr
!=NULL
) VOID
fclose(finptr
);
282 if (frewrite
!=NULL
) VOID
fclose(frewrite
);
283 for (i
=0; i
<freefilename
; i
++) {
284 if (tfnames
[i
][0]!='\0') VOID
unlink(tfnames
[i
]);
291 char * mktempfile(fullpath
,filename
)
292 register char * fullpath
, * filename
;
293 /* Function: Creates a unique filename using the process id and stores it
294 * into a free slot in tfnames. The filename consists of the path contained
295 * in fullpath concatenated with filename. filename should end in "XXXXXX".
296 * Because of storage in tfnames, cleanup() can unlink the file later.
297 * freefilename indicates the lowest unoccupied slot in tfnames.
298 * Returns a pointer to the filename created.
299 * Example use: mktempfile("/tmp/", somefilename)
302 register char * lastslash
, *tp
;
303 if ((tp
=tfnames
[freefilename
])==nil
)
304 tp
=tfnames
[freefilename
] = talloc(NCPPN
);
305 if (fullpath
!=nil
&& (lastslash
=rindex(fullpath
,'/'))!=0) {
307 while (fullpath
<=lastslash
) *tp
++ = *fullpath
++;
309 while (*tp
++ = *filename
++);
310 return (mktemp(tfnames
[freefilename
++]));
317 register char * sp
, c
;
318 /* Function: Finds the last occurrence of character c in string sp
319 * and returns a pointer to the character just beyond it. If the
320 * character doesn't occur in the string, sp is returned.
325 if (*sp
++ == c
) r
=sp
;
335 /* function: initializes an admin node */
336 { register char * Suffix
;
339 Head
=Dbranch
=nil
; AccessList
=nil
; Symbols
=nil
; Locks
=nil
;
340 StrictLocks
=STRICT_LOCKING
;
342 /* guess the comment leader from the suffix*/
343 Suffix
=bindex(workfilename
, '.');
344 if (Suffix
==workfilename
) Suffix
= ""; /* empty suffix; will get default*/
346 if (comtable
[i
].suffix
==nil
) {
347 Comment
=comtable
[i
].comlead
; /*default*/
349 } elsif (strcmp(Suffix
,comtable
[i
].suffix
)==0) {
350 Comment
=comtable
[i
].comlead
; /*default*/
354 Lexinit(); /* Note: if finptr==NULL, reads nothing; only initializes*/
359 char * findpairfile(argc
, argv
, fname
)
360 int argc
; char * argv
[], *fname
;
361 /* Function: Given a filename fname, findpairfile scans argv for a pathname
362 * ending in fname. If found, returns a pointer to the pathname, and sets
363 * the corresponding pointer in argv to nil. Otherwise returns fname.
364 * argc indicates the number of entries in argv. Some of them may be nil.
367 register char * * next
, * match
;
370 for (next
= argv
, count
= argc
; count
>0; next
++,count
--) {
371 if ((*next
!= nil
) && strcmp(bindex(*next
,'/'),fname
)==0) {
372 /* bindex finds the beginning of the file name stem */
382 int pairfilenames(argc
, argv
, mustread
, tostdout
)
383 int argc
; char ** argv
; int mustread
, tostdout
;
384 /* Function: Pairs the filenames pointed to by argv; argc indicates
385 * how many there are.
386 * Places a pointer to the RCS filename into RCSfilename,
387 * and a pointer to the name of the working file into workfilename.
388 * If both the workfilename and the RCS filename are given, and tostdout
389 * is true, a warning is printed.
391 * If the working file exists, places its status into workstat and
392 * sets haveworkstat to 0; otherwise, haveworkstat is set to -1;
393 * Similarly for the RCS file and the variables RCSstat and haveRCSstat.
395 * If the RCS file exists, it is opened for reading, the file pointer
396 * is placed into finptr, and the admin-node is read in; returns 1.
397 * If the RCS file does not exist and mustread==true, an error is printed
399 * If the RCS file does not exist and mustread==false, the admin node
400 * is initialized to empty (Head, AccessList, Locks, Symbols, StrictLocks, Dbranch)
403 * 0 is returned on all errors. Files that are directories are errors.
404 * Also calls InitCleanup();
407 register char * sp
, * tp
;
408 char * lastsep
, * purefname
, * pureRCSname
;
409 int opened
, returncode
;
413 if (*argv
== nil
) return 0; /* already paired filename */
414 if (rindex(*argv
,KDELIM
)!=0) {
415 /* KDELIM causes havoc in keyword expansion */
416 error("RCS file name may not contain %c",KDELIM
);
421 /* first check suffix to see whether it is an RCS file or not */
422 purefname
=bindex(*argv
, '/'); /* skip path */
423 lastsep
=rindex(purefname
, RCSSEP
);
424 if (lastsep
!= 0 && *(lastsep
+1)==RCSSUF
&& *(lastsep
+2)=='\0') {
425 /* RCS file name given*/
426 RCS1
=(*argv
); pureRCSname
=purefname
;
427 /* derive workfilename*/
428 sp
= purefname
; tp
=tempfilename
;
429 while (sp
<lastsep
) *tp
++ = *sp
++; *tp
='\0';
430 /* try to find workfile name among arguments */
431 workfilename
=findpairfile(argc
-1,argv
+1,tempfilename
);
432 if (strlen(pureRCSname
)>NCPFN
) {
433 error("RCS file name %s too long",RCS1
);
437 /* working file given; now try to find RCS file */
439 /* derive RCS file name*/
440 sp
=purefname
; tp
=tempfilename
;
441 while (*tp
++ = *sp
++);
442 *(tp
-1)=RCSSEP
; *tp
++=RCSSUF
; *tp
++='\0';
443 /* Try to find RCS file name among arguments*/
444 RCS1
=findpairfile(argc
-1,argv
+1,tempfilename
);
445 pureRCSname
=bindex(RCS1
, '/');
446 if (strlen(pureRCSname
)>NCPFN
) {
447 error("working file name %s too long",workfilename
);
451 /* now we have a (tentative) RCS filename in RCS1 and workfilename */
452 /* First, get status of workfilename */
453 haveworkstat
=stat(workfilename
, &workstat
);
454 if ((haveworkstat
==0) && ((workstat
.st_mode
& S_IFDIR
) == S_IFDIR
)) {
455 diagnose("Directory %s ignored",workfilename
);
458 /* Second, try to find the right RCS file */
459 if (pureRCSname
!=RCS1
) {
460 /* a path for RCSfile is given; single RCS file to look for */
461 finptr
=fopen(RCSfilename
=RCS1
, "r");
464 } else { /* could not open */
465 if (access(RCSfilename
,0)==0) {
466 error("Can't open existing %s", RCSfilename
);
470 error("Can't find %s", RCSfilename
);
473 /* initialize if not mustread */
478 /* no path for RCS file name. Prefix it with path of work */
479 /* file if RCS file omitted. Make a second name including */
480 /* RCSDIR and try to open that one first. */
481 sub1filename
[0]=sub2filename
[0]= '\0';
482 if (RCS1
==tempfilename
) {
483 /* RCS file name not given; prepend work path */
484 sp
= *argv
; tp
= sub1filename
;
485 while (sp
<purefname
) *tp
++ = *sp
++;
487 VOID
strcpy(sub2filename
,sub1filename
); /* second one */
489 VOID
strcat(sub1filename
,RCSDIR
);
490 VOID
strcpy(prefdir
,sub1filename
); /* preferred directory for RCS file*/
491 VOID
strcat(sub1filename
,RCS1
); VOID
strcat(sub2filename
,RCS1
);
495 ((finptr
=fopen(RCSfilename
=sub1filename
, "r"))!=NULL
) ||
496 ((finptr
=fopen(RCSfilename
=sub2filename
,"r"))!=NULL
) );
502 /* open failed; may be read protected */
503 if ((access(RCSfilename
=sub1filename
,0)==0) ||
504 (access(RCSfilename
=sub2filename
,0)==0)) {
505 error("Can't open existing %s",RCSfilename
);
509 error("Can't find %s nor %s",sub1filename
,sub2filename
);
512 /* initialize new file. Put into ./RCS if possible, strip off suffix*/
513 RCSfilename
= (access(prefdir
,0)==0)?sub1filename
:sub2filename
;
519 if (returncode
== 1) { /* RCS file open */
520 haveRCSstat
=fstat(fileno(finptr
),&RCSstat
);
521 if ((haveRCSstat
== 0) && ((RCSstat
.st_mode
& S_IFDIR
) == S_IFDIR
)) {
522 diagnose("Directory %s ignored",RCSfilename
);
525 Lexinit(); getadmin();
526 } else { /* returncode == -1; RCS file nonexisting */
532 !(RCS1
==tempfilename
||workfilename
==tempfilename
))
533 /*The last term determines whether a pair of */
534 /* file names was given in the argument list */
535 warn("Option -p is set; ignoring output file %s",workfilename
);
541 char * getfullRCSname()
542 /* Function: returns a pointer to the full path name of the RCS file.
543 * Calls getwd(), but only once.
544 * removes leading "../" and "./".
546 { static char pathbuf
[NCPPN
];
547 static char namebuf
[NCPPN
];
548 static int pathlength
;
550 register char * realname
, * lastpathchar
;
551 register int dotdotcounter
, realpathlength
;
553 if (*RCSfilename
=='/') {
556 if (pathlength
==0) { /*call curdir for the first time*/
557 if (getwd(pathbuf
)==NULL
)
558 faterror("Can't build current directory path");
559 pathlength
=strlen(pathbuf
);
560 if (!((pathlength
==1) && (pathbuf
[0]=='/'))) {
561 pathbuf
[pathlength
++]='/';
562 /* Check needed because some getwd implementations */
563 /* generate "/" for the root. */
566 /*the following must be redone since RCSfilename may change*/
567 /* find how many ../ to remvove from RCSfilename */
569 realname
= RCSfilename
;
570 while( realname
[0]=='.' &&
571 (realname
[1]=='/'||(realname
[1]=='.'&&realname
[2]=='/'))){
572 if (realname
[1]=='/') {
573 /* drop leading ./ */
576 /* drop leading ../ and remember */
581 /* now remove dotdotcounter trailing directories from pathbuf*/
582 lastpathchar
=pathbuf
+ pathlength
-1;
583 while (dotdotcounter
>0 && lastpathchar
>pathbuf
) {
584 /* move pointer backwards over trailing directory */
586 if (*lastpathchar
=='/') {
590 if (dotdotcounter
>0) {
591 error("Can't generate full path name for RCS file");
594 /* build full path name */
595 realpathlength
=lastpathchar
-pathbuf
+1;
596 VOID
strncpy(namebuf
,pathbuf
,realpathlength
);
597 VOID
strcpy(&namebuf
[realpathlength
],realname
);
605 int trydiraccess(filename
)
607 /* checks write permission in directory of filename and returns
608 * true if writable, false otherwise
611 char pathname
[NCPPN
];
612 register char * tp
, *sp
, *lp
;
613 lp
= rindex(filename
,'/');
615 /* check current directory */
616 if (access(".",2)==0)
619 error("Current directory not writable");
626 do *tp
++ = *sp
++; while (sp
<=lp
);
628 if (access(pathname
,2)==0)
631 error("Directory %s not writable", pathname
);
639 /* rename() and getwd() will be provided in bsd 4.2 */
644 /* Function: renames a file with the name given by from to the name given by to.
645 * unlinks the to-file if it already exists. returns -1 on error, 0 otherwise.
647 { VOID
unlink(to
); /* no need to check return code; will be caught by link*/
648 /* no harm done if file "to" does not exist */
649 if (link(from
,to
)<0) return -1;
650 return(unlink(from
));
662 /* Function: places full pathname of current working directory into name and
663 * returns name on success, NULL on failure.
664 * getwd is an adaptation of pwd. May not return to the current directory on
670 char buf
[2]; /* to NUL-terminate dir.d_name */
680 if (stat("/", &d
)<0) return NULL
;
684 if (stat(dot
, &d
)<0) return NULL
;
685 if (d
.st_ino
==rino
&& d
.st_dev
==rdev
) {
686 if (name
[off
] == '/') name
[off
] = '\0';
687 chdir(name
); /*change back to current directory*/
690 if ((file
= fopen(dotdot
,"r")) == NULL
) return NULL
;
691 if (fstat(fileno(file
), &dd
)<0) goto fail
;
693 if(d
.st_dev
== dd
.st_dev
) {
694 if(d
.st_ino
== dd
.st_ino
) {
695 if (name
[off
] == '/') name
[off
] = '\0';
696 chdir(name
); /*change back to current directory*/
701 if (fread((char *)&dir
, sizeof(dir
), 1, file
) !=1)
703 } while (dir
.d_ino
!= d
.st_ino
);
706 if(fread((char *)&dir
, sizeof(dir
), 1, file
) != 1) {
710 dd
.st_ino
= d
.st_ino
+ 1;
711 else if (stat(dir
.d_name
, &dd
) < 0)
713 } while(dd
.st_ino
!= d
.st_ino
|| dd
.st_dev
!= d
.st_dev
);
716 /* concatenate file name */
718 while (dir
.d_name
[++i
] != 0);
719 for(j
=off
+1; j
>0; --j
)
720 name
[j
+i
+1] = name
[j
];
724 name
[i
+1] = dir
.d_name
[i
];
727 fail
: VOID
fclose(file
);
736 /* test program for pairfilenames() and getfullRCSname() */
737 char * workfilename
, *RCSfilename
;
738 extern int quietflag
;
741 int argc
; char *argv
[];
744 int initflag
,tostdout
;
745 quietflag
=tostdout
=initflag
=false;
748 while(--argc
, ++argv
, argc
>=1 && ((*argv
)[0] == '-')) {
749 switch ((*argv
)[1]) {
751 case 'p': tostdout
=true;
753 case 'i': initflag
=true;
755 case 'q': quietflag
=true;
757 default: error("unknown option: %s", *argv
);
763 RCSfilename
=workfilename
=nil
;
764 result
=pairfilenames(argc
,argv
,!initflag
,tostdout
);
766 diagnose("RCSfile: %s; working file: %s",RCSfilename
,workfilename
);
767 diagnose("Full RCS file name: %s", getfullRCSname());
770 case 0: continue; /* already paired file */
772 case 1: if (initflag
) {
773 error("RCS file %s exists already",RCSfilename
);
775 diagnose("RCS file %s exists",RCSfilename
);
780 case -1:diagnose("RCS file does not exist");
784 } while (++argv
, --argc
>=1);