xref: /original-bsd/contrib/rcs-V5.6/src/rcsfnms.c (revision 627f8e86)
1 /*
2  *                     RCS file name handling
3  */
4 /****************************************************************************
5  *                     creation and deletion of /tmp temporaries
6  *                     pairing of RCS file names and working file names.
7  *                     Testprogram: define PAIRTEST
8  ****************************************************************************
9  */
10 
11 /* Copyright (C) 1982, 1988, 1989 Walter Tichy
12    Copyright 1990, 1991 by Paul Eggert
13    Distributed under license by the Free Software Foundation, Inc.
14 
15 This file is part of RCS.
16 
17 RCS is free software; you can redistribute it and/or modify
18 it under the terms of the GNU General Public License as published by
19 the Free Software Foundation; either version 2, or (at your option)
20 any later version.
21 
22 RCS is distributed in the hope that it will be useful,
23 but WITHOUT ANY WARRANTY; without even the implied warranty of
24 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
25 GNU General Public License for more details.
26 
27 You should have received a copy of the GNU General Public License
28 along with RCS; see the file COPYING.  If not, write to
29 the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
30 
31 Report problems and direct all questions to:
32 
33     rcs-bugs@cs.purdue.edu
34 
35 */
36 
37 
38 
39 
40 /* $Log: rcsfnms.c,v $
41  * Revision 5.8  1991/09/24  00:28:40  eggert
42  * Don't export bindex().
43  *
44  * Revision 5.7  1991/08/19  03:13:55  eggert
45  * Fix messages when rcswriteopen fails.
46  * Look in $TMP and $TEMP if $TMPDIR isn't set.  Tune.
47  *
48  * Revision 5.6  1991/04/21  11:58:23  eggert
49  * Fix errno bugs.  Add -x, RCSINIT, MS-DOS support.
50  *
51  * Revision 5.5  1991/02/26  17:48:38  eggert
52  * Fix setuid bug.  Support new link behavior.
53  * Define more portable getcwd().
54  *
55  * Revision 5.4  1990/11/01  05:03:43  eggert
56  * Permit arbitrary data in comment leaders.
57  *
58  * Revision 5.3  1990/09/14  22:56:16  hammer
59  * added more filename extensions and their comment leaders
60  *
61  * Revision 5.2  1990/09/04  08:02:23  eggert
62  * Fix typo when !RCSSEP.
63  *
64  * Revision 5.1  1990/08/29  07:13:59  eggert
65  * Work around buggy compilers with defective argument promotion.
66  *
67  * Revision 5.0  1990/08/22  08:12:50  eggert
68  * Ignore signals when manipulating the semaphore file.
69  * Modernize list of file name extensions.
70  * Permit paths of arbitrary length.  Beware file names beginning with "-".
71  * Remove compile-time limits; use malloc instead.
72  * Permit dates past 1999/12/31.  Make lock and temp files faster and safer.
73  * Ansify and Posixate.
74  * Don't use access().  Fix test for non-regular files.  Tune.
75  *
76  * Revision 4.8  89/05/01  15:09:41  narten
77  * changed getwd to not stat empty directories.
78  *
79  * Revision 4.7  88/08/09  19:12:53  eggert
80  * Fix troff macro comment leader bug; add Prolog; allow cc -R; remove lint.
81  *
82  * Revision 4.6  87/12/18  11:40:23  narten
83  * additional file types added from 4.3 BSD version, and SPARC assembler
84  * comment character added. Also, more lint cleanups. (Guy Harris)
85  *
86  * Revision 4.5  87/10/18  10:34:16  narten
87  * Updating version numbers. Changes relative to 1.1 actually relative
88  * to verion 4.3
89  *
90  * Revision 1.3  87/03/27  14:22:21  jenkins
91  * Port to suns
92  *
93  * Revision 1.2  85/06/26  07:34:28  svb
94  * Comment leader '% ' for '*.tex' files added.
95  *
96  * Revision 4.3  83/12/15  12:26:48  wft
97  * Added check for KDELIM in file names to pairfilenames().
98  *
99  * Revision 4.2  83/12/02  22:47:45  wft
100  * Added csh, red, and sl file name suffixes.
101  *
102  * Revision 4.1  83/05/11  16:23:39  wft
103  * Added initialization of Dbranch to InitAdmin(). Canged pairfilenames():
104  * 1. added copying of path from workfile to RCS file, if RCS file is omitted;
105  * 2. added getting the file status of RCS and working files;
106  * 3. added ignoring of directories.
107  *
108  * Revision 3.7  83/05/11  15:01:58  wft
109  * Added comtable[] which pairs file name suffixes with comment leaders;
110  * updated InitAdmin() accordingly.
111  *
112  * Revision 3.6  83/04/05  14:47:36  wft
113  * fixed Suffix in InitAdmin().
114  *
115  * Revision 3.5  83/01/17  18:01:04  wft
116  * Added getwd() and rename(); these can be removed by defining
117  * V4_2BSD, since they are not needed in 4.2 bsd.
118  * Changed sys/param.h to sys/types.h.
119  *
120  * Revision 3.4  82/12/08  21:55:20  wft
121  * removed unused variable.
122  *
123  * Revision 3.3  82/11/28  20:31:37  wft
124  * Changed mktempfile() to store the generated file names.
125  * Changed getfullRCSname() to store the file and pathname, and to
126  * delete leading "../" and "./".
127  *
128  * Revision 3.2  82/11/12  14:29:40  wft
129  * changed pairfilenames() to handle file.sfx,v; also deleted checkpathnosfx(),
130  * checksuffix(), checkfullpath(). Semaphore name generation updated.
131  * mktempfile() now checks for nil path; freefilename initialized properly.
132  * Added Suffix .h to InitAdmin. Added testprogram PAIRTEST.
133  * Moved rmsema, trysema, trydiraccess, getfullRCSname from rcsutil.c to here.
134  *
135  * Revision 3.1  82/10/18  14:51:28  wft
136  * InitAdmin() now initializes StrictLocks=STRICT_LOCKING (def. in rcsbase.h).
137  * renamed checkpath() to checkfullpath().
138  */
139 
140 
141 #include "rcsbase.h"
142 
143 libId(fnmsId, "$Id: rcsfnms.c,v 5.8 1991/09/24 00:28:40 eggert Exp $")
144 
145 char const *RCSfilename;
146 char *workfilename;
147 FILE *workstdout;
148 struct stat RCSstat;
149 char const *suffixes;
150 
151 static char const rcsdir[] = "RCS";
152 #define rcsdirlen (sizeof(rcsdir)-1)
153 
154 static struct buf RCSbuf, RCSb;
155 static int RCSerrno;
156 
157 
158 /* Temp file names to be unlinked when done, if they are not nil.  */
159 #define TEMPNAMES 5 /* must be at least DIRTEMPNAMES (see rcsedit.c) */
160 static char *volatile tfnames[TEMPNAMES];
161 
162 
163 struct compair {
164 	char const *suffix, *comlead;
165 };
166 
167 static struct compair const comtable[] = {
168 /* comtable pairs each filename suffix with a comment leader. The comment   */
169 /* leader is placed before each line generated by the $Log keyword. This    */
170 /* table is used to guess the proper comment leader from the working file's */
171 /* suffix during initial ci (see InitAdmin()). Comment leaders are needed   */
172 /* for languages without multiline comments; for others they are optional.  */
173 	"a",   "-- ",   /* Ada         */
174 	"ada", "-- ",
175 	"asm", ";; ",	/* assembler (MS-DOS) */
176 	"bat", ":: ",	/* batch (MS-DOS) */
177         "c",   " * ",   /* C           */
178 	"c++", "// ",	/* C++ in all its infinite guises */
179 	"cc",  "// ",
180 	"cpp", "// ",
181 	"cxx", "// ",
182 	"cl",  ";;; ",  /* Common Lisp */
183 	"cmd", ":: ",	/* command (OS/2) */
184 	"cmf", "c ",	/* CM Fortran  */
185 	"cs",  " * ",	/* C*          */
186 	"el",  "; ",    /* Emacs Lisp  */
187 	"f",   "c ",    /* Fortran     */
188 	"for", "c ",
189         "h",   " * ",   /* C-header    */
190 	"hpp", "// ",	/* C++ header  */
191 	"hxx", "// ",
192         "l",   " * ",   /* lex      NOTE: conflict between lex and franzlisp */
193 	"lisp",";;; ",	/* Lucid Lisp  */
194 	"lsp", ";; ",	/* Microsoft Lisp */
195 	"mac", ";; ",	/* macro (DEC-10, MS-DOS, PDP-11, VMS, etc) */
196 	"me",  ".\\\" ",/* me-macros   t/nroff*/
197 	"ml",  "; ",    /* mocklisp    */
198 	"mm",  ".\\\" ",/* mm-macros   t/nroff*/
199 	"ms",  ".\\\" ",/* ms-macros   t/nroff*/
200 	"p",   " * ",   /* Pascal      */
201 	"pas", " * ",
202 	"pl",  "% ",	/* Prolog      */
203 	"tex", "% ",	/* TeX	       */
204         "y",   " * ",   /* yacc        */
205 	nil,   "# "     /* default for unknown suffix; must always be last */
206 };
207 
208 #if has_mktemp
209 	static char const *
210 tmp()
211 /* Yield the name of the tmp directory.  */
212 {
213 	static char const *s;
214 	if (!s
215 		&&  !(s = cgetenv("TMPDIR"))	/* Unix tradition */
216 		&&  !(s = cgetenv("TMP"))	/* DOS tradition */
217 		&&  !(s = cgetenv("TEMP"))	/* another DOS tradition */
218 	)
219 		s = TMPDIR;
220 	return s;
221 }
222 #endif
223 
224 	char const *
225 maketemp(n)
226 	int n;
227 /* Create a unique filename using n and the process id and store it
228  * into the nth slot in tfnames.
229  * Because of storage in tfnames, tempunlink() can unlink the file later.
230  * Returns a pointer to the filename created.
231  */
232 {
233 	char *p;
234 	char const *t = tfnames[n];
235 
236 	if (t)
237 		return t;
238 
239 	catchints();
240 	{
241 #	if has_mktemp
242 	    char const *tp = tmp();
243 	    p = testalloc(strlen(tp) + 10);
244 	    VOID sprintf(p, "%s%cT%cXXXXXX", tp, SLASH, '0'+n);
245 	    if (!mktemp(p) || !*p)
246 		faterror("can't make temporary file name `%s%cT%cXXXXXX'",
247 			tp, SLASH, '0'+n
248 		);
249 #	else
250 	    static char tfnamebuf[TEMPNAMES][L_tmpnam];
251 	    p = tfnamebuf[n];
252 	    if (!tmpnam(p) || !*p)
253 #		ifdef P_tmpdir
254 		    faterror("can't make temporary file name `%s...'",P_tmpdir);
255 #		else
256 		    faterror("can't make temporary file name");
257 #		endif
258 #	endif
259 	}
260 
261 	tfnames[n] = p;
262 	return p;
263 }
264 
265 	void
266 tempunlink()
267 /* Clean up maketemp() files.  May be invoked by signal handler.
268  */
269 {
270 	register int i;
271 	register char *p;
272 
273 	for (i = TEMPNAMES;  0 <= --i;  )
274 	    if ((p = tfnames[i])) {
275 		VOID unlink(p);
276 		/*
277 		 * We would tfree(p) here,
278 		 * but this might dump core if we're handing a signal.
279 		 * We're about to exit anyway, so we won't bother.
280 		 */
281 		tfnames[i] = 0;
282 	    }
283 }
284 
285 
286 	static char const *
287 bindex(sp,ch)
288 	register char const *sp;
289 	int ch;
290 /* Function: Finds the last occurrence of character c in string sp
291  * and returns a pointer to the character just beyond it. If the
292  * character doesn't occur in the string, sp is returned.
293  */
294 {
295 	register char const c=ch, *r;
296         r = sp;
297         while (*sp) {
298                 if (*sp++ == c) r=sp;
299         }
300         return r;
301 }
302 
303 
304 
305 	static int
306 suffix_matches(suffix, pattern)
307 	register char const *suffix, *pattern;
308 {
309 	register int c;
310 	if (!pattern)
311 		return true;
312 	for (;;)
313 		switch (*suffix++ - (c = *pattern++)) {
314 		    case 0:
315 			if (!c)
316 				return true;
317 			break;
318 
319 		    case 'A'-'a':
320 			if (ctab[c] == Letter)
321 				break;
322 			/* fall into */
323 		    default:
324 			return false;
325 		}
326 }
327 
328 
329 	static void
330 InitAdmin()
331 /* function: initializes an admin node */
332 {
333 	register char const *Suffix;
334         register int i;
335 
336 	Head=nil; Dbranch=nil; AccessList=nil; Symbols=nil; Locks=nil;
337         StrictLocks=STRICT_LOCKING;
338 
339         /* guess the comment leader from the suffix*/
340         Suffix=bindex(workfilename, '.');
341         if (Suffix==workfilename) Suffix= ""; /* empty suffix; will get default*/
342 	for (i=0; !suffix_matches(Suffix,comtable[i].suffix); i++)
343 		;
344 	Comment.string = comtable[i].comlead;
345 	Comment.size = strlen(comtable[i].comlead);
346 	Lexinit(); /* note: if !finptr, reads nothing; only initializes */
347 }
348 
349 
350 #if defined(_POSIX_NO_TRUNC)
351 #	define LONG_NAMES_MAY_BE_SILENTLY_TRUNCATED 0
352 #else
353 #	define LONG_NAMES_MAY_BE_SILENTLY_TRUNCATED 1
354 #endif
355 
356 #if LONG_NAMES_MAY_BE_SILENTLY_TRUNCATED
357 #ifdef NAME_MAX
358 #	define filenametoolong(path) (NAME_MAX < strlen(basename(path)))
359 #else
360 	static int
361 filenametoolong(path)
362 	char *path;
363 /* Yield true if the last file name in PATH is too long. */
364 {
365 	static unsigned long dot_namemax;
366 
367 	register size_t namelen;
368 	register char *base;
369 	register unsigned long namemax;
370 
371 	base = path + dirlen(path);
372 	namelen = strlen(base);
373 	if (namelen <= _POSIX_NAME_MAX) /* fast check for shorties */
374 		return false;
375 	if (base != path) {
376 		*--base = 0;
377 		namemax = pathconf(path, _PC_NAME_MAX);
378 		*base = SLASH;
379 	} else {
380 		/* Cache the results for the working directory, for speed. */
381 		if (!dot_namemax)
382 			dot_namemax = pathconf(".", _PC_NAME_MAX);
383 		namemax = dot_namemax;
384 	}
385 	/* If pathconf() yielded -1, namemax is now ULONG_MAX.  */
386 	return namemax<namelen;
387 }
388 #endif
389 #endif
390 
391 	void
392 bufalloc(b, size)
393 	register struct buf *b;
394 	size_t size;
395 /* Ensure *B is a name buffer of at least SIZE bytes.
396  * *B's old contents can be freed; *B's new contents are undefined.
397  */
398 {
399 	if (b->size < size) {
400 		if (b->size)
401 			tfree(b->string);
402 		else
403 			b->size = sizeof(malloc_type);
404 		while (b->size < size)
405 			b->size <<= 1;
406 		b->string = tnalloc(char, b->size);
407 	}
408 }
409 
410 	void
411 bufrealloc(b, size)
412 	register struct buf *b;
413 	size_t size;
414 /* like bufalloc, except *B's old contents, if any, are preserved */
415 {
416 	if (b->size < size) {
417 		if (!b->size)
418 			bufalloc(b, size);
419 		else {
420 			while ((b->size <<= 1)  <  size)
421 				;
422 			b->string = trealloc(char, b->string, b->size);
423 		}
424 	}
425 }
426 
427 	void
428 bufautoend(b)
429 	struct buf *b;
430 /* Free an auto buffer at block exit. */
431 {
432 	if (b->size)
433 		tfree(b->string);
434 }
435 
436 	struct cbuf
437 bufremember(b, s)
438 	struct buf *b;
439 	size_t s;
440 /*
441  * Free the buffer B with used size S.
442  * Yield a cbuf with identical contents.
443  * The cbuf will be reclaimed when this input file is finished.
444  */
445 {
446 	struct cbuf cb;
447 
448 	if ((cb.size = s))
449 		cb.string = fremember(trealloc(char, b->string, s));
450 	else {
451 		bufautoend(b); /* not really auto */
452 		cb.string = "";
453 	}
454 	return cb;
455 }
456 
457 	char *
458 bufenlarge(b, alim)
459 	register struct buf *b;
460 	char const **alim;
461 /* Make *B larger.  Set *ALIM to its new limit, and yield the relocated value
462  * of its old limit.
463  */
464 {
465 	size_t s = b->size;
466 	bufrealloc(b, s + 1);
467 	*alim = b->string + b->size;
468 	return b->string + s;
469 }
470 
471 	void
472 bufscat(b, s)
473 	struct buf *b;
474 	char const *s;
475 /* Concatenate S to B's end. */
476 {
477 	size_t blen  =  b->string ? strlen(b->string) : 0;
478 	bufrealloc(b, blen+strlen(s)+1);
479 	VOID strcpy(b->string+blen, s);
480 }
481 
482 	void
483 bufscpy(b, s)
484 	struct buf *b;
485 	char const *s;
486 /* Copy S into B. */
487 {
488 	bufalloc(b, strlen(s)+1);
489 	VOID strcpy(b->string, s);
490 }
491 
492 
493 	char const *
494 basename(p)
495 	char const *p;
496 /* Yield the address of the base filename of the pathname P.  */
497 {
498 	register char const *b = p, *q = p;
499 	for (;;)
500 	    switch (*q++) {
501 		case SLASHes: b = q; break;
502 		case 0: return b;
503 	    }
504 }
505 
506 	size_t
507 dirlen(p)
508 	char const *p;
509 /* Yield the length of P's directory, including its trailing SLASH.  */
510 {
511 	return basename(p) - p;
512 }
513 
514 
515 	static size_t
516 suffixlen(x)
517 	char const *x;
518 /* Yield the length of X, an RCS filename suffix.  */
519 {
520 	register char const *p;
521 
522 	p = x;
523 	for (;;)
524 	    switch (*p) {
525 		case 0: case SLASHes:
526 		    return p - x;
527 
528 		default:
529 		    ++p;
530 		    continue;
531 	    }
532 }
533 
534 	char const *
535 rcssuffix(name)
536 	char const *name;
537 /* Yield the suffix of NAME if it is an RCS filename, 0 otherwise.  */
538 {
539 	char const *x, *p, *nz;
540 	size_t dl, nl, xl;
541 
542 	nl = strlen(name);
543 	nz = name + nl;
544 	x = suffixes;
545 	do {
546 	    if ((xl = suffixlen(x))) {
547 		if (xl <= nl  &&  memcmp(p = nz-xl, x, xl) == 0)
548 		    return p;
549 	    } else {
550 		dl = dirlen(name);
551 		if (
552 		    rcsdirlen < dl  &&
553 		    !memcmp(p = name+(dl-=rcsdirlen+1), rcsdir, rcsdirlen) &&
554 		    (!dl  ||  isSLASH(*--p))
555 		)
556 		    return nz;
557 	    }
558 	    x += xl;
559 	} while (*x++);
560 	return 0;
561 }
562 
563 	/*ARGSUSED*/ RILE *
564 rcsreadopen(RCSname, status, mustread)
565 	struct buf *RCSname;
566 	struct stat *status;
567 	int mustread;
568 /* Open RCSNAME for reading and yield its FILE* descriptor.
569  * If successful, set *STATUS to its status.
570  * Pass this routine to pairfilenames() for read-only access to the file.  */
571 {
572 	return Iopen(RCSname->string, FOPEN_R, status);
573 }
574 
575 	static int
576 finopen(rcsopen, mustread)
577 	/* jlf RILE *(*rcsopen)P((struct buf*,struct stat*,int)); */
578 	RILE *(*rcsopen)();
579 	int mustread;
580 /*
581  * Use RCSOPEN to open an RCS file; MUSTREAD is set if the file must be read.
582  * Set finptr to the result and yield true if successful.
583  * RCSb holds the file's name.
584  * Set RCSbuf to the best RCS name found so far, and RCSerrno to its errno.
585  * Yield true if successful or if an unusual failure.
586  */
587 {
588 	int interesting, preferold;
589 
590 	/*
591 	 * We prefer an old name to that of a nonexisting new RCS file,
592 	 * unless we tried locking the old name and failed.
593 	 */
594 	preferold  =  RCSbuf.string[0] && (mustread||frewrite);
595 
596 	finptr = (*rcsopen)(&RCSb, &RCSstat, mustread);
597 	interesting = finptr || errno!=ENOENT;
598 	if (interesting || !preferold) {
599 		/* Use the new name.  */
600 		RCSerrno = errno;
601 		bufscpy(&RCSbuf, RCSb.string);
602 	}
603 	return interesting;
604 }
605 
606 	static int
607 fin2open(d, dlen, base, baselen, x, xlen, rcsopen, mustread)
608 	char const *d, *base, *x;
609 	size_t dlen, baselen, xlen;
610 	/* jlf RILE *(*rcsopen)P((struct buf*,struct stat*,int)); */
611 	RILE *(*rcsopen)();
612 	int mustread;
613 /*
614  * D is a directory name with length DLEN (including trailing slash).
615  * BASE is a filename with length BASELEN.
616  * X is an RCS filename suffix with length XLEN.
617  * Use RCSOPEN to open an RCS file; MUSTREAD is set if the file must be read.
618  * Yield true if successful.
619  * Try dRCS/basex first; if that fails and x is nonempty, try dbasex.
620  * Put these potential names in RCSb.
621  * Set RCSbuf to the best RCS name found so far, and RCSerrno to its errno.
622  * Yield true if successful or if an unusual failure.
623  */
624 {
625 	register char *p;
626 
627 	bufalloc(&RCSb, dlen + rcsdirlen + 1 + baselen + xlen + 1);
628 
629 	/* Try dRCS/basex.  */
630 	VOID memcpy(p = RCSb.string, d, dlen);
631 	VOID memcpy(p += dlen, rcsdir, rcsdirlen);
632 	p += rcsdirlen;
633 	*p++ = SLASH;
634 	VOID memcpy(p, base, baselen);
635 	VOID memcpy(p += baselen, x, xlen);
636 	p[xlen] = 0;
637 	if (xlen) {
638 	    if (finopen(rcsopen, mustread))
639 		return true;
640 
641 	    /* Try dbasex.  */
642 	    /* Start from scratch, because finopen() may have changed RCSb.  */
643 	    VOID memcpy(p = RCSb.string, d, dlen);
644 	    VOID memcpy(p += dlen, base, baselen);
645 	    VOID memcpy(p += baselen, x, xlen);
646 	    p[xlen] = 0;
647 	}
648 	return finopen(rcsopen, mustread);
649 }
650 
651 	int
652 pairfilenames(argc, argv, rcsopen, mustread, quiet)
653 	int argc;
654 	char **argv;
655 	/* jlf RILE *(*rcsopen)P((struct buf*,struct stat*,int)); */
656 	RILE *(*rcsopen)();
657 	int mustread, quiet;
658 /* Function: Pairs the filenames pointed to by argv; argc indicates
659  * how many there are.
660  * Places a pointer to the RCS filename into RCSfilename,
661  * and a pointer to the name of the working file into workfilename.
662  * If both the workfilename and the RCS filename are given, and workstdout
663  * is set, a warning is printed.
664  *
665  * If the RCS file exists, places its status into RCSstat.
666  *
667  * If the RCS file exists, it is RCSOPENed for reading, the file pointer
668  * is placed into finptr, and the admin-node is read in; returns 1.
669  * If the RCS file does not exist and MUSTREAD,
670  * print an error unless QUIET and return 0.
671  * Otherwise, initialize the admin node and return -1.
672  *
673  * 0 is returned on all errors, e.g. files that are not regular files.
674  */
675 {
676 	static struct buf tempbuf;
677 
678 	register char *p, *arg, *RCS1;
679 	char const *purefname, *pureRCSname, *x;
680 	int paired;
681 	size_t arglen, dlen, baselen, xlen;
682 
683 	if (!(arg = *argv)) return 0; /* already paired filename */
684 	if (*arg == '-') {
685 		error("%s option is ignored after file names", arg);
686 		return 0;
687 	}
688 
689 	purefname = basename(arg);
690 
691 	/* Allocate buffer temporary to hold the default paired file name. */
692 	p = arg;
693 	for (;;) {
694 		switch (*p++) {
695 		    /* Beware characters that cause havoc with ci -k. */
696 		    case KDELIM:
697 			error("RCS file name `%s' contains %c", arg, KDELIM);
698 			return 0;
699 		    case ' ': case '\n': case '\t':
700 			error("RCS file name `%s' contains white space", arg);
701 			return 0;
702 		    default:
703 			continue;
704 		    case 0:
705 			break;
706 		}
707 		break;
708 	}
709 
710 	paired = false;
711 
712         /* first check suffix to see whether it is an RCS file or not */
713 	if ((x = rcssuffix(arg)))
714 	{
715                 /* RCS file name given*/
716 		RCS1 = arg;
717 		pureRCSname = purefname;
718 		baselen = x - purefname;
719 		if (
720 		    1 < argc  &&
721 		    !rcssuffix(workfilename = p = argv[1])  &&
722 		    baselen <= (arglen = strlen(p))  &&
723 		    ((p+=arglen-baselen) == workfilename  ||  isSLASH(p[-1])) &&
724 		    memcmp(purefname, p, baselen) == 0
725 		) {
726 			argv[1] = 0;
727 			paired = true;
728 		} else {
729 			bufscpy(&tempbuf, purefname);
730 			workfilename = p = tempbuf.string;
731 			p[baselen] = 0;
732 		}
733         } else {
734                 /* working file given; now try to find RCS file */
735 		workfilename = arg;
736 		baselen = p - purefname - 1;
737                 /* derive RCS file name*/
738 		if (
739 		    1 < argc  &&
740 		    (x = rcssuffix(RCS1 = argv[1]))  &&
741 		    baselen  <=  x - RCS1  &&
742 		    ((pureRCSname=x-baselen)==RCS1 || isSLASH(pureRCSname[-1])) &&
743 		    memcmp(purefname, pureRCSname, baselen) == 0
744 		) {
745 			argv[1] = 0;
746 			paired = true;
747 		} else
748 			pureRCSname = RCS1 = 0;
749         }
750         /* now we have a (tentative) RCS filename in RCS1 and workfilename  */
751         /* Second, try to find the right RCS file */
752         if (pureRCSname!=RCS1) {
753                 /* a path for RCSfile is given; single RCS file to look for */
754 		bufscpy(&RCSbuf, RCS1);
755 		finptr = (*rcsopen)(&RCSbuf, &RCSstat, mustread);
756 		RCSerrno = errno;
757         } else {
758 		bufscpy(&RCSbuf, "");
759 		if (RCS1)
760 			/* RCS file name was given without path.  */
761 			VOID fin2open(arg, (size_t)0, pureRCSname, baselen,
762 				x, strlen(x), rcsopen, mustread
763 			);
764 		else {
765 			/* No RCS file name was given.  */
766 			/* Try each suffix in turn.  */
767 			dlen = purefname-arg;
768 			x = suffixes;
769 			while (! fin2open(arg, dlen, purefname, baselen,
770 					x, xlen=suffixlen(x), rcsopen, mustread
771 			)) {
772 				x += xlen;
773 				if (!*x++)
774 					break;
775 			}
776 		}
777         }
778 	RCSfilename = p = RCSbuf.string;
779 	if (finptr) {
780 		if (!S_ISREG(RCSstat.st_mode)) {
781 			error("%s isn't a regular file -- ignored", p);
782                         return 0;
783                 }
784                 Lexinit(); getadmin();
785 	} else {
786 		if (RCSerrno!=ENOENT || mustread || !frewrite) {
787 			if (RCSerrno == EEXIST)
788 				error("RCS file %s is in use", p);
789 			else if (!quiet || RCSerrno!=ENOENT)
790 				enerror(RCSerrno, p);
791 			return 0;
792 		}
793                 InitAdmin();
794         };
795 #	if LONG_NAMES_MAY_BE_SILENTLY_TRUNCATED
796 	    if (filenametoolong(p)) {
797 		error("RCS file name %s is too long", p);
798 		return 0;
799 	    }
800 #	    ifndef NAME_MAX
801 		/*
802 		 * Check workfilename too, even though it cannot be longer,
803 		 * because it may reside on a different filesystem.
804 		 */
805 		if (filenametoolong(workfilename)) {
806 		    error("working file name %s is too long", workfilename);
807 		    return 0;
808 		}
809 #	    endif
810 #	endif
811 
812 	if (paired && workstdout)
813                 warn("Option -p is set; ignoring output file %s",workfilename);
814 
815 	prevkeys = false;
816 	return finptr ? 1 : -1;
817 }
818 
819 
820 	char const *
821 getfullRCSname()
822 /* Function: returns a pointer to the full path name of the RCS file.
823  * Gets the working directory's name at most once.
824  * Removes leading "../" and "./".
825  */
826 {
827 	static char const *wdptr;
828 	static struct buf rcsbuf, wdbuf;
829 	static size_t pathlength;
830 
831 	register char const *realname;
832 	register size_t parentdirlength;
833 	register unsigned dotdotcounter;
834 	register char *d;
835 	register char const *wd;
836 
837 	if (ROOTPATH(RCSfilename)) {
838                 return(RCSfilename);
839         } else {
840 		if (!(wd = wdptr)) {
841 		    /* Get working directory for the first time.  */
842 		    if (!(d = cgetenv("PWD"))) {
843 			bufalloc(&wdbuf, SIZEABLE_PATH + 1);
844 #			if !has_getcwd && has_getwd
845 			    d = getwd(wdbuf.string);
846 #			else
847 			    while (
848 				    !(d = getcwd(wdbuf.string, wdbuf.size))
849 				&&  errno==ERANGE
850 			    )
851 				bufalloc(&wdbuf, wdbuf.size<<1);
852 #			endif
853 			if (!d)
854 			    efaterror("working directory");
855 		    }
856 		    parentdirlength = strlen(d);
857 		    while (parentdirlength && isSLASH(d[parentdirlength-1])) {
858 			d[--parentdirlength] = 0;
859                         /* Check needed because some getwd implementations */
860                         /* generate "/" for the root.                      */
861                     }
862 		    wdptr = wd = d;
863 		    pathlength = parentdirlength;
864                 }
865                 /*the following must be redone since RCSfilename may change*/
866 		/* Find how many `../'s to remove from RCSfilename.  */
867                 dotdotcounter =0;
868                 realname = RCSfilename;
869 		while (realname[0]=='.') {
870 			if (isSLASH(realname[1])) {
871                             /* drop leading ./ */
872                             realname += 2;
873 			} else if (realname[1]=='.' && isSLASH(realname[2])) {
874                             /* drop leading ../ and remember */
875                             dotdotcounter++;
876                             realname += 3;
877 			} else
878 			    break;
879                 }
880 		/* Now remove dotdotcounter trailing directories from wd. */
881 		parentdirlength = pathlength;
882 		while (dotdotcounter && parentdirlength) {
883                     /* move pointer backwards over trailing directory */
884 		    if (isSLASH(wd[--parentdirlength])) {
885                         dotdotcounter--;
886                     }
887                 }
888 		/* build full path name */
889 		bufalloc(&rcsbuf, parentdirlength+strlen(realname)+2);
890 		d = rcsbuf.string;
891 		VOID memcpy(d, wd, parentdirlength);
892 		d += parentdirlength;
893 		*d++ = SLASH;
894 		VOID strcpy(d, realname);
895 		return rcsbuf.string;
896         }
897 }
898 
899 #ifndef isSLASH
900 	int
901 isSLASH(c)
902 	int c;
903 {
904 	switch (c) {
905 	    case SLASHes:
906 		return true;
907 	    default:
908 		return false;
909 	}
910 }
911 #endif
912 
913 
914 #if !has_getcwd && !has_getwd
915 
916 	char *
917 getcwd(path, size)
918 	char *path;
919 	size_t size;
920 {
921 	static char const usrbinpwd[] = "/usr/bin/pwd";
922 #	define binpwd (usrbinpwd+4)
923 
924 	register FILE *fp;
925 	register int c;
926 	register char *p, *lim;
927 	int closeerrno, closeerror, e, fd[2], readerror, toolong, wstatus;
928 	pid_t child;
929 #	if !has_waitpid
930 		pid_t w;
931 #	endif
932 
933 	if (!size) {
934 		errno = EINVAL;
935 		return 0;
936 	}
937 	if (pipe(fd) != 0)
938 		return 0;
939 	if (!(child = vfork())) {
940 		if (
941 			close(fd[0]) == 0 &&
942 			(fd[1] == STDOUT_FILENO ||
943 #				ifdef F_DUPFD
944 					(VOID close(STDOUT_FILENO),
945 					fcntl(fd[1], F_DUPFD, STDOUT_FILENO))
946 #				else
947 					dup2(fd[1], STDOUT_FILENO)
948 #				endif
949 				== STDOUT_FILENO &&
950 				close(fd[1]) == 0
951 			)
952 		) {
953 			VOID close(STDERR_FILENO);
954 			VOID execl(binpwd, binpwd, (char *)0);
955 			VOID execl(usrbinpwd, usrbinpwd, (char *)0);
956 		}
957 		_exit(EXIT_FAILURE);
958 	}
959 	e = errno;
960 	closeerror = close(fd[1]);
961 	closeerrno = errno;
962 	fp = 0;
963 	readerror = toolong = wstatus = 0;
964 	p = path;
965 	if (0 <= child) {
966 		fp = fdopen(fd[0], "r");
967 		e = errno;
968 		if (fp) {
969 			lim = p + size;
970 			for (p = path;  ;  *p++ = c) {
971 				if ((c=getc(fp)) < 0) {
972 					if (feof(fp))
973 						break;
974 					if (ferror(fp)) {
975 						readerror = 1;
976 						e = errno;
977 						break;
978 					}
979 				}
980 				if (p == lim) {
981 					toolong = 1;
982 					break;
983 				}
984 			}
985 		}
986 #		if has_waitpid
987 			if (waitpid(child, &wstatus, 0) < 0)
988 				wstatus = 1;
989 #		else
990 			do {
991 				if ((w = wait(&wstatus)) < 0) {
992 					wstatus = 1;
993 					break;
994 				}
995 			} while (w != child);
996 #		endif
997 	}
998 	if (!fp) {
999 		VOID close(fd[0]);
1000 		errno = e;
1001 		return 0;
1002 	}
1003 	if (fclose(fp) != 0)
1004 		return 0;
1005 	if (readerror) {
1006 		errno = e;
1007 		return 0;
1008 	}
1009 	if (closeerror) {
1010 		errno = closeerrno;
1011 		return 0;
1012 	}
1013 	if (toolong) {
1014 		errno = ERANGE;
1015 		return 0;
1016 	}
1017 	if (wstatus  ||  p == path  ||  *--p != '\n') {
1018 		errno = EACCES;
1019 		return 0;
1020 	}
1021 	*p = '\0';
1022 	return path;
1023 }
1024 #endif
1025 
1026 
1027 #ifdef PAIRTEST
1028 /* test program for pairfilenames() and getfullRCSname() */
1029 
1030 char const cmdid[] = "pair";
1031 
1032 main(argc, argv)
1033 int argc; char *argv[];
1034 {
1035         int result;
1036 	int initflag;
1037 	quietflag = initflag = false;
1038 
1039         while(--argc, ++argv, argc>=1 && ((*argv)[0] == '-')) {
1040                 switch ((*argv)[1]) {
1041 
1042 		case 'p':       workstdout = stdout;
1043                                 break;
1044                 case 'i':       initflag=true;
1045                                 break;
1046                 case 'q':       quietflag=true;
1047                                 break;
1048                 default:        error("unknown option: %s", *argv);
1049                                 break;
1050                 }
1051         }
1052 
1053         do {
1054                 RCSfilename=workfilename=nil;
1055 		result = pairfilenames(argc,argv,rcsreadopen,!initflag,quietflag);
1056                 if (result!=0) {
1057 		    diagnose("RCS file: %s; working file: %s\nFull RCS file name: %s\n",
1058 			     RCSfilename,workfilename,getfullRCSname()
1059 		    );
1060                 }
1061                 switch (result) {
1062                         case 0: continue; /* already paired file */
1063 
1064                         case 1: if (initflag) {
1065                                     error("RCS file %s exists already",RCSfilename);
1066                                 } else {
1067 				    diagnose("RCS file %s exists\n",RCSfilename);
1068                                 }
1069 				Ifclose(finptr);
1070                                 break;
1071 
1072 			case -1:diagnose("RCS file doesn't exist\n");
1073                                 break;
1074                 }
1075 
1076         } while (++argv, --argc>=1);
1077 
1078 }
1079 
1080 	exiting void
1081 exiterr()
1082 {
1083 	dirtempunlink();
1084 	tempunlink();
1085 	_exit(EXIT_FAILURE);
1086 }
1087 #endif
1088