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