Initial import from FreeBSD RELENG_4:
[games.git] / gnu / usr.bin / rcs / lib / rcsfnms.c
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: src/gnu/usr.bin/rcs/lib/rcsfnms.c,v 1.10.2.1 2001/05/12 10:29:43 kris Exp $")
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 *
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 *
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
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 *
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
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
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
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
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
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
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 *
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
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
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 *
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
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 *
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 *
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
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
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 *
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 *
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
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
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 *
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
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
1126 exiterr()
1127 {
1128         dirtempunlink();
1129         tempunlink();
1130         _exit(EXIT_FAILURE);
1131 }
1132 #endif