Add the DragonFly cvs id and perform general cleanups on cvs/rcs/sccs ids. Most
[dragonfly.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  * $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 *
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 *
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
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 *
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
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
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
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
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
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
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 *
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
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
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 *
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
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 *
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 *
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
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
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 *
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 *
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
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
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 *
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
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
1129 exiterr()
1130 {
1131         dirtempunlink();
1132         tempunlink();
1133         _exit(EXIT_FAILURE);
1134 }
1135 #endif