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