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