111894Speter/* RCS filename and pathname handling */ 211894Speter 39Sjkh/**************************************************************************** 49Sjkh * creation and deletion of /tmp temporaries 511894Speter * pairing of RCS pathnames and working pathnames. 69Sjkh * Testprogram: define PAIRTEST 79Sjkh **************************************************************************** 89Sjkh */ 99Sjkh 1011894Speter/* Copyright 1982, 1988, 1989 Walter Tichy 1111894Speter Copyright 1990, 1991, 1992, 1993, 1994, 1995 Paul Eggert 129Sjkh Distributed under license by the Free Software Foundation, Inc. 139Sjkh 149SjkhThis file is part of RCS. 159Sjkh 169SjkhRCS is free software; you can redistribute it and/or modify 179Sjkhit under the terms of the GNU General Public License as published by 189Sjkhthe Free Software Foundation; either version 2, or (at your option) 199Sjkhany later version. 209Sjkh 219SjkhRCS is distributed in the hope that it will be useful, 229Sjkhbut WITHOUT ANY WARRANTY; without even the implied warranty of 239SjkhMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 249SjkhGNU General Public License for more details. 259Sjkh 269SjkhYou should have received a copy of the GNU General Public License 2711894Speteralong with RCS; see the file COPYING. 2811894SpeterIf not, write to the Free Software Foundation, 2911894Speter59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 309Sjkh 319SjkhReport problems and direct all questions to: 329Sjkh 339Sjkh rcs-bugs@cs.purdue.edu 349Sjkh 359Sjkh*/ 369Sjkh 379Sjkh 389Sjkh 399Sjkh 4011894Speter/* 4111894Speter * Revision 5.16 1995/06/16 06:19:24 eggert 4211894Speter * Update FSF address. 438858Srgrimes * 4411894Speter * Revision 5.15 1995/06/01 16:23:43 eggert 4511894Speter * (basefilename): Renamed from basename to avoid collisions. 4611894Speter * (dirlen): Remove (for similar reasons). 4711894Speter * (rcsreadopen): Open with FOPEN_RB. 4811894Speter * (SLASHSLASH_is_SLASH): Default is 0. 4911894Speter * (getcwd): Work around bad_wait_if_SIGCHLD_ignored bug. 5011894Speter * 5111894Speter * Revision 5.14 1994/03/17 14:05:48 eggert 5211894Speter * Strip trailing SLASHes from TMPDIR; some systems need this. Remove lint. 5311894Speter * 5411894Speter * Revision 5.13 1993/11/03 17:42:27 eggert 5511894Speter * Determine whether a file name is too long indirectly, 5611894Speter * by examining inode numbers, instead of trying to use operating system 5711894Speter * primitives like pathconf, which are not trustworthy in general. 5811894Speter * File names may now hold white space or $. 5911894Speter * Do not flatten ../X in pathnames; that may yield wrong answer for symlinks. 6011894Speter * Add getabsname hook. Improve quality of diagnostics. 6111894Speter * 6211894Speter * Revision 5.12 1992/07/28 16:12:44 eggert 6311894Speter * Add .sty. .pl now implies Perl, not Prolog. Fix fdlock initialization bug. 6411894Speter * Check that $PWD is really ".". Be consistent about pathnames vs filenames. 6511894Speter * 6611894Speter * Revision 5.11 1992/02/17 23:02:25 eggert 6711894Speter * `a/RCS/b/c' is now an RCS file with an empty extension, not just `a/b/RCS/c'. 6811894Speter * 6911894Speter * Revision 5.10 1992/01/24 18:44:19 eggert 7011894Speter * Fix bug: Expand and Ignored weren't reinitialized. 7111894Speter * Avoid `char const c=ch;' compiler bug. 7211894Speter * Add support for bad_creat0. 7311894Speter * 7411894Speter * Revision 5.9 1992/01/06 02:42:34 eggert 7511894Speter * Shorten long (>31 chars) name. 7611894Speter * while (E) ; -> while (E) continue; 7711894Speter * 789Sjkh * Revision 5.8 1991/09/24 00:28:40 eggert 799Sjkh * Don't export bindex(). 809Sjkh * 819Sjkh * Revision 5.7 1991/08/19 03:13:55 eggert 829Sjkh * Fix messages when rcswriteopen fails. 839Sjkh * Look in $TMP and $TEMP if $TMPDIR isn't set. Tune. 849Sjkh * 859Sjkh * Revision 5.6 1991/04/21 11:58:23 eggert 869Sjkh * Fix errno bugs. Add -x, RCSINIT, MS-DOS support. 879Sjkh * 889Sjkh * Revision 5.5 1991/02/26 17:48:38 eggert 899Sjkh * Fix setuid bug. Support new link behavior. 909Sjkh * Define more portable getcwd(). 919Sjkh * 929Sjkh * Revision 5.4 1990/11/01 05:03:43 eggert 939Sjkh * Permit arbitrary data in comment leaders. 949Sjkh * 959Sjkh * Revision 5.3 1990/09/14 22:56:16 hammer 969Sjkh * added more filename extensions and their comment leaders 979Sjkh * 989Sjkh * Revision 5.2 1990/09/04 08:02:23 eggert 999Sjkh * Fix typo when !RCSSEP. 1009Sjkh * 1019Sjkh * Revision 5.1 1990/08/29 07:13:59 eggert 1029Sjkh * Work around buggy compilers with defective argument promotion. 1039Sjkh * 1049Sjkh * Revision 5.0 1990/08/22 08:12:50 eggert 1059Sjkh * Ignore signals when manipulating the semaphore file. 10611894Speter * Modernize list of filename extensions. 10711894Speter * Permit paths of arbitrary length. Beware filenames beginning with "-". 1089Sjkh * Remove compile-time limits; use malloc instead. 1099Sjkh * Permit dates past 1999/12/31. Make lock and temp files faster and safer. 1109Sjkh * Ansify and Posixate. 1119Sjkh * Don't use access(). Fix test for non-regular files. Tune. 1129Sjkh * 1139Sjkh * Revision 4.8 89/05/01 15:09:41 narten 1149Sjkh * changed getwd to not stat empty directories. 1158858Srgrimes * 1169Sjkh * Revision 4.7 88/08/09 19:12:53 eggert 1179Sjkh * Fix troff macro comment leader bug; add Prolog; allow cc -R; remove lint. 1188858Srgrimes * 1199Sjkh * Revision 4.6 87/12/18 11:40:23 narten 1209Sjkh * additional file types added from 4.3 BSD version, and SPARC assembler 1219Sjkh * comment character added. Also, more lint cleanups. (Guy Harris) 1228858Srgrimes * 1239Sjkh * Revision 4.5 87/10/18 10:34:16 narten 1249Sjkh * Updating version numbers. Changes relative to 1.1 actually relative 1259Sjkh * to verion 4.3 1268858Srgrimes * 1279Sjkh * Revision 1.3 87/03/27 14:22:21 jenkins 1289Sjkh * Port to suns 1298858Srgrimes * 1309Sjkh * Revision 1.2 85/06/26 07:34:28 svb 1319Sjkh * Comment leader '% ' for '*.tex' files added. 1328858Srgrimes * 1339Sjkh * Revision 4.3 83/12/15 12:26:48 wft 13411894Speter * Added check for KDELIM in filenames to pairfilenames(). 1358858Srgrimes * 1369Sjkh * Revision 4.2 83/12/02 22:47:45 wft 13711894Speter * Added csh, red, and sl filename suffixes. 1388858Srgrimes * 1399Sjkh * Revision 4.1 83/05/11 16:23:39 wft 1409Sjkh * Added initialization of Dbranch to InitAdmin(). Canged pairfilenames(): 1419Sjkh * 1. added copying of path from workfile to RCS file, if RCS file is omitted; 1429Sjkh * 2. added getting the file status of RCS and working files; 1439Sjkh * 3. added ignoring of directories. 1448858Srgrimes * 1459Sjkh * Revision 3.7 83/05/11 15:01:58 wft 14611894Speter * Added comtable[] which pairs filename suffixes with comment leaders; 1479Sjkh * updated InitAdmin() accordingly. 1488858Srgrimes * 1499Sjkh * Revision 3.6 83/04/05 14:47:36 wft 1509Sjkh * fixed Suffix in InitAdmin(). 1518858Srgrimes * 1529Sjkh * Revision 3.5 83/01/17 18:01:04 wft 1539Sjkh * Added getwd() and rename(); these can be removed by defining 1549Sjkh * V4_2BSD, since they are not needed in 4.2 bsd. 1559Sjkh * Changed sys/param.h to sys/types.h. 1569Sjkh * 1579Sjkh * Revision 3.4 82/12/08 21:55:20 wft 1589Sjkh * removed unused variable. 1599Sjkh * 1609Sjkh * Revision 3.3 82/11/28 20:31:37 wft 16111894Speter * Changed mktempfile() to store the generated filenames. 1629Sjkh * Changed getfullRCSname() to store the file and pathname, and to 1639Sjkh * delete leading "../" and "./". 1649Sjkh * 1659Sjkh * Revision 3.2 82/11/12 14:29:40 wft 1669Sjkh * changed pairfilenames() to handle file.sfx,v; also deleted checkpathnosfx(), 1679Sjkh * checksuffix(), checkfullpath(). Semaphore name generation updated. 1689Sjkh * mktempfile() now checks for nil path; freefilename initialized properly. 1699Sjkh * Added Suffix .h to InitAdmin. Added testprogram PAIRTEST. 1709Sjkh * Moved rmsema, trysema, trydiraccess, getfullRCSname from rcsutil.c to here. 1719Sjkh * 1729Sjkh * Revision 3.1 82/10/18 14:51:28 wft 1739Sjkh * InitAdmin() now initializes StrictLocks=STRICT_LOCKING (def. in rcsbase.h). 1749Sjkh * renamed checkpath() to checkfullpath(). 1759Sjkh */ 1769Sjkh 1779Sjkh 1789Sjkh#include "rcsbase.h" 1799Sjkh 18050472SpeterlibId(fnmsId, "$FreeBSD$") 1819Sjkh 18211894Speterstatic char const *bindex P((char const*,int)); 18311894Speterstatic int fin2open P((char const*, size_t, char const*, size_t, char const*, size_t, RILE*(*)P((struct buf*,struct stat*,int)), int)); 18411894Speterstatic int finopen P((RILE*(*)P((struct buf*,struct stat*,int)), int)); 18511894Speterstatic int suffix_matches P((char const*,char const*)); 18611894Speterstatic size_t dir_useful_len P((char const*)); 18711894Speterstatic size_t suffixlen P((char const*)); 18811894Speterstatic void InitAdmin P((void)); 18911894Speter 19011894Speterchar const *RCSname; 19111894Speterchar *workname; 19211894Speterint fdlock; 1939SjkhFILE *workstdout; 1949Sjkhstruct stat RCSstat; 1959Sjkhchar const *suffixes; 1969Sjkh 1979Sjkhstatic char const rcsdir[] = "RCS"; 19811894Speter#define rcslen (sizeof(rcsdir)-1) 1999Sjkh 2009Sjkhstatic struct buf RCSbuf, RCSb; 2019Sjkhstatic int RCSerrno; 2029Sjkh 2039Sjkh 20411894Speter/* Temp names to be unlinked when done, if they are not 0. */ 2059Sjkh#define TEMPNAMES 5 /* must be at least DIRTEMPNAMES (see rcsedit.c) */ 20611894Speterstatic char *volatile tpnames[TEMPNAMES]; 2079Sjkh 2089Sjkh 2099Sjkhstruct compair { 2109Sjkh char const *suffix, *comlead; 2119Sjkh}; 2129Sjkh 21311894Speter/* 21411894Speter* This table is present only for backwards compatibility. 21511894Speter* Normally we ignore this table, and use the prefix of the `$Log' line instead. 21611894Speter*/ 2179Sjkhstatic struct compair const comtable[] = { 21811894Speter { "a" , "-- " }, /* Ada */ 21911894Speter { "ada" , "-- " }, 22011894Speter { "adb" , "-- " }, 22111894Speter { "ads" , "-- " }, 22211894Speter { "asm" , ";; " }, /* assembler (MS-DOS) */ 22311894Speter { "bat" , ":: " }, /* batch (MS-DOS) */ 22411894Speter { "body", "-- " }, /* Ada */ 22511894Speter { "c" , " * " }, /* C */ 22611894Speter { "c++" , "// " }, /* C++ in all its infinite guises */ 22711894Speter { "cc" , "// " }, 22811894Speter { "cpp" , "// " }, 22911894Speter { "cxx" , "// " }, 23011894Speter { "cl" , ";;; "}, /* Common Lisp */ 23111894Speter { "cmd" , ":: " }, /* command (OS/2) */ 23211894Speter { "cmf" , "c " }, /* CM Fortran */ 23311894Speter { "cs" , " * " }, /* C* */ 23411894Speter { "el" , "; " }, /* Emacs Lisp */ 23511894Speter { "f" , "c " }, /* Fortran */ 23611894Speter { "for" , "c " }, 23711894Speter { "h" , " * " }, /* C-header */ 23811894Speter { "hpp" , "// " }, /* C++ header */ 23911894Speter { "hxx" , "// " }, 24011894Speter { "l" , " * " }, /* lex (NOTE: franzlisp disagrees) */ 24111894Speter { "lisp", ";;; "}, /* Lucid Lisp */ 24211894Speter { "lsp" , ";; " }, /* Microsoft Lisp */ 24311894Speter { "m" , "// " }, /* Objective C */ 24411894Speter { "mac" , ";; " }, /* macro (DEC-10, MS-DOS, PDP-11, VMS, etc) */ 24511894Speter { "me" , ".\\\" "}, /* troff -me */ 24611894Speter { "ml" , "; " }, /* mocklisp */ 24711894Speter { "mm" , ".\\\" "}, /* troff -mm */ 24811894Speter { "ms" , ".\\\" "}, /* troff -ms */ 24911894Speter { "p" , " * " }, /* Pascal */ 25011894Speter { "pas" , " * " }, 25111894Speter { "ps" , "% " }, /* PostScript */ 25211894Speter { "spec", "-- " }, /* Ada */ 25311894Speter { "sty" , "% " }, /* LaTeX style */ 25411894Speter { "tex" , "% " }, /* TeX */ 25511894Speter { "y" , " * " }, /* yacc */ 25611894Speter { 0 , "# " } /* default for unknown suffix; must be last */ 2579Sjkh}; 2589Sjkh 2599Sjkh#if has_mktemp 26011894Speter static char const *tmp P((void)); 2619Sjkh static char const * 2629Sjkhtmp() 2639Sjkh/* Yield the name of the tmp directory. */ 2649Sjkh{ 2659Sjkh static char const *s; 2669Sjkh if (!s 2679Sjkh && !(s = cgetenv("TMPDIR")) /* Unix tradition */ 2689Sjkh && !(s = cgetenv("TMP")) /* DOS tradition */ 2699Sjkh && !(s = cgetenv("TEMP")) /* another DOS tradition */ 2709Sjkh ) 2719Sjkh s = TMPDIR; 2729Sjkh return s; 2739Sjkh} 2749Sjkh#endif 2759Sjkh 2769Sjkh char const * 2779Sjkhmaketemp(n) 2789Sjkh int n; 27911894Speter/* Create a unique pathname using n and the process id and store it 28011894Speter * into the nth slot in tpnames. 28111894Speter * Because of storage in tpnames, tempunlink() can unlink the file later. 28211894Speter * Return a pointer to the pathname created. 2839Sjkh */ 2849Sjkh{ 2859Sjkh char *p; 28611894Speter char const *t = tpnames[n]; 28776301Skris# if has_mktemp 28876301Skris int fd; 28976301Skris# endif 2909Sjkh 2919Sjkh if (t) 2929Sjkh return t; 2939Sjkh 2949Sjkh catchints(); 2959Sjkh { 2969Sjkh# if has_mktemp 2979Sjkh char const *tp = tmp(); 29811894Speter size_t tplen = dir_useful_len(tp); 29911894Speter p = testalloc(tplen + 10); 30011894Speter VOID sprintf(p, "%.*s%cT%cXXXXXX", (int)tplen, tp, SLASH, '0'+n); 30176301Skris fd = mkstemp(p); 30276301Skris if (fd < 0 || !*p) 30311894Speter faterror("can't make temporary pathname `%.*s%cT%cXXXXXX'", 30411894Speter (int)tplen, tp, SLASH, '0'+n 3059Sjkh ); 30676301Skris close(fd); 3079Sjkh# else 30811894Speter static char tpnamebuf[TEMPNAMES][L_tmpnam]; 30911894Speter p = tpnamebuf[n]; 3109Sjkh if (!tmpnam(p) || !*p) 3119Sjkh# ifdef P_tmpdir 31211894Speter faterror("can't make temporary pathname `%s...'",P_tmpdir); 3139Sjkh# else 31411894Speter faterror("can't make temporary pathname"); 3159Sjkh# endif 3169Sjkh# endif 3179Sjkh } 3189Sjkh 31911894Speter tpnames[n] = p; 3209Sjkh return p; 3219Sjkh} 3229Sjkh 3239Sjkh void 3249Sjkhtempunlink() 3259Sjkh/* Clean up maketemp() files. May be invoked by signal handler. 3269Sjkh */ 3279Sjkh{ 3289Sjkh register int i; 3299Sjkh register char *p; 3309Sjkh 3319Sjkh for (i = TEMPNAMES; 0 <= --i; ) 33211894Speter if ((p = tpnames[i])) { 3339Sjkh VOID unlink(p); 3349Sjkh /* 3359Sjkh * We would tfree(p) here, 3369Sjkh * but this might dump core if we're handing a signal. 3379Sjkh * We're about to exit anyway, so we won't bother. 3389Sjkh */ 33911894Speter tpnames[i] = 0; 3409Sjkh } 3419Sjkh} 3429Sjkh 3439Sjkh 3449Sjkh static char const * 34511894Speterbindex(sp, c) 3469Sjkh register char const *sp; 34711894Speter register int c; 3489Sjkh/* Function: Finds the last occurrence of character c in string sp 3499Sjkh * and returns a pointer to the character just beyond it. If the 3509Sjkh * character doesn't occur in the string, sp is returned. 3519Sjkh */ 3529Sjkh{ 35311894Speter register char const *r; 3549Sjkh r = sp; 3559Sjkh while (*sp) { 3569Sjkh if (*sp++ == c) r=sp; 3579Sjkh } 3589Sjkh return r; 3599Sjkh} 3609Sjkh 3619Sjkh 3629Sjkh 3639Sjkh static int 3649Sjkhsuffix_matches(suffix, pattern) 3659Sjkh register char const *suffix, *pattern; 3669Sjkh{ 3679Sjkh register int c; 3689Sjkh if (!pattern) 3699Sjkh return true; 3709Sjkh for (;;) 3719Sjkh switch (*suffix++ - (c = *pattern++)) { 3729Sjkh case 0: 3739Sjkh if (!c) 3749Sjkh return true; 3759Sjkh break; 3769Sjkh 3779Sjkh case 'A'-'a': 3789Sjkh if (ctab[c] == Letter) 3799Sjkh break; 3809Sjkh /* fall into */ 3819Sjkh default: 3829Sjkh return false; 3839Sjkh } 3849Sjkh} 3859Sjkh 3869Sjkh 3879Sjkh static void 3889SjkhInitAdmin() 3899Sjkh/* function: initializes an admin node */ 3909Sjkh{ 3919Sjkh register char const *Suffix; 3929Sjkh register int i; 3939Sjkh 39411894Speter Head=0; Dbranch=0; AccessList=0; Symbols=0; Locks=0; 3959Sjkh StrictLocks=STRICT_LOCKING; 3969Sjkh 3979Sjkh /* guess the comment leader from the suffix*/ 39811894Speter Suffix = bindex(workname, '.'); 39911894Speter if (Suffix==workname) Suffix= ""; /* empty suffix; will get default*/ 4009Sjkh for (i=0; !suffix_matches(Suffix,comtable[i].suffix); i++) 40111894Speter continue; 4029Sjkh Comment.string = comtable[i].comlead; 4039Sjkh Comment.size = strlen(comtable[i].comlead); 40411894Speter Expand = KEYVAL_EXPAND; 40511894Speter clear_buf(&Ignored); 4069Sjkh Lexinit(); /* note: if !finptr, reads nothing; only initializes */ 4079Sjkh} 4089Sjkh 4099Sjkh 4109Sjkh 4119Sjkh void 4129Sjkhbufalloc(b, size) 4139Sjkh register struct buf *b; 4149Sjkh size_t size; 4159Sjkh/* Ensure *B is a name buffer of at least SIZE bytes. 4169Sjkh * *B's old contents can be freed; *B's new contents are undefined. 4179Sjkh */ 4189Sjkh{ 4199Sjkh if (b->size < size) { 4209Sjkh if (b->size) 4219Sjkh tfree(b->string); 4229Sjkh else 4239Sjkh b->size = sizeof(malloc_type); 4249Sjkh while (b->size < size) 4259Sjkh b->size <<= 1; 4269Sjkh b->string = tnalloc(char, b->size); 4279Sjkh } 4289Sjkh} 4299Sjkh 4309Sjkh void 4319Sjkhbufrealloc(b, size) 4329Sjkh register struct buf *b; 4339Sjkh size_t size; 4349Sjkh/* like bufalloc, except *B's old contents, if any, are preserved */ 4359Sjkh{ 4369Sjkh if (b->size < size) { 4379Sjkh if (!b->size) 4389Sjkh bufalloc(b, size); 4399Sjkh else { 4409Sjkh while ((b->size <<= 1) < size) 44111894Speter continue; 4429Sjkh b->string = trealloc(char, b->string, b->size); 4439Sjkh } 4449Sjkh } 4459Sjkh} 4469Sjkh 4479Sjkh void 4489Sjkhbufautoend(b) 4499Sjkh struct buf *b; 4509Sjkh/* Free an auto buffer at block exit. */ 4519Sjkh{ 4529Sjkh if (b->size) 4539Sjkh tfree(b->string); 4549Sjkh} 4559Sjkh 4569Sjkh struct cbuf 4579Sjkhbufremember(b, s) 4589Sjkh struct buf *b; 4599Sjkh size_t s; 4609Sjkh/* 4619Sjkh * Free the buffer B with used size S. 4629Sjkh * Yield a cbuf with identical contents. 4639Sjkh * The cbuf will be reclaimed when this input file is finished. 4649Sjkh */ 4659Sjkh{ 4669Sjkh struct cbuf cb; 4679Sjkh 4689Sjkh if ((cb.size = s)) 4699Sjkh cb.string = fremember(trealloc(char, b->string, s)); 4709Sjkh else { 4719Sjkh bufautoend(b); /* not really auto */ 4729Sjkh cb.string = ""; 4739Sjkh } 4749Sjkh return cb; 4759Sjkh} 4769Sjkh 4779Sjkh char * 4789Sjkhbufenlarge(b, alim) 4799Sjkh register struct buf *b; 4809Sjkh char const **alim; 4819Sjkh/* Make *B larger. Set *ALIM to its new limit, and yield the relocated value 4829Sjkh * of its old limit. 4839Sjkh */ 4849Sjkh{ 4859Sjkh size_t s = b->size; 4869Sjkh bufrealloc(b, s + 1); 4879Sjkh *alim = b->string + b->size; 4889Sjkh return b->string + s; 4899Sjkh} 4909Sjkh 4919Sjkh void 4929Sjkhbufscat(b, s) 4939Sjkh struct buf *b; 4949Sjkh char const *s; 4959Sjkh/* Concatenate S to B's end. */ 4969Sjkh{ 4979Sjkh size_t blen = b->string ? strlen(b->string) : 0; 4989Sjkh bufrealloc(b, blen+strlen(s)+1); 4999Sjkh VOID strcpy(b->string+blen, s); 5009Sjkh} 5019Sjkh 5029Sjkh void 5039Sjkhbufscpy(b, s) 5049Sjkh struct buf *b; 5059Sjkh char const *s; 5069Sjkh/* Copy S into B. */ 5079Sjkh{ 5089Sjkh bufalloc(b, strlen(s)+1); 5099Sjkh VOID strcpy(b->string, s); 5109Sjkh} 5119Sjkh 5129Sjkh 5139Sjkh char const * 51411894Speterbasefilename(p) 5159Sjkh char const *p; 5169Sjkh/* Yield the address of the base filename of the pathname P. */ 5179Sjkh{ 5189Sjkh register char const *b = p, *q = p; 5199Sjkh for (;;) 5209Sjkh switch (*q++) { 5219Sjkh case SLASHes: b = q; break; 5229Sjkh case 0: return b; 5239Sjkh } 5249Sjkh} 5259Sjkh 5269Sjkh 5279Sjkh static size_t 5289Sjkhsuffixlen(x) 5299Sjkh char const *x; 53011894Speter/* Yield the length of X, an RCS pathname suffix. */ 5319Sjkh{ 5329Sjkh register char const *p; 5339Sjkh 5349Sjkh p = x; 5359Sjkh for (;;) 5369Sjkh switch (*p) { 5379Sjkh case 0: case SLASHes: 5389Sjkh return p - x; 5399Sjkh 5409Sjkh default: 5419Sjkh ++p; 5429Sjkh continue; 5439Sjkh } 5449Sjkh} 5459Sjkh 5469Sjkh char const * 5479Sjkhrcssuffix(name) 5489Sjkh char const *name; 54911894Speter/* Yield the suffix of NAME if it is an RCS pathname, 0 otherwise. */ 5509Sjkh{ 5519Sjkh char const *x, *p, *nz; 55211894Speter size_t nl, xl; 5539Sjkh 5549Sjkh nl = strlen(name); 5559Sjkh nz = name + nl; 5569Sjkh x = suffixes; 5579Sjkh do { 5589Sjkh if ((xl = suffixlen(x))) { 5599Sjkh if (xl <= nl && memcmp(p = nz-xl, x, xl) == 0) 5609Sjkh return p; 56111894Speter } else 56211894Speter for (p = name; p < nz - rcslen; p++) 56311894Speter if ( 56411894Speter isSLASH(p[rcslen]) 56511894Speter && (p==name || isSLASH(p[-1])) 56611894Speter && memcmp(p, rcsdir, rcslen) == 0 56711894Speter ) 56811894Speter return nz; 5699Sjkh x += xl; 5709Sjkh } while (*x++); 5719Sjkh return 0; 5729Sjkh} 5739Sjkh 5749Sjkh /*ARGSUSED*/ RILE * 57511894Speterrcsreadopen(RCSpath, status, mustread) 57611894Speter struct buf *RCSpath; 5779Sjkh struct stat *status; 5789Sjkh int mustread; 57911894Speter/* Open RCSPATH for reading and yield its FILE* descriptor. 5809Sjkh * If successful, set *STATUS to its status. 58111894Speter * Pass this routine to pairnames() for read-only access to the file. */ 5829Sjkh{ 58311894Speter return Iopen(RCSpath->string, FOPEN_RB, status); 5849Sjkh} 5859Sjkh 5869Sjkh static int 5879Sjkhfinopen(rcsopen, mustread) 5889Sjkh RILE *(*rcsopen)P((struct buf*,struct stat*,int)); 5899Sjkh int mustread; 5909Sjkh/* 5919Sjkh * Use RCSOPEN to open an RCS file; MUSTREAD is set if the file must be read. 5929Sjkh * Set finptr to the result and yield true if successful. 5939Sjkh * RCSb holds the file's name. 5949Sjkh * Set RCSbuf to the best RCS name found so far, and RCSerrno to its errno. 5959Sjkh * Yield true if successful or if an unusual failure. 5969Sjkh */ 5979Sjkh{ 5989Sjkh int interesting, preferold; 5999Sjkh 6009Sjkh /* 6019Sjkh * We prefer an old name to that of a nonexisting new RCS file, 6029Sjkh * unless we tried locking the old name and failed. 6039Sjkh */ 60411894Speter preferold = RCSbuf.string[0] && (mustread||0<=fdlock); 6059Sjkh 6069Sjkh finptr = (*rcsopen)(&RCSb, &RCSstat, mustread); 6079Sjkh interesting = finptr || errno!=ENOENT; 6089Sjkh if (interesting || !preferold) { 6099Sjkh /* Use the new name. */ 6109Sjkh RCSerrno = errno; 6119Sjkh bufscpy(&RCSbuf, RCSb.string); 6129Sjkh } 6139Sjkh return interesting; 6149Sjkh} 6159Sjkh 6169Sjkh static int 6179Sjkhfin2open(d, dlen, base, baselen, x, xlen, rcsopen, mustread) 6189Sjkh char const *d, *base, *x; 6199Sjkh size_t dlen, baselen, xlen; 6209Sjkh RILE *(*rcsopen)P((struct buf*,struct stat*,int)); 6219Sjkh int mustread; 6229Sjkh/* 6239Sjkh * D is a directory name with length DLEN (including trailing slash). 6249Sjkh * BASE is a filename with length BASELEN. 62511894Speter * X is an RCS pathname suffix with length XLEN. 6269Sjkh * Use RCSOPEN to open an RCS file; MUSTREAD is set if the file must be read. 6279Sjkh * Yield true if successful. 6289Sjkh * Try dRCS/basex first; if that fails and x is nonempty, try dbasex. 6299Sjkh * Put these potential names in RCSb. 6309Sjkh * Set RCSbuf to the best RCS name found so far, and RCSerrno to its errno. 6319Sjkh * Yield true if successful or if an unusual failure. 6329Sjkh */ 6339Sjkh{ 6349Sjkh register char *p; 6359Sjkh 63611894Speter bufalloc(&RCSb, dlen + rcslen + 1 + baselen + xlen + 1); 6379Sjkh 6389Sjkh /* Try dRCS/basex. */ 6399Sjkh VOID memcpy(p = RCSb.string, d, dlen); 64011894Speter VOID memcpy(p += dlen, rcsdir, rcslen); 64111894Speter p += rcslen; 6429Sjkh *p++ = SLASH; 6439Sjkh VOID memcpy(p, base, baselen); 6449Sjkh VOID memcpy(p += baselen, x, xlen); 6459Sjkh p[xlen] = 0; 6469Sjkh if (xlen) { 6479Sjkh if (finopen(rcsopen, mustread)) 6489Sjkh return true; 6499Sjkh 6509Sjkh /* Try dbasex. */ 6519Sjkh /* Start from scratch, because finopen() may have changed RCSb. */ 6529Sjkh VOID memcpy(p = RCSb.string, d, dlen); 6539Sjkh VOID memcpy(p += dlen, base, baselen); 6549Sjkh VOID memcpy(p += baselen, x, xlen); 6559Sjkh p[xlen] = 0; 6569Sjkh } 6579Sjkh return finopen(rcsopen, mustread); 6589Sjkh} 6599Sjkh 6609Sjkh int 66111894Speterpairnames(argc, argv, rcsopen, mustread, quiet) 6629Sjkh int argc; 6639Sjkh char **argv; 6649Sjkh RILE *(*rcsopen)P((struct buf*,struct stat*,int)); 6659Sjkh int mustread, quiet; 66611894Speter/* 66711894Speter * Pair the pathnames pointed to by argv; argc indicates 6689Sjkh * how many there are. 66911894Speter * Place a pointer to the RCS pathname into RCSname, 67011894Speter * and a pointer to the pathname of the working file into workname. 67111894Speter * If both are given, and workstdout 6729Sjkh * is set, a warning is printed. 6739Sjkh * 6749Sjkh * If the RCS file exists, places its status into RCSstat. 6759Sjkh * 6769Sjkh * If the RCS file exists, it is RCSOPENed for reading, the file pointer 6779Sjkh * is placed into finptr, and the admin-node is read in; returns 1. 6789Sjkh * If the RCS file does not exist and MUSTREAD, 6799Sjkh * print an error unless QUIET and return 0. 6809Sjkh * Otherwise, initialize the admin node and return -1. 6819Sjkh * 6829Sjkh * 0 is returned on all errors, e.g. files that are not regular files. 6839Sjkh */ 6849Sjkh{ 6859Sjkh static struct buf tempbuf; 6869Sjkh 6879Sjkh register char *p, *arg, *RCS1; 68811894Speter char const *base, *RCSbase, *x; 6899Sjkh int paired; 6909Sjkh size_t arglen, dlen, baselen, xlen; 6919Sjkh 69211894Speter fdlock = -1; 69311894Speter 69411894Speter if (!(arg = *argv)) return 0; /* already paired pathname */ 6959Sjkh if (*arg == '-') { 69611894Speter error("%s option is ignored after pathnames", arg); 6979Sjkh return 0; 6989Sjkh } 6999Sjkh 70011894Speter base = basefilename(arg); 7019Sjkh paired = false; 7029Sjkh 7039Sjkh /* first check suffix to see whether it is an RCS file or not */ 7049Sjkh if ((x = rcssuffix(arg))) 7059Sjkh { 70611894Speter /* RCS pathname given */ 7079Sjkh RCS1 = arg; 70811894Speter RCSbase = base; 70911894Speter baselen = x - base; 7109Sjkh if ( 7119Sjkh 1 < argc && 71211894Speter !rcssuffix(workname = p = argv[1]) && 7139Sjkh baselen <= (arglen = strlen(p)) && 71411894Speter ((p+=arglen-baselen) == workname || isSLASH(p[-1])) && 71511894Speter memcmp(base, p, baselen) == 0 7169Sjkh ) { 7179Sjkh argv[1] = 0; 7189Sjkh paired = true; 7199Sjkh } else { 72011894Speter bufscpy(&tempbuf, base); 72111894Speter workname = p = tempbuf.string; 7229Sjkh p[baselen] = 0; 7239Sjkh } 7249Sjkh } else { 7259Sjkh /* working file given; now try to find RCS file */ 72611894Speter workname = arg; 72711894Speter baselen = strlen(base); 72811894Speter /* Derive RCS pathname. */ 7299Sjkh if ( 7309Sjkh 1 < argc && 7319Sjkh (x = rcssuffix(RCS1 = argv[1])) && 7329Sjkh baselen <= x - RCS1 && 73311894Speter ((RCSbase=x-baselen)==RCS1 || isSLASH(RCSbase[-1])) && 73411894Speter memcmp(base, RCSbase, baselen) == 0 7359Sjkh ) { 7369Sjkh argv[1] = 0; 7379Sjkh paired = true; 7389Sjkh } else 73911894Speter RCSbase = RCS1 = 0; 7409Sjkh } 74111894Speter /* Now we have a (tentative) RCS pathname in RCS1 and workname. */ 7429Sjkh /* Second, try to find the right RCS file */ 74311894Speter if (RCSbase!=RCS1) { 7449Sjkh /* a path for RCSfile is given; single RCS file to look for */ 7459Sjkh bufscpy(&RCSbuf, RCS1); 7469Sjkh finptr = (*rcsopen)(&RCSbuf, &RCSstat, mustread); 7479Sjkh RCSerrno = errno; 7489Sjkh } else { 7499Sjkh bufscpy(&RCSbuf, ""); 7509Sjkh if (RCS1) 75111894Speter /* RCS filename was given without path. */ 75211894Speter VOID fin2open(arg, (size_t)0, RCSbase, baselen, 7539Sjkh x, strlen(x), rcsopen, mustread 7549Sjkh ); 7559Sjkh else { 75611894Speter /* No RCS pathname was given. */ 7579Sjkh /* Try each suffix in turn. */ 75811894Speter dlen = base-arg; 7599Sjkh x = suffixes; 76011894Speter while (! fin2open(arg, dlen, base, baselen, 7619Sjkh x, xlen=suffixlen(x), rcsopen, mustread 7629Sjkh )) { 7639Sjkh x += xlen; 7649Sjkh if (!*x++) 7659Sjkh break; 7669Sjkh } 7679Sjkh } 7689Sjkh } 76911894Speter RCSname = p = RCSbuf.string; 7709Sjkh if (finptr) { 7719Sjkh if (!S_ISREG(RCSstat.st_mode)) { 7729Sjkh error("%s isn't a regular file -- ignored", p); 7739Sjkh return 0; 7749Sjkh } 7759Sjkh Lexinit(); getadmin(); 7769Sjkh } else { 77711894Speter if (RCSerrno!=ENOENT || mustread || fdlock<0) { 7789Sjkh if (RCSerrno == EEXIST) 7799Sjkh error("RCS file %s is in use", p); 7809Sjkh else if (!quiet || RCSerrno!=ENOENT) 7819Sjkh enerror(RCSerrno, p); 7829Sjkh return 0; 7839Sjkh } 7849Sjkh InitAdmin(); 7859Sjkh }; 7869Sjkh 7879Sjkh if (paired && workstdout) 78811894Speter workwarn("Working file ignored due to -p option"); 7899Sjkh 7909Sjkh prevkeys = false; 7919Sjkh return finptr ? 1 : -1; 7929Sjkh} 7939Sjkh 7949Sjkh 7959Sjkh char const * 7969SjkhgetfullRCSname() 79711894Speter/* 79811894Speter * Return a pointer to the full pathname of the RCS file. 79911894Speter * Remove leading `./'. 8009Sjkh */ 8019Sjkh{ 80211894Speter if (ROOTPATH(RCSname)) { 80311894Speter return RCSname; 80411894Speter } else { 80511894Speter static struct buf rcsbuf; 80611894Speter# if needs_getabsname 80711894Speter bufalloc(&rcsbuf, SIZEABLE_PATH + 1); 80811894Speter while (getabsname(RCSname, rcsbuf.string, rcsbuf.size) != 0) 80911894Speter if (errno == ERANGE) 81011894Speter bufalloc(&rcsbuf, rcsbuf.size<<1); 81111894Speter else 81211894Speter efaterror("getabsname"); 81311894Speter# else 81411894Speter static char const *wdptr; 81511894Speter static struct buf wdbuf; 81611894Speter static size_t wdlen; 8179Sjkh 81811894Speter register char const *r; 81911894Speter register size_t dlen; 82011894Speter register char *d; 82111894Speter register char const *wd; 8229Sjkh 8239Sjkh if (!(wd = wdptr)) { 8249Sjkh /* Get working directory for the first time. */ 82511894Speter char *PWD = cgetenv("PWD"); 82611894Speter struct stat PWDstat, dotstat; 82711894Speter if (! ( 82811894Speter (d = PWD) && 82911894Speter ROOTPATH(PWD) && 83011894Speter stat(PWD, &PWDstat) == 0 && 83111894Speter stat(".", &dotstat) == 0 && 83211894Speter same_file(PWDstat, dotstat, 1) 83311894Speter )) { 8349Sjkh bufalloc(&wdbuf, SIZEABLE_PATH + 1); 83511894Speter# if has_getcwd || !has_getwd 83611894Speter while (!(d = getcwd(wdbuf.string, wdbuf.size))) 83711894Speter if (errno == ERANGE) 83811894Speter bufalloc(&wdbuf, wdbuf.size<<1); 83911894Speter else if ((d = PWD)) 84011894Speter break; 84111894Speter else 84211894Speter efaterror("getcwd"); 84311894Speter# else 8449Sjkh d = getwd(wdbuf.string); 84511894Speter if (!d && !(d = PWD)) 84611894Speter efaterror("getwd"); 8479Sjkh# endif 8489Sjkh } 84911894Speter wdlen = dir_useful_len(d); 85011894Speter d[wdlen] = 0; 8519Sjkh wdptr = wd = d; 8529Sjkh } 85311894Speter /* 85411894Speter * Remove leading `./'s from RCSname. 85511894Speter * Do not try to handle `../', since removing it may yield 85611894Speter * the wrong answer in the presence of symbolic links. 85711894Speter */ 85811894Speter for (r = RCSname; r[0]=='.' && isSLASH(r[1]); r += 2) 85911894Speter /* `.////' is equivalent to `./'. */ 86011894Speter while (isSLASH(r[2])) 86111894Speter r++; 86211894Speter /* Build full pathname. */ 86311894Speter dlen = wdlen; 86411894Speter bufalloc(&rcsbuf, dlen + strlen(r) + 2); 8659Sjkh d = rcsbuf.string; 86611894Speter VOID memcpy(d, wd, dlen); 86711894Speter d += dlen; 8689Sjkh *d++ = SLASH; 86911894Speter VOID strcpy(d, r); 87011894Speter# endif 87111894Speter return rcsbuf.string; 8729Sjkh } 8739Sjkh} 8749Sjkh 87528407Speter/* Derived from code from the XFree86 project */ 87625699Speter char const * 87725699SpetergetfullCVSname() 87828407Speter/* Function: returns a pointer to the path name of the RCS file with the 87928407Speter * CVSROOT part stripped off, and with 'Attic/' stripped off (if present). 88025699Speter */ 88125699Speter{ 88225699Speter 88328407Speter#define ATTICDIR "/Attic" 88425699Speter 88528407Speter char const *namebuf = getfullRCSname(); 88628407Speter char *cvsroot = cgetenv("CVSROOT"); 88728407Speter int cvsrootlen; 88828407Speter char *c = NULL; 88928407Speter int alen = strlen(ATTICDIR); 89028407Speter 89128407Speter if ((c = strrchr(namebuf, '/')) != NULL) { 89228566Speter if (namebuf - c >= alen) { 89328407Speter if (!strncmp(c - alen, ATTICDIR, alen)) { 89428407Speter while(*c != '\0') { 89528407Speter *(c - alen) = *c; 89628407Speter c++; 89728407Speter } 89828407Speter *(c - alen) = '\0'; 89925699Speter } 90028407Speter } 90125699Speter } 90228407Speter 90328407Speter if (!cvsroot) 90428407Speter return(namebuf); 90528407Speter else 90628407Speter { 90728407Speter cvsrootlen = strlen(cvsroot); 90828407Speter if (!strncmp(namebuf, cvsroot, cvsrootlen) && 90928407Speter namebuf[cvsrootlen] == '/') 91028407Speter return(namebuf + cvsrootlen + 1); 91128407Speter else 91228407Speter return(namebuf); 91328407Speter } 91425699Speter} 91525699Speter 91611894Speter static size_t 91711894Speterdir_useful_len(d) 91811894Speter char const *d; 91911894Speter/* 92011894Speter* D names a directory; yield the number of characters of D's useful part. 92111894Speter* To create a file in D, append a SLASH and a file name to D's useful part. 92211894Speter* Ignore trailing slashes if possible; not only are they ugly, 92311894Speter* but some non-Posix systems misbehave unless the slashes are omitted. 92411894Speter*/ 92511894Speter{ 92611894Speter# ifndef SLASHSLASH_is_SLASH 92711894Speter# define SLASHSLASH_is_SLASH 0 92811894Speter# endif 92911894Speter size_t dlen = strlen(d); 93011894Speter if (!SLASHSLASH_is_SLASH && dlen==2 && isSLASH(d[0]) && isSLASH(d[1])) 93111894Speter --dlen; 93211894Speter else 93311894Speter while (dlen && isSLASH(d[dlen-1])) 93411894Speter --dlen; 93511894Speter return dlen; 93611894Speter} 93711894Speter 9389Sjkh#ifndef isSLASH 9399Sjkh int 9409SjkhisSLASH(c) 9419Sjkh int c; 9429Sjkh{ 9439Sjkh switch (c) { 9449Sjkh case SLASHes: 9459Sjkh return true; 9469Sjkh default: 9479Sjkh return false; 9489Sjkh } 9499Sjkh} 9509Sjkh#endif 9519Sjkh 9529Sjkh 9539Sjkh#if !has_getcwd && !has_getwd 9549Sjkh 9559Sjkh char * 9569Sjkhgetcwd(path, size) 9579Sjkh char *path; 9589Sjkh size_t size; 9599Sjkh{ 9609Sjkh static char const usrbinpwd[] = "/usr/bin/pwd"; 9619Sjkh# define binpwd (usrbinpwd+4) 9629Sjkh 9639Sjkh register FILE *fp; 9649Sjkh register int c; 9659Sjkh register char *p, *lim; 9669Sjkh int closeerrno, closeerror, e, fd[2], readerror, toolong, wstatus; 9679Sjkh pid_t child; 9689Sjkh 9699Sjkh if (!size) { 9709Sjkh errno = EINVAL; 9719Sjkh return 0; 9729Sjkh } 9739Sjkh if (pipe(fd) != 0) 9749Sjkh return 0; 97511894Speter# if bad_wait_if_SIGCHLD_ignored 97611894Speter# ifndef SIGCHLD 97711894Speter# define SIGCHLD SIGCLD 97811894Speter# endif 97911894Speter VOID signal(SIGCHLD, SIG_DFL); 98011894Speter# endif 9819Sjkh if (!(child = vfork())) { 9829Sjkh if ( 9839Sjkh close(fd[0]) == 0 && 9849Sjkh (fd[1] == STDOUT_FILENO || 9859Sjkh# ifdef F_DUPFD 9869Sjkh (VOID close(STDOUT_FILENO), 9879Sjkh fcntl(fd[1], F_DUPFD, STDOUT_FILENO)) 9889Sjkh# else 9899Sjkh dup2(fd[1], STDOUT_FILENO) 9909Sjkh# endif 9919Sjkh == STDOUT_FILENO && 9929Sjkh close(fd[1]) == 0 9939Sjkh ) 9949Sjkh ) { 9959Sjkh VOID close(STDERR_FILENO); 9969Sjkh VOID execl(binpwd, binpwd, (char *)0); 9979Sjkh VOID execl(usrbinpwd, usrbinpwd, (char *)0); 9989Sjkh } 9999Sjkh _exit(EXIT_FAILURE); 10009Sjkh } 10019Sjkh e = errno; 10029Sjkh closeerror = close(fd[1]); 10039Sjkh closeerrno = errno; 10049Sjkh fp = 0; 10059Sjkh readerror = toolong = wstatus = 0; 10069Sjkh p = path; 10079Sjkh if (0 <= child) { 10089Sjkh fp = fdopen(fd[0], "r"); 10099Sjkh e = errno; 10109Sjkh if (fp) { 10119Sjkh lim = p + size; 10129Sjkh for (p = path; ; *p++ = c) { 10139Sjkh if ((c=getc(fp)) < 0) { 10149Sjkh if (feof(fp)) 10159Sjkh break; 10169Sjkh if (ferror(fp)) { 10179Sjkh readerror = 1; 10189Sjkh e = errno; 10199Sjkh break; 10209Sjkh } 10219Sjkh } 10229Sjkh if (p == lim) { 10239Sjkh toolong = 1; 10249Sjkh break; 10259Sjkh } 10269Sjkh } 10279Sjkh } 10289Sjkh# if has_waitpid 10299Sjkh if (waitpid(child, &wstatus, 0) < 0) 10309Sjkh wstatus = 1; 10319Sjkh# else 103211894Speter { 103311894Speter pid_t w; 103411894Speter do { 103511894Speter if ((w = wait(&wstatus)) < 0) { 103611894Speter wstatus = 1; 103711894Speter break; 103811894Speter } 103911894Speter } while (w != child); 104011894Speter } 10419Sjkh# endif 10429Sjkh } 10439Sjkh if (!fp) { 10449Sjkh VOID close(fd[0]); 10459Sjkh errno = e; 10469Sjkh return 0; 10479Sjkh } 10489Sjkh if (fclose(fp) != 0) 10499Sjkh return 0; 10509Sjkh if (readerror) { 10519Sjkh errno = e; 10529Sjkh return 0; 10539Sjkh } 10549Sjkh if (closeerror) { 10559Sjkh errno = closeerrno; 10569Sjkh return 0; 10579Sjkh } 10589Sjkh if (toolong) { 10599Sjkh errno = ERANGE; 10609Sjkh return 0; 10619Sjkh } 10629Sjkh if (wstatus || p == path || *--p != '\n') { 10639Sjkh errno = EACCES; 10649Sjkh return 0; 10659Sjkh } 10669Sjkh *p = '\0'; 10679Sjkh return path; 10689Sjkh} 10699Sjkh#endif 10709Sjkh 10719Sjkh 10729Sjkh#ifdef PAIRTEST 107311894Speter/* test program for pairnames() and getfullRCSname() */ 10749Sjkh 10759Sjkhchar const cmdid[] = "pair"; 10769Sjkh 10779Sjkhmain(argc, argv) 10789Sjkhint argc; char *argv[]; 10799Sjkh{ 10809Sjkh int result; 10819Sjkh int initflag; 10829Sjkh quietflag = initflag = false; 10839Sjkh 10849Sjkh while(--argc, ++argv, argc>=1 && ((*argv)[0] == '-')) { 10859Sjkh switch ((*argv)[1]) { 10869Sjkh 10879Sjkh case 'p': workstdout = stdout; 10889Sjkh break; 10899Sjkh case 'i': initflag=true; 10909Sjkh break; 10919Sjkh case 'q': quietflag=true; 10929Sjkh break; 10939Sjkh default: error("unknown option: %s", *argv); 10949Sjkh break; 10959Sjkh } 10969Sjkh } 10979Sjkh 10989Sjkh do { 109911894Speter RCSname = workname = 0; 110011894Speter result = pairnames(argc,argv,rcsreadopen,!initflag,quietflag); 11019Sjkh if (result!=0) { 110211894Speter diagnose("RCS pathname: %s; working pathname: %s\nFull RCS pathname: %s\n", 110311894Speter RCSname, workname, getfullRCSname() 11049Sjkh ); 11059Sjkh } 11069Sjkh switch (result) { 11079Sjkh case 0: continue; /* already paired file */ 11089Sjkh 11099Sjkh case 1: if (initflag) { 111011894Speter rcserror("already exists"); 11119Sjkh } else { 111211894Speter diagnose("RCS file %s exists\n", RCSname); 11139Sjkh } 11149Sjkh Ifclose(finptr); 11159Sjkh break; 11169Sjkh 11179Sjkh case -1:diagnose("RCS file doesn't exist\n"); 11189Sjkh break; 11199Sjkh } 11209Sjkh 11219Sjkh } while (++argv, --argc>=1); 11229Sjkh 11239Sjkh} 11249Sjkh 112511894Speter void 11269Sjkhexiterr() 11279Sjkh{ 11289Sjkh dirtempunlink(); 11299Sjkh tempunlink(); 11309Sjkh _exit(EXIT_FAILURE); 11319Sjkh} 11329Sjkh#endif 1133