rcsfnms.c revision 22996
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, "$Id$")
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
288	if (t)
289		return t;
290
291	catchints();
292	{
293#	if has_mktemp
294	    char const *tp = tmp();
295	    size_t tplen = dir_useful_len(tp);
296	    p = testalloc(tplen + 10);
297	    VOID sprintf(p, "%.*s%cT%cXXXXXX", (int)tplen, tp, SLASH, '0'+n);
298	    if (!mktemp(p) || !*p)
299		faterror("can't make temporary pathname `%.*s%cT%cXXXXXX'",
300			(int)tplen, tp, SLASH, '0'+n
301		);
302#	else
303	    static char tpnamebuf[TEMPNAMES][L_tmpnam];
304	    p = tpnamebuf[n];
305	    if (!tmpnam(p) || !*p)
306#		ifdef P_tmpdir
307		    faterror("can't make temporary pathname `%s...'",P_tmpdir);
308#		else
309		    faterror("can't make temporary pathname");
310#		endif
311#	endif
312	}
313
314	tpnames[n] = p;
315	return p;
316}
317
318	void
319tempunlink()
320/* Clean up maketemp() files.  May be invoked by signal handler.
321 */
322{
323	register int i;
324	register char *p;
325
326	for (i = TEMPNAMES;  0 <= --i;  )
327	    if ((p = tpnames[i])) {
328		VOID unlink(p);
329		/*
330		 * We would tfree(p) here,
331		 * but this might dump core if we're handing a signal.
332		 * We're about to exit anyway, so we won't bother.
333		 */
334		tpnames[i] = 0;
335	    }
336}
337
338
339	static char const *
340bindex(sp, c)
341	register char const *sp;
342	register int c;
343/* Function: Finds the last occurrence of character c in string sp
344 * and returns a pointer to the character just beyond it. If the
345 * character doesn't occur in the string, sp is returned.
346 */
347{
348	register char const *r;
349        r = sp;
350        while (*sp) {
351                if (*sp++ == c) r=sp;
352        }
353        return r;
354}
355
356
357
358	static int
359suffix_matches(suffix, pattern)
360	register char const *suffix, *pattern;
361{
362	register int c;
363	if (!pattern)
364		return true;
365	for (;;)
366		switch (*suffix++ - (c = *pattern++)) {
367		    case 0:
368			if (!c)
369				return true;
370			break;
371
372		    case 'A'-'a':
373			if (ctab[c] == Letter)
374				break;
375			/* fall into */
376		    default:
377			return false;
378		}
379}
380
381
382	static void
383InitAdmin()
384/* function: initializes an admin node */
385{
386	register char const *Suffix;
387        register int i;
388
389	Head=0; Dbranch=0; AccessList=0; Symbols=0; Locks=0;
390        StrictLocks=STRICT_LOCKING;
391
392        /* guess the comment leader from the suffix*/
393	Suffix = bindex(workname, '.');
394	if (Suffix==workname) Suffix= ""; /* empty suffix; will get default*/
395	for (i=0; !suffix_matches(Suffix,comtable[i].suffix); i++)
396		continue;
397	Comment.string = comtable[i].comlead;
398	Comment.size = strlen(comtable[i].comlead);
399	Expand = KEYVAL_EXPAND;
400	clear_buf(&Ignored);
401	Lexinit(); /* note: if !finptr, reads nothing; only initializes */
402}
403
404
405
406	void
407bufalloc(b, size)
408	register struct buf *b;
409	size_t size;
410/* Ensure *B is a name buffer of at least SIZE bytes.
411 * *B's old contents can be freed; *B's new contents are undefined.
412 */
413{
414	if (b->size < size) {
415		if (b->size)
416			tfree(b->string);
417		else
418			b->size = sizeof(malloc_type);
419		while (b->size < size)
420			b->size <<= 1;
421		b->string = tnalloc(char, b->size);
422	}
423}
424
425	void
426bufrealloc(b, size)
427	register struct buf *b;
428	size_t size;
429/* like bufalloc, except *B's old contents, if any, are preserved */
430{
431	if (b->size < size) {
432		if (!b->size)
433			bufalloc(b, size);
434		else {
435			while ((b->size <<= 1)  <  size)
436				continue;
437			b->string = trealloc(char, b->string, b->size);
438		}
439	}
440}
441
442	void
443bufautoend(b)
444	struct buf *b;
445/* Free an auto buffer at block exit. */
446{
447	if (b->size)
448		tfree(b->string);
449}
450
451	struct cbuf
452bufremember(b, s)
453	struct buf *b;
454	size_t s;
455/*
456 * Free the buffer B with used size S.
457 * Yield a cbuf with identical contents.
458 * The cbuf will be reclaimed when this input file is finished.
459 */
460{
461	struct cbuf cb;
462
463	if ((cb.size = s))
464		cb.string = fremember(trealloc(char, b->string, s));
465	else {
466		bufautoend(b); /* not really auto */
467		cb.string = "";
468	}
469	return cb;
470}
471
472	char *
473bufenlarge(b, alim)
474	register struct buf *b;
475	char const **alim;
476/* Make *B larger.  Set *ALIM to its new limit, and yield the relocated value
477 * of its old limit.
478 */
479{
480	size_t s = b->size;
481	bufrealloc(b, s + 1);
482	*alim = b->string + b->size;
483	return b->string + s;
484}
485
486	void
487bufscat(b, s)
488	struct buf *b;
489	char const *s;
490/* Concatenate S to B's end. */
491{
492	size_t blen  =  b->string ? strlen(b->string) : 0;
493	bufrealloc(b, blen+strlen(s)+1);
494	VOID strcpy(b->string+blen, s);
495}
496
497	void
498bufscpy(b, s)
499	struct buf *b;
500	char const *s;
501/* Copy S into B. */
502{
503	bufalloc(b, strlen(s)+1);
504	VOID strcpy(b->string, s);
505}
506
507
508	char const *
509basefilename(p)
510	char const *p;
511/* Yield the address of the base filename of the pathname P.  */
512{
513	register char const *b = p, *q = p;
514	for (;;)
515	    switch (*q++) {
516		case SLASHes: b = q; break;
517		case 0: return b;
518	    }
519}
520
521
522	static size_t
523suffixlen(x)
524	char const *x;
525/* Yield the length of X, an RCS pathname suffix.  */
526{
527	register char const *p;
528
529	p = x;
530	for (;;)
531	    switch (*p) {
532		case 0: case SLASHes:
533		    return p - x;
534
535		default:
536		    ++p;
537		    continue;
538	    }
539}
540
541	char const *
542rcssuffix(name)
543	char const *name;
544/* Yield the suffix of NAME if it is an RCS pathname, 0 otherwise.  */
545{
546	char const *x, *p, *nz;
547	size_t nl, xl;
548
549	nl = strlen(name);
550	nz = name + nl;
551	x = suffixes;
552	do {
553	    if ((xl = suffixlen(x))) {
554		if (xl <= nl  &&  memcmp(p = nz-xl, x, xl) == 0)
555		    return p;
556	    } else
557		for (p = name;  p < nz - rcslen;  p++)
558		    if (
559			isSLASH(p[rcslen])
560			&& (p==name || isSLASH(p[-1]))
561			&& memcmp(p, rcsdir, rcslen) == 0
562		    )
563			return nz;
564	    x += xl;
565	} while (*x++);
566	return 0;
567}
568
569	/*ARGSUSED*/ RILE *
570rcsreadopen(RCSpath, status, mustread)
571	struct buf *RCSpath;
572	struct stat *status;
573	int mustread;
574/* Open RCSPATH for reading and yield its FILE* descriptor.
575 * If successful, set *STATUS to its status.
576 * Pass this routine to pairnames() for read-only access to the file.  */
577{
578	return Iopen(RCSpath->string, FOPEN_RB, status);
579}
580
581	static int
582finopen(rcsopen, mustread)
583	RILE *(*rcsopen)P((struct buf*,struct stat*,int));
584	int mustread;
585/*
586 * Use RCSOPEN to open an RCS file; MUSTREAD is set if the file must be read.
587 * Set finptr to the result and yield true if successful.
588 * RCSb holds the file's name.
589 * Set RCSbuf to the best RCS name found so far, and RCSerrno to its errno.
590 * Yield true if successful or if an unusual failure.
591 */
592{
593	int interesting, preferold;
594
595	/*
596	 * We prefer an old name to that of a nonexisting new RCS file,
597	 * unless we tried locking the old name and failed.
598	 */
599	preferold  =  RCSbuf.string[0] && (mustread||0<=fdlock);
600
601	finptr = (*rcsopen)(&RCSb, &RCSstat, mustread);
602	interesting = finptr || errno!=ENOENT;
603	if (interesting || !preferold) {
604		/* Use the new name.  */
605		RCSerrno = errno;
606		bufscpy(&RCSbuf, RCSb.string);
607	}
608	return interesting;
609}
610
611	static int
612fin2open(d, dlen, base, baselen, x, xlen, rcsopen, mustread)
613	char const *d, *base, *x;
614	size_t dlen, baselen, xlen;
615	RILE *(*rcsopen)P((struct buf*,struct stat*,int));
616	int mustread;
617/*
618 * D is a directory name with length DLEN (including trailing slash).
619 * BASE is a filename with length BASELEN.
620 * X is an RCS pathname suffix with length XLEN.
621 * Use RCSOPEN to open an RCS file; MUSTREAD is set if the file must be read.
622 * Yield true if successful.
623 * Try dRCS/basex first; if that fails and x is nonempty, try dbasex.
624 * Put these potential names in RCSb.
625 * Set RCSbuf to the best RCS name found so far, and RCSerrno to its errno.
626 * Yield true if successful or if an unusual failure.
627 */
628{
629	register char *p;
630
631	bufalloc(&RCSb, dlen + rcslen + 1 + baselen + xlen + 1);
632
633	/* Try dRCS/basex.  */
634	VOID memcpy(p = RCSb.string, d, dlen);
635	VOID memcpy(p += dlen, rcsdir, rcslen);
636	p += rcslen;
637	*p++ = SLASH;
638	VOID memcpy(p, base, baselen);
639	VOID memcpy(p += baselen, x, xlen);
640	p[xlen] = 0;
641	if (xlen) {
642	    if (finopen(rcsopen, mustread))
643		return true;
644
645	    /* Try dbasex.  */
646	    /* Start from scratch, because finopen() may have changed RCSb.  */
647	    VOID memcpy(p = RCSb.string, d, dlen);
648	    VOID memcpy(p += dlen, base, baselen);
649	    VOID memcpy(p += baselen, x, xlen);
650	    p[xlen] = 0;
651	}
652	return finopen(rcsopen, mustread);
653}
654
655	int
656pairnames(argc, argv, rcsopen, mustread, quiet)
657	int argc;
658	char **argv;
659	RILE *(*rcsopen)P((struct buf*,struct stat*,int));
660	int mustread, quiet;
661/*
662 * Pair the pathnames pointed to by argv; argc indicates
663 * how many there are.
664 * Place a pointer to the RCS pathname into RCSname,
665 * and a pointer to the pathname of the working file into workname.
666 * If both are given, and workstdout
667 * is set, a warning is printed.
668 *
669 * If the RCS file exists, places its status into RCSstat.
670 *
671 * If the RCS file exists, it is RCSOPENed for reading, the file pointer
672 * is placed into finptr, and the admin-node is read in; returns 1.
673 * If the RCS file does not exist and MUSTREAD,
674 * print an error unless QUIET and return 0.
675 * Otherwise, initialize the admin node and return -1.
676 *
677 * 0 is returned on all errors, e.g. files that are not regular files.
678 */
679{
680	static struct buf tempbuf;
681
682	register char *p, *arg, *RCS1;
683	char const *base, *RCSbase, *x;
684	int paired;
685	size_t arglen, dlen, baselen, xlen;
686
687	fdlock = -1;
688
689	if (!(arg = *argv)) return 0; /* already paired pathname */
690	if (*arg == '-') {
691		error("%s option is ignored after pathnames", arg);
692		return 0;
693	}
694
695	base = basefilename(arg);
696	paired = false;
697
698        /* first check suffix to see whether it is an RCS file or not */
699	if ((x = rcssuffix(arg)))
700	{
701		/* RCS pathname given */
702		RCS1 = arg;
703		RCSbase = base;
704		baselen = x - base;
705		if (
706		    1 < argc  &&
707		    !rcssuffix(workname = p = argv[1])  &&
708		    baselen <= (arglen = strlen(p))  &&
709		    ((p+=arglen-baselen) == workname  ||  isSLASH(p[-1])) &&
710		    memcmp(base, p, baselen) == 0
711		) {
712			argv[1] = 0;
713			paired = true;
714		} else {
715			bufscpy(&tempbuf, base);
716			workname = p = tempbuf.string;
717			p[baselen] = 0;
718		}
719        } else {
720                /* working file given; now try to find RCS file */
721		workname = arg;
722		baselen = strlen(base);
723		/* Derive RCS pathname.  */
724		if (
725		    1 < argc  &&
726		    (x = rcssuffix(RCS1 = argv[1]))  &&
727		    baselen  <=  x - RCS1  &&
728		    ((RCSbase=x-baselen)==RCS1 || isSLASH(RCSbase[-1])) &&
729		    memcmp(base, RCSbase, baselen) == 0
730		) {
731			argv[1] = 0;
732			paired = true;
733		} else
734			RCSbase = RCS1 = 0;
735        }
736	/* Now we have a (tentative) RCS pathname in RCS1 and workname.  */
737        /* Second, try to find the right RCS file */
738	if (RCSbase!=RCS1) {
739                /* a path for RCSfile is given; single RCS file to look for */
740		bufscpy(&RCSbuf, RCS1);
741		finptr = (*rcsopen)(&RCSbuf, &RCSstat, mustread);
742		RCSerrno = errno;
743        } else {
744		bufscpy(&RCSbuf, "");
745		if (RCS1)
746			/* RCS filename was given without path.  */
747			VOID fin2open(arg, (size_t)0, RCSbase, baselen,
748				x, strlen(x), rcsopen, mustread
749			);
750		else {
751			/* No RCS pathname was given.  */
752			/* Try each suffix in turn.  */
753			dlen = base-arg;
754			x = suffixes;
755			while (! fin2open(arg, dlen, base, baselen,
756					x, xlen=suffixlen(x), rcsopen, mustread
757			)) {
758				x += xlen;
759				if (!*x++)
760					break;
761			}
762		}
763        }
764	RCSname = p = RCSbuf.string;
765	if (finptr) {
766		if (!S_ISREG(RCSstat.st_mode)) {
767			error("%s isn't a regular file -- ignored", p);
768                        return 0;
769                }
770                Lexinit(); getadmin();
771	} else {
772		if (RCSerrno!=ENOENT || mustread || fdlock<0) {
773			if (RCSerrno == EEXIST)
774				error("RCS file %s is in use", p);
775			else if (!quiet || RCSerrno!=ENOENT)
776				enerror(RCSerrno, p);
777			return 0;
778		}
779                InitAdmin();
780        };
781
782	if (paired && workstdout)
783		workwarn("Working file ignored due to -p option");
784
785	prevkeys = false;
786	return finptr ? 1 : -1;
787}
788
789
790	char const *
791getfullRCSname()
792/*
793 * Return a pointer to the full pathname of the RCS file.
794 * Remove leading `./'.
795 */
796{
797	if (ROOTPATH(RCSname)) {
798	    return RCSname;
799	} else {
800	    static struct buf rcsbuf;
801#	    if needs_getabsname
802		bufalloc(&rcsbuf, SIZEABLE_PATH + 1);
803		while (getabsname(RCSname, rcsbuf.string, rcsbuf.size) != 0)
804		    if (errno == ERANGE)
805			bufalloc(&rcsbuf, rcsbuf.size<<1);
806		    else
807			efaterror("getabsname");
808#	    else
809		static char const *wdptr;
810		static struct buf wdbuf;
811		static size_t wdlen;
812
813		register char const *r;
814		register size_t dlen;
815		register char *d;
816		register char const *wd;
817
818		if (!(wd = wdptr)) {
819		    /* Get working directory for the first time.  */
820		    char *PWD = cgetenv("PWD");
821		    struct stat PWDstat, dotstat;
822		    if (! (
823			(d = PWD) &&
824			ROOTPATH(PWD) &&
825			stat(PWD, &PWDstat) == 0 &&
826			stat(".", &dotstat) == 0 &&
827			same_file(PWDstat, dotstat, 1)
828		    )) {
829			bufalloc(&wdbuf, SIZEABLE_PATH + 1);
830#			if has_getcwd || !has_getwd
831			    while (!(d = getcwd(wdbuf.string, wdbuf.size)))
832				if (errno == ERANGE)
833				    bufalloc(&wdbuf, wdbuf.size<<1);
834				else if ((d = PWD))
835				    break;
836				else
837				    efaterror("getcwd");
838#			else
839			    d = getwd(wdbuf.string);
840			    if (!d  &&  !(d = PWD))
841				efaterror("getwd");
842#			endif
843		    }
844		    wdlen = dir_useful_len(d);
845		    d[wdlen] = 0;
846		    wdptr = wd = d;
847                }
848		/*
849		* Remove leading `./'s from RCSname.
850		* Do not try to handle `../', since removing it may yield
851		* the wrong answer in the presence of symbolic links.
852		*/
853		for (r = RCSname;  r[0]=='.' && isSLASH(r[1]);  r += 2)
854		    /* `.////' is equivalent to `./'.  */
855		    while (isSLASH(r[2]))
856			r++;
857		/* Build full pathname.  */
858		dlen = wdlen;
859		bufalloc(&rcsbuf, dlen + strlen(r) + 2);
860		d = rcsbuf.string;
861		VOID memcpy(d, wd, dlen);
862		d += dlen;
863		*d++ = SLASH;
864		VOID strcpy(d, r);
865#	    endif
866	    return rcsbuf.string;
867        }
868}
869
870	static size_t
871dir_useful_len(d)
872	char const *d;
873/*
874* D names a directory; yield the number of characters of D's useful part.
875* To create a file in D, append a SLASH and a file name to D's useful part.
876* Ignore trailing slashes if possible; not only are they ugly,
877* but some non-Posix systems misbehave unless the slashes are omitted.
878*/
879{
880#	ifndef SLASHSLASH_is_SLASH
881#	define SLASHSLASH_is_SLASH 0
882#	endif
883	size_t dlen = strlen(d);
884	if (!SLASHSLASH_is_SLASH && dlen==2 && isSLASH(d[0]) && isSLASH(d[1]))
885	    --dlen;
886	else
887	    while (dlen && isSLASH(d[dlen-1]))
888		--dlen;
889	return dlen;
890}
891
892#ifndef isSLASH
893	int
894isSLASH(c)
895	int c;
896{
897	switch (c) {
898	    case SLASHes:
899		return true;
900	    default:
901		return false;
902	}
903}
904#endif
905
906
907#if !has_getcwd && !has_getwd
908
909	char *
910getcwd(path, size)
911	char *path;
912	size_t size;
913{
914	static char const usrbinpwd[] = "/usr/bin/pwd";
915#	define binpwd (usrbinpwd+4)
916
917	register FILE *fp;
918	register int c;
919	register char *p, *lim;
920	int closeerrno, closeerror, e, fd[2], readerror, toolong, wstatus;
921	pid_t child;
922
923	if (!size) {
924		errno = EINVAL;
925		return 0;
926	}
927	if (pipe(fd) != 0)
928		return 0;
929#	if bad_wait_if_SIGCHLD_ignored
930#		ifndef SIGCHLD
931#		define SIGCHLD SIGCLD
932#		endif
933		VOID signal(SIGCHLD, SIG_DFL);
934#	endif
935	if (!(child = vfork())) {
936		if (
937			close(fd[0]) == 0 &&
938			(fd[1] == STDOUT_FILENO ||
939#				ifdef F_DUPFD
940					(VOID close(STDOUT_FILENO),
941					fcntl(fd[1], F_DUPFD, STDOUT_FILENO))
942#				else
943					dup2(fd[1], STDOUT_FILENO)
944#				endif
945				== STDOUT_FILENO &&
946				close(fd[1]) == 0
947			)
948		) {
949			VOID close(STDERR_FILENO);
950			VOID execl(binpwd, binpwd, (char *)0);
951			VOID execl(usrbinpwd, usrbinpwd, (char *)0);
952		}
953		_exit(EXIT_FAILURE);
954	}
955	e = errno;
956	closeerror = close(fd[1]);
957	closeerrno = errno;
958	fp = 0;
959	readerror = toolong = wstatus = 0;
960	p = path;
961	if (0 <= child) {
962		fp = fdopen(fd[0], "r");
963		e = errno;
964		if (fp) {
965			lim = p + size;
966			for (p = path;  ;  *p++ = c) {
967				if ((c=getc(fp)) < 0) {
968					if (feof(fp))
969						break;
970					if (ferror(fp)) {
971						readerror = 1;
972						e = errno;
973						break;
974					}
975				}
976				if (p == lim) {
977					toolong = 1;
978					break;
979				}
980			}
981		}
982#		if has_waitpid
983			if (waitpid(child, &wstatus, 0) < 0)
984				wstatus = 1;
985#		else
986			{
987				pid_t w;
988				do {
989					if ((w = wait(&wstatus)) < 0) {
990						wstatus = 1;
991						break;
992					}
993				} while (w != child);
994			}
995#		endif
996	}
997	if (!fp) {
998		VOID close(fd[0]);
999		errno = e;
1000		return 0;
1001	}
1002	if (fclose(fp) != 0)
1003		return 0;
1004	if (readerror) {
1005		errno = e;
1006		return 0;
1007	}
1008	if (closeerror) {
1009		errno = closeerrno;
1010		return 0;
1011	}
1012	if (toolong) {
1013		errno = ERANGE;
1014		return 0;
1015	}
1016	if (wstatus  ||  p == path  ||  *--p != '\n') {
1017		errno = EACCES;
1018		return 0;
1019	}
1020	*p = '\0';
1021	return path;
1022}
1023#endif
1024
1025
1026#ifdef PAIRTEST
1027/* test program for pairnames() and getfullRCSname() */
1028
1029char const cmdid[] = "pair";
1030
1031main(argc, argv)
1032int argc; char *argv[];
1033{
1034        int result;
1035	int initflag;
1036	quietflag = initflag = false;
1037
1038        while(--argc, ++argv, argc>=1 && ((*argv)[0] == '-')) {
1039                switch ((*argv)[1]) {
1040
1041		case 'p':       workstdout = stdout;
1042                                break;
1043                case 'i':       initflag=true;
1044                                break;
1045                case 'q':       quietflag=true;
1046                                break;
1047                default:        error("unknown option: %s", *argv);
1048                                break;
1049                }
1050        }
1051
1052        do {
1053		RCSname = workname = 0;
1054		result = pairnames(argc,argv,rcsreadopen,!initflag,quietflag);
1055                if (result!=0) {
1056		    diagnose("RCS pathname: %s; working pathname: %s\nFull RCS pathname: %s\n",
1057			     RCSname, workname, getfullRCSname()
1058		    );
1059                }
1060                switch (result) {
1061                        case 0: continue; /* already paired file */
1062
1063                        case 1: if (initflag) {
1064				    rcserror("already exists");
1065                                } else {
1066				    diagnose("RCS file %s exists\n", RCSname);
1067                                }
1068				Ifclose(finptr);
1069                                break;
1070
1071			case -1:diagnose("RCS file doesn't exist\n");
1072                                break;
1073                }
1074
1075        } while (++argv, --argc>=1);
1076
1077}
1078
1079	void
1080exiterr()
1081{
1082	dirtempunlink();
1083	tempunlink();
1084	_exit(EXIT_FAILURE);
1085}
1086#endif
1087