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