1 /* RCS filename and pathname handling */
3 /****************************************************************************
4 * creation and deletion of /tmp temporaries
5 * pairing of RCS pathnames and working pathnames.
6 * Testprogram: define PAIRTEST
7 ****************************************************************************
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.
14 This file is part of RCS.
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)
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.
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.
31 Report problems and direct all questions to:
33 rcs-bugs@cs.purdue.edu
41 * Revision 5.16 1995/06/16 06:19:24 eggert
44 * Revision 5.15 1995/06/01 16:23:43 eggert
45 * (basefilename): Renamed from basename to avoid collisions.
46 * (dirlen): Remove (for similar reasons).
47 * (rcsreadopen): Open with FOPEN_RB.
48 * (SLASHSLASH_is_SLASH): Default is 0.
49 * (getcwd): Work around bad_wait_if_SIGCHLD_ignored bug.
51 * Revision 5.14 1994/03/17 14:05:48 eggert
52 * Strip trailing SLASHes from TMPDIR; some systems need this. Remove lint.
54 * Revision 5.13 1993/11/03 17:42:27 eggert
55 * Determine whether a file name is too long indirectly,
56 * by examining inode numbers, instead of trying to use operating system
57 * primitives like pathconf, which are not trustworthy in general.
58 * File names may now hold white space or $.
59 * Do not flatten ../X in pathnames; that may yield wrong answer for symlinks.
60 * Add getabsname hook. Improve quality of diagnostics.
62 * Revision 5.12 1992/07/28 16:12:44 eggert
63 * Add .sty. .pl now implies Perl, not Prolog. Fix fdlock initialization bug.
64 * Check that $PWD is really ".". Be consistent about pathnames vs filenames.
66 * Revision 5.11 1992/02/17 23:02:25 eggert
67 * `a/RCS/b/c' is now an RCS file with an empty extension, not just `a/b/RCS/c'.
69 * Revision 5.10 1992/01/24 18:44:19 eggert
70 * Fix bug: Expand and Ignored weren't reinitialized.
71 * Avoid `char const c=ch;' compiler bug.
72 * Add support for bad_creat0.
74 * Revision 5.9 1992/01/06 02:42:34 eggert
75 * Shorten long (>31 chars) name.
76 * while (E) ; -> while (E) continue;
78 * Revision 5.8 1991/09/24 00:28:40 eggert
79 * Don't export bindex().
81 * Revision 5.7 1991/08/19 03:13:55 eggert
82 * Fix messages when rcswriteopen fails.
83 * Look in $TMP and $TEMP if $TMPDIR isn't set. Tune.
85 * Revision 5.6 1991/04/21 11:58:23 eggert
86 * Fix errno bugs. Add -x, RCSINIT, MS-DOS support.
88 * Revision 5.5 1991/02/26 17:48:38 eggert
89 * Fix setuid bug. Support new link behavior.
90 * Define more portable getcwd().
92 * Revision 5.4 1990/11/01 05:03:43 eggert
93 * Permit arbitrary data in comment leaders.
95 * Revision 5.3 1990/09/14 22:56:16 hammer
96 * added more filename extensions and their comment leaders
98 * Revision 5.2 1990/09/04 08:02:23 eggert
99 * Fix typo when !RCSSEP.
101 * Revision 5.1 1990/08/29 07:13:59 eggert
102 * Work around buggy compilers with defective argument promotion.
104 * Revision 5.0 1990/08/22 08:12:50 eggert
105 * Ignore signals when manipulating the semaphore file.
106 * Modernize list of filename extensions.
107 * Permit paths of arbitrary length. Beware filenames beginning with "-".
108 * Remove compile-time limits; use malloc instead.
109 * Permit dates past 1999/12/31. Make lock and temp files faster and safer.
110 * Ansify and Posixate.
111 * Don't use access(). Fix test for non-regular files. Tune.
113 * Revision 4.8 89/05/01 15:09:41 narten
114 * changed getwd to not stat empty directories.
116 * Revision 4.7 88/08/09 19:12:53 eggert
117 * Fix troff macro comment leader bug; add Prolog; allow cc -R; remove lint.
119 * Revision 4.6 87/12/18 11:40:23 narten
120 * additional file types added from 4.3 BSD version, and SPARC assembler
121 * comment character added. Also, more lint cleanups. (Guy Harris)
123 * Revision 4.5 87/10/18 10:34:16 narten
124 * Updating version numbers. Changes relative to 1.1 actually relative
127 * Revision 1.3 87/03/27 14:22:21 jenkins
130 * Revision 1.2 85/06/26 07:34:28 svb
131 * Comment leader '% ' for '*.tex' files added.
133 * Revision 4.3 83/12/15 12:26:48 wft
134 * Added check for KDELIM in filenames to pairfilenames().
136 * Revision 4.2 83/12/02 22:47:45 wft
137 * Added csh, red, and sl filename suffixes.
139 * Revision 4.1 83/05/11 16:23:39 wft
140 * Added initialization of Dbranch to InitAdmin(). Canged pairfilenames():
141 * 1. added copying of path from workfile to RCS file, if RCS file is omitted;
142 * 2. added getting the file status of RCS and working files;
143 * 3. added ignoring of directories.
145 * Revision 3.7 83/05/11 15:01:58 wft
146 * Added comtable[] which pairs filename suffixes with comment leaders;
147 * updated InitAdmin() accordingly.
149 * Revision 3.6 83/04/05 14:47:36 wft
150 * fixed Suffix in InitAdmin().
152 * Revision 3.5 83/01/17 18:01:04 wft
153 * Added getwd() and rename(); these can be removed by defining
154 * V4_2BSD, since they are not needed in 4.2 bsd.
155 * Changed sys/param.h to sys/types.h.
157 * Revision 3.4 82/12/08 21:55:20 wft
158 * removed unused variable.
160 * Revision 3.3 82/11/28 20:31:37 wft
161 * Changed mktempfile() to store the generated filenames.
162 * Changed getfullRCSname() to store the file and pathname, and to
163 * delete leading "../" and "./".
165 * Revision 3.2 82/11/12 14:29:40 wft
166 * changed pairfilenames() to handle file.sfx,v; also deleted checkpathnosfx(),
167 * checksuffix(), checkfullpath(). Semaphore name generation updated.
168 * mktempfile() now checks for nil path; freefilename initialized properly.
169 * Added Suffix .h to InitAdmin. Added testprogram PAIRTEST.
170 * Moved rmsema, trysema, trydiraccess, getfullRCSname from rcsutil.c to here.
172 * Revision 3.1 82/10/18 14:51:28 wft
173 * InitAdmin() now initializes StrictLocks=STRICT_LOCKING (def. in rcsbase.h).
174 * renamed checkpath() to checkfullpath().
180 libId(fnmsId, "$FreeBSD$")
182 static char const *bindex P((char const*,int));
183 static int fin2open P((char const*, size_t, char const*, size_t, char const*, size_t, RILE*(*)P((struct buf*,struct stat*,int)), int));
184 static int finopen P((RILE*(*)P((struct buf*,struct stat*,int)), int));
185 static int suffix_matches P((char const*,char const*));
186 static size_t dir_useful_len P((char const*));
187 static size_t suffixlen P((char const*));
188 static void InitAdmin P((void));
195 char const *suffixes;
197 static char const rcsdir[] = "RCS";
198 #define rcslen (sizeof(rcsdir)-1)
200 static struct buf RCSbuf, RCSb;
204 /* Temp names to be unlinked when done, if they are not 0. */
205 #define TEMPNAMES 5 /* must be at least DIRTEMPNAMES (see rcsedit.c) */
206 static char *volatile tpnames[TEMPNAMES];
210 char const *suffix, *comlead;
214 * This table is present only for backwards compatibility.
215 * Normally we ignore this table, and use the prefix of the `$Log' line instead.
217 static struct compair const comtable[] = {
218 { "a" , "-- " }, /* Ada */
222 { "asm" , ";; " }, /* assembler (MS-DOS) */
223 { "bat" , ":: " }, /* batch (MS-DOS) */
224 { "body", "-- " }, /* Ada */
225 { "c" , " * " }, /* C */
226 { "c++" , "// " }, /* C++ in all its infinite guises */
230 { "cl" , ";;; "}, /* Common Lisp */
231 { "cmd" , ":: " }, /* command (OS/2) */
232 { "cmf" , "c " }, /* CM Fortran */
233 { "cs" , " * " }, /* C* */
234 { "el" , "; " }, /* Emacs Lisp */
235 { "f" , "c " }, /* Fortran */
237 { "h" , " * " }, /* C-header */
238 { "hpp" , "// " }, /* C++ header */
240 { "l" , " * " }, /* lex (NOTE: franzlisp disagrees) */
241 { "lisp", ";;; "}, /* Lucid Lisp */
242 { "lsp" , ";; " }, /* Microsoft Lisp */
243 { "m" , "// " }, /* Objective C */
244 { "mac" , ";; " }, /* macro (DEC-10, MS-DOS, PDP-11, VMS, etc) */
245 { "me" , ".\\\" "}, /* troff -me */
246 { "ml" , "; " }, /* mocklisp */
247 { "mm" , ".\\\" "}, /* troff -mm */
248 { "ms" , ".\\\" "}, /* troff -ms */
249 { "p" , " * " }, /* Pascal */
251 { "ps" , "% " }, /* PostScript */
252 { "spec", "-- " }, /* Ada */
253 { "sty" , "% " }, /* LaTeX style */
254 { "tex" , "% " }, /* TeX */
255 { "y" , " * " }, /* yacc */
256 { 0 , "# " } /* default for unknown suffix; must be last */
260 static char const *tmp P((void));
263 /* Yield the name of the tmp directory. */
265 static char const *s;
267 && !(s = cgetenv("TMPDIR")) /* Unix tradition */
268 && !(s = cgetenv("TMP")) /* DOS tradition */
269 && !(s = cgetenv("TEMP")) /* another DOS tradition */
279 /* Create a unique pathname using n and the process id and store it
280 * into the nth slot in tpnames.
281 * Because of storage in tpnames, tempunlink() can unlink the file later.
282 * Return a pointer to the pathname created.
286 char const *t = tpnames[n];
297 char const *tp = tmp();
298 size_t tplen = dir_useful_len(tp);
299 p = testalloc(tplen + 10);
300 VOID sprintf(p, "%.*s%cT%cXXXXXX", (int)tplen, tp, SLASH, '0'+n);
303 faterror("can't make temporary pathname `%.*s%cT%cXXXXXX'",
304 (int)tplen, tp, SLASH, '0'+n
308 static char tpnamebuf[TEMPNAMES][L_tmpnam];
310 if (!tmpnam(p) || !*p)
312 faterror("can't make temporary pathname `%s...'",P_tmpdir);
314 faterror("can't make temporary pathname");
325 /* Clean up maketemp() files. May be invoked by signal handler.
331 for (i = TEMPNAMES; 0 <= --i; )
332 if ((p = tpnames[i])) {
335 * We would tfree(p) here,
336 * but this might dump core if we're handing a signal.
337 * We're about to exit anyway, so we won't bother.
346 register char const *sp;
348 /* Function: Finds the last occurrence of character c in string sp
349 * and returns a pointer to the character just beyond it. If the
350 * character doesn't occur in the string, sp is returned.
353 register char const *r;
356 if (*sp++ == c) r=sp;
364 suffix_matches(suffix, pattern)
365 register char const *suffix, *pattern;
371 switch (*suffix++ - (c = *pattern++)) {
378 if (ctab[c] == Letter)
389 /* function: initializes an admin node */
391 register char const *Suffix;
394 Head=0; Dbranch=0; AccessList=0; Symbols=0; Locks=0;
395 StrictLocks=STRICT_LOCKING;
397 /* guess the comment leader from the suffix*/
398 Suffix = bindex(workname, '.');
399 if (Suffix==workname) Suffix= ""; /* empty suffix; will get default*/
400 for (i=0; !suffix_matches(Suffix,comtable[i].suffix); i++)
402 Comment.string = comtable[i].comlead;
403 Comment.size = strlen(comtable[i].comlead);
404 Expand = KEYVAL_EXPAND;
406 Lexinit(); /* note: if !finptr, reads nothing; only initializes */
413 register struct buf *b;
415 /* Ensure *B is a name buffer of at least SIZE bytes.
416 * *B's old contents can be freed; *B's new contents are undefined.
419 if (b->size < size) {
423 b->size = sizeof(malloc_type);
424 while (b->size < size)
426 b->string = tnalloc(char, b->size);
432 register struct buf *b;
434 /* like bufalloc, except *B's old contents, if any, are preserved */
436 if (b->size < size) {
440 while ((b->size <<= 1) < size)
442 b->string = trealloc(char, b->string, b->size);
450 /* Free an auto buffer at block exit. */
461 * Free the buffer B with used size S.
462 * Yield a cbuf with identical contents.
463 * The cbuf will be reclaimed when this input file is finished.
469 cb.string = fremember(trealloc(char, b->string, s));
471 bufautoend(b); /* not really auto */
479 register struct buf *b;
481 /* Make *B larger. Set *ALIM to its new limit, and yield the relocated value
486 bufrealloc(b, s + 1);
487 *alim = b->string + b->size;
488 return b->string + s;
495 /* Concatenate S to B's end. */
497 size_t blen = b->string ? strlen(b->string) : 0;
498 bufrealloc(b, blen+strlen(s)+1);
499 VOID strcpy(b->string+blen, s);
508 bufalloc(b, strlen(s)+1);
509 VOID strcpy(b->string, s);
516 /* Yield the address of the base filename of the pathname P. */
518 register char const *b = p, *q = p;
521 case SLASHes: b = q; break;
530 /* Yield the length of X, an RCS pathname suffix. */
532 register char const *p;
537 case 0: case SLASHes:
549 /* Yield the suffix of NAME if it is an RCS pathname, 0 otherwise. */
551 char const *x, *p, *nz;
558 if ((xl = suffixlen(x))) {
559 if (xl <= nl && memcmp(p = nz-xl, x, xl) == 0)
562 for (p = name; p < nz - rcslen; p++)
565 && (p==name || isSLASH(p[-1]))
566 && memcmp(p, rcsdir, rcslen) == 0
575 rcsreadopen(RCSpath, status, mustread)
579 /* Open RCSPATH for reading and yield its FILE* descriptor.
580 * If successful, set *STATUS to its status.
581 * Pass this routine to pairnames() for read-only access to the file. */
583 return Iopen(RCSpath->string, FOPEN_RB, status);
587 finopen(rcsopen, mustread)
588 RILE *(*rcsopen)P((struct buf*,struct stat*,int));
591 * Use RCSOPEN to open an RCS file; MUSTREAD is set if the file must be read.
592 * Set finptr to the result and yield true if successful.
593 * RCSb holds the file's name.
594 * Set RCSbuf to the best RCS name found so far, and RCSerrno to its errno.
595 * Yield true if successful or if an unusual failure.
598 int interesting, preferold;
601 * We prefer an old name to that of a nonexisting new RCS file,
602 * unless we tried locking the old name and failed.
604 preferold = RCSbuf.string[0] && (mustread||0<=fdlock);
606 finptr = (*rcsopen)(&RCSb, &RCSstat, mustread);
607 interesting = finptr || errno!=ENOENT;
608 if (interesting || !preferold) {
609 /* Use the new name. */
611 bufscpy(&RCSbuf, RCSb.string);
617 fin2open(d, dlen, base, baselen, x, xlen, rcsopen, mustread)
618 char const *d, *base, *x;
619 size_t dlen, baselen, xlen;
620 RILE *(*rcsopen)P((struct buf*,struct stat*,int));
623 * D is a directory name with length DLEN (including trailing slash).
624 * BASE is a filename with length BASELEN.
625 * X is an RCS pathname suffix with length XLEN.
626 * Use RCSOPEN to open an RCS file; MUSTREAD is set if the file must be read.
627 * Yield true if successful.
628 * Try dRCS/basex first; if that fails and x is nonempty, try dbasex.
629 * Put these potential names in RCSb.
630 * Set RCSbuf to the best RCS name found so far, and RCSerrno to its errno.
631 * Yield true if successful or if an unusual failure.
636 bufalloc(&RCSb, dlen + rcslen + 1 + baselen + xlen + 1);
638 /* Try dRCS/basex. */
639 VOID memcpy(p = RCSb.string, d, dlen);
640 VOID memcpy(p += dlen, rcsdir, rcslen);
643 VOID memcpy(p, base, baselen);
644 VOID memcpy(p += baselen, x, xlen);
647 if (finopen(rcsopen, mustread))
651 /* Start from scratch, because finopen() may have changed RCSb. */
652 VOID memcpy(p = RCSb.string, d, dlen);
653 VOID memcpy(p += dlen, base, baselen);
654 VOID memcpy(p += baselen, x, xlen);
657 return finopen(rcsopen, mustread);
661 pairnames(argc, argv, rcsopen, mustread, quiet)
664 RILE *(*rcsopen)P((struct buf*,struct stat*,int));
667 * Pair the pathnames pointed to by argv; argc indicates
668 * how many there are.
669 * Place a pointer to the RCS pathname into RCSname,
670 * and a pointer to the pathname of the working file into workname.
671 * If both are given, and workstdout
672 * is set, a warning is printed.
674 * If the RCS file exists, places its status into RCSstat.
676 * If the RCS file exists, it is RCSOPENed for reading, the file pointer
677 * is placed into finptr, and the admin-node is read in; returns 1.
678 * If the RCS file does not exist and MUSTREAD,
679 * print an error unless QUIET and return 0.
680 * Otherwise, initialize the admin node and return -1.
682 * 0 is returned on all errors, e.g. files that are not regular files.
685 static struct buf tempbuf;
687 register char *p, *arg, *RCS1;
688 char const *base, *RCSbase, *x;
690 size_t arglen, dlen, baselen, xlen;
694 if (!(arg = *argv)) return 0; /* already paired pathname */
696 error("%s option is ignored after pathnames", arg);
700 base = basefilename(arg);
703 /* first check suffix to see whether it is an RCS file or not */
704 if ((x = rcssuffix(arg)))
706 /* RCS pathname given */
712 !rcssuffix(workname = p = argv[1]) &&
713 baselen <= (arglen = strlen(p)) &&
714 ((p+=arglen-baselen) == workname || isSLASH(p[-1])) &&
715 memcmp(base, p, baselen) == 0
720 bufscpy(&tempbuf, base);
721 workname = p = tempbuf.string;
725 /* working file given; now try to find RCS file */
727 baselen = strlen(base);
728 /* Derive RCS pathname. */
731 (x = rcssuffix(RCS1 = argv[1])) &&
732 baselen <= x - RCS1 &&
733 ((RCSbase=x-baselen)==RCS1 || isSLASH(RCSbase[-1])) &&
734 memcmp(base, RCSbase, baselen) == 0
741 /* Now we have a (tentative) RCS pathname in RCS1 and workname. */
742 /* Second, try to find the right RCS file */
744 /* a path for RCSfile is given; single RCS file to look for */
745 bufscpy(&RCSbuf, RCS1);
746 finptr = (*rcsopen)(&RCSbuf, &RCSstat, mustread);
749 bufscpy(&RCSbuf, "");
751 /* RCS filename was given without path. */
752 VOID fin2open(arg, (size_t)0, RCSbase, baselen,
753 x, strlen(x), rcsopen, mustread
756 /* No RCS pathname was given. */
757 /* Try each suffix in turn. */
760 while (! fin2open(arg, dlen, base, baselen,
761 x, xlen=suffixlen(x), rcsopen, mustread
769 RCSname = p = RCSbuf.string;
771 if (!S_ISREG(RCSstat.st_mode)) {
772 error("%s isn't a regular file -- ignored", p);
775 Lexinit(); getadmin();
777 if (RCSerrno!=ENOENT || mustread || fdlock<0) {
778 if (RCSerrno == EEXIST)
779 error("RCS file %s is in use", p);
780 else if (!quiet || RCSerrno!=ENOENT)
781 enerror(RCSerrno, p);
787 if (paired && workstdout)
788 workwarn("Working file ignored due to -p option");
791 return finptr ? 1 : -1;
798 * Return a pointer to the full pathname of the RCS file.
799 * Remove leading `./'.
802 if (ROOTPATH(RCSname)) {
805 static struct buf rcsbuf;
806 # if needs_getabsname
807 bufalloc(&rcsbuf, SIZEABLE_PATH + 1);
808 while (getabsname(RCSname, rcsbuf.string, rcsbuf.size) != 0)
810 bufalloc(&rcsbuf, rcsbuf.size<<1);
812 efaterror("getabsname");
814 static char const *wdptr;
815 static struct buf wdbuf;
818 register char const *r;
819 register size_t dlen;
821 register char const *wd;
824 /* Get working directory for the first time. */
825 char *PWD = cgetenv("PWD");
826 struct stat PWDstat, dotstat;
830 stat(PWD, &PWDstat) == 0 &&
831 stat(".", &dotstat) == 0 &&
832 same_file(PWDstat, dotstat, 1)
834 bufalloc(&wdbuf, SIZEABLE_PATH + 1);
835 # if has_getcwd || !has_getwd
836 while (!(d = getcwd(wdbuf.string, wdbuf.size)))
838 bufalloc(&wdbuf, wdbuf.size<<1);
844 d = getwd(wdbuf.string);
845 if (!d && !(d = PWD))
849 wdlen = dir_useful_len(d);
854 * Remove leading `./'s from RCSname.
855 * Do not try to handle `../', since removing it may yield
856 * the wrong answer in the presence of symbolic links.
858 for (r = RCSname; r[0]=='.' && isSLASH(r[1]); r += 2)
859 /* `.////' is equivalent to `./'. */
860 while (isSLASH(r[2]))
862 /* Build full pathname. */
864 bufalloc(&rcsbuf, dlen + strlen(r) + 2);
866 VOID memcpy(d, wd, dlen);
871 return rcsbuf.string;
875 /* Derived from code from the XFree86 project */
878 /* Function: returns a pointer to the path name of the RCS file with the
879 * CVSROOT part stripped off, and with 'Attic/' stripped off (if present).
883 #define ATTICDIR "/Attic"
885 char const *namebuf = getfullRCSname();
886 char *cvsroot = cgetenv("CVSROOT");
889 int alen = strlen(ATTICDIR);
891 if ((c = strrchr(namebuf, '/')) != NULL) {
892 if (namebuf - c >= alen) {
893 if (!strncmp(c - alen, ATTICDIR, alen)) {
907 cvsrootlen = strlen(cvsroot);
908 if (!strncmp(namebuf, cvsroot, cvsrootlen) &&
909 namebuf[cvsrootlen] == '/')
910 return(namebuf + cvsrootlen + 1);
920 * D names a directory; yield the number of characters of D's useful part.
921 * To create a file in D, append a SLASH and a file name to D's useful part.
922 * Ignore trailing slashes if possible; not only are they ugly,
923 * but some non-Posix systems misbehave unless the slashes are omitted.
926 # ifndef SLASHSLASH_is_SLASH
927 # define SLASHSLASH_is_SLASH 0
929 size_t dlen = strlen(d);
930 if (!SLASHSLASH_is_SLASH && dlen==2 && isSLASH(d[0]) && isSLASH(d[1]))
933 while (dlen && isSLASH(d[dlen-1]))
953 #if !has_getcwd && !has_getwd
960 static char const usrbinpwd[] = "/usr/bin/pwd";
961 # define binpwd (usrbinpwd+4)
965 register char *p, *lim;
966 int closeerrno, closeerror, e, fd[2], readerror, toolong, wstatus;
975 # if bad_wait_if_SIGCHLD_ignored
977 # define SIGCHLD SIGCLD
979 VOID signal(SIGCHLD, SIG_DFL);
981 if (!(child = vfork())) {
984 (fd[1] == STDOUT_FILENO ||
986 (VOID close(STDOUT_FILENO),
987 fcntl(fd[1], F_DUPFD, STDOUT_FILENO))
989 dup2(fd[1], STDOUT_FILENO)
995 VOID close(STDERR_FILENO);
996 VOID execl(binpwd, binpwd, (char *)0);
997 VOID execl(usrbinpwd, usrbinpwd, (char *)0);
1002 closeerror = close(fd[1]);
1005 readerror = toolong = wstatus = 0;
1008 fp = fdopen(fd[0], "r");
1012 for (p = path; ; *p++ = c) {
1013 if ((c=getc(fp)) < 0) {
1029 if (waitpid(child, &wstatus, 0) < 0)
1035 if ((w = wait(&wstatus)) < 0) {
1039 } while (w != child);
1048 if (fclose(fp) != 0)
1062 if (wstatus || p == path || *--p != '\n') {
1073 /* test program for pairnames() and getfullRCSname() */
1075 char const cmdid[] = "pair";
1078 int argc; char *argv[];
1082 quietflag = initflag = false;
1084 while(--argc, ++argv, argc>=1 && ((*argv)[0] == '-')) {
1085 switch ((*argv)[1]) {
1087 case 'p': workstdout = stdout;
1089 case 'i': initflag=true;
1091 case 'q': quietflag=true;
1093 default: error("unknown option: %s", *argv);
1099 RCSname = workname = 0;
1100 result = pairnames(argc,argv,rcsreadopen,!initflag,quietflag);
1102 diagnose("RCS pathname: %s; working pathname: %s\nFull RCS pathname: %s\n",
1103 RCSname, workname, getfullRCSname()
1107 case 0: continue; /* already paired file */
1109 case 1: if (initflag) {
1110 rcserror("already exists");
1112 diagnose("RCS file %s exists\n", RCSname);
1117 case -1:diagnose("RCS file doesn't exist\n");
1121 } while (++argv, --argc>=1);
1130 _exit(EXIT_FAILURE);