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