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