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