xref: /dragonfly/gnu/usr.bin/rcs/lib/rcsfnms.c (revision 86d7f5d305c6adaa56ff4582ece9859d73106103)
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