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