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