/* RCS stream editor */ /****************************************************************************** * edits the input file according to a * script from stdin, generated by diff -n * performs keyword expansion ****************************************************************************** */ /* Copyright 1982, 1988, 1989 Walter Tichy Copyright 1990, 1991, 1992, 1993, 1994 Paul Eggert Distributed under license by the Free Software Foundation, Inc. This file is part of RCS. RCS is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. RCS is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with RCS; see the file COPYING. If not, write to the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. Report problems and direct all questions to: rcs-bugs@cs.purdue.edu */ /* * $Log: rcsedit.c,v $ * Revision 1.2 1996/04/19 12:40:07 mickey * -L option added to support LOCALID behaviour. * maybe set up in RCSINIT environment variable. * * Revision 1.1.1.1 1995/10/18 08:41:01 deraadt * initial import of NetBSD tree * * Revision 1.5 1995/02/24 02:25:01 mycroft * RCS 5.6.7.4 * * Revision 5.17 1994/03/20 04:52:58 eggert * Normally calculate the $Log prefix from context, not from RCS file. * Move setmtime here from rcsutil.c. Add ORCSerror. Remove lint. * * Revision 5.16 1993/11/03 17:42:27 eggert * Add -z. Add Name keyword. If bad_unlink, ignore errno when unlink fails. * Escape white space, $, and \ in keyword string file names. * Don't output 2 spaces between date and time after Log. * * Revision 5.15 1992/07/28 16:12:44 eggert * Some hosts have readlink but not ELOOP. Avoid `unsigned'. * Preserve dates more systematically. Statement macro names now end in _. * * Revision 5.14 1992/02/17 23:02:24 eggert * Add -T support. * * Revision 5.13 1992/01/24 18:44:19 eggert * Add support for bad_chmod_close, bad_creat0. * * Revision 5.12 1992/01/06 02:42:34 eggert * Add setmode parameter to chnamemod. addsymbol now reports changes. * while (E) ; -> while (E) continue; * * Revision 5.11 1991/11/03 01:11:44 eggert * Move the warning about link breaking to where they're actually being broken. * * Revision 5.10 1991/10/07 17:32:46 eggert * Support piece tables even if !has_mmap. Fix rare NFS bugs. * * Revision 5.9 1991/09/17 19:07:40 eggert * SGI readlink() yields ENXIO, not EINVAL, for nonlinks. * * Revision 5.8 1991/08/19 03:13:55 eggert * Add piece tables, NFS bug workarounds. Catch odd filenames. Tune. * * Revision 5.7 1991/04/21 11:58:21 eggert * Fix errno bugs. Add -x, RCSINIT, MS-DOS support. * * Revision 5.6 1991/02/25 07:12:40 eggert * Fix setuid bug. Support new link behavior. Work around broken "w+" fopen. * * Revision 5.5 1990/12/30 05:07:35 eggert * Fix report of busy RCS files when !defined(O_CREAT) | !defined(O_EXCL). * * Revision 5.4 1990/11/01 05:03:40 eggert * Permit arbitrary data in comment leaders. * * Revision 5.3 1990/09/11 02:41:13 eggert * Tune expandline(). * * Revision 5.2 1990/09/04 08:02:21 eggert * Count RCS lines better. Improve incomplete line handling. * * Revision 5.1 1990/08/29 07:13:56 eggert * Add -kkvl. * Fix bug when getting revisions to files ending in incomplete lines. * Fix bug in comment leader expansion. * * Revision 5.0 1990/08/22 08:12:47 eggert * Don't require final newline. * Don't append "checked in with -k by " to logs, * so that checking in a program with -k doesn't change it. * Don't generate trailing white space for empty comment leader. * Remove compile-time limits; use malloc instead. Add -k, -V. * Permit dates past 1999/12/31. Make lock and temp files faster and safer. * Ansify and Posixate. Check diff's output. * * Revision 4.8 89/05/01 15:12:35 narten * changed copyright header to reflect current distribution rules * * Revision 4.7 88/11/08 13:54:14 narten * misplaced semicolon caused infinite loop * * Revision 4.6 88/08/09 19:12:45 eggert * Shrink stdio code size; allow cc -R. * * Revision 4.5 87/12/18 11:38:46 narten * Changes from the 43. version. Don't know the significance of the * first change involving "rewind". Also, additional "lint" cleanup. * (Guy Harris) * * Revision 4.4 87/10/18 10:32:21 narten * Updating version numbers. Changes relative to version 1.1 actually * relative to 4.1 * * Revision 1.4 87/09/24 13:59:29 narten * Sources now pass through lint (if you ignore printf/sprintf/fprintf * warnings) * * Revision 1.3 87/09/15 16:39:39 shepler * added an initializatin of the variables editline and linecorr * this will be done each time a file is processed. * (there was an obscure bug where if co was used to retrieve multiple files * it would dump) * fix attributed to Roy Morris @FileNet Corp ...!felix!roy * * Revision 1.2 87/03/27 14:22:17 jenkins * Port to suns * * Revision 4.1 83/05/12 13:10:30 wft * Added new markers Id and RCSfile; added locker to Header and Id. * Overhauled expandline completely() (problem with $01234567890123456789@). * Moved trymatch() and marker table to rcskeys.c. * * Revision 3.7 83/05/12 13:04:39 wft * Added retry to expandline to resume after failed match which ended in $. * Fixed truncation problem for $19chars followed by@@. * Log no longer expands full path of RCS file. * * Revision 3.6 83/05/11 16:06:30 wft * added retry to expandline to resume after failed match which ended in $. * Fixed truncation problem for $19chars followed by@@. * * Revision 3.5 82/12/04 13:20:56 wft * Added expansion of keyword Locker. * * Revision 3.4 82/12/03 12:26:54 wft * Added line number correction in case editing does not start at the * beginning of the file. * Changed keyword expansion to always print a space before closing KDELIM; * Expansion for Header shortened. * * Revision 3.3 82/11/14 14:49:30 wft * removed Suffix from keyword expansion. Replaced fclose with ffclose. * keyreplace() gets log message from delta, not from curlogmsg. * fixed expression overflow in while(c=putc(GETC.... * checked nil printing. * * Revision 3.2 82/10/18 21:13:39 wft * I added checks for write errors during the co process, and renamed * expandstring() to xpandstring(). * * Revision 3.1 82/10/13 15:52:55 wft * changed type of result of getc() from char to int. * made keyword expansion loop in expandline() portable to machines * without sign-extension. */ #include "rcsbase.h" libId(editId, "$Id: rcsedit.c,v 1.2 1996/04/19 12:40:07 mickey Exp $") static void editEndsPrematurely P((void)) exiting; static void editLineNumberOverflow P((void)) exiting; static void escape_string P((FILE*,char const*)); static void keyreplace P((enum markers,struct hshentry const*,int,RILE*,FILE*,int)); FILE *fcopy; /* result file descriptor */ char const *resultname; /* result pathname */ int locker_expansion; /* should the locker name be appended to Id val? */ #if !large_memory static RILE *fedit; /* edit file descriptor */ static char const *editname; /* edit pathname */ #endif static long editline; /* edit line counter; #lines before cursor */ static long linecorr; /* #adds - #deletes in each edit run. */ /*used to correct editline in case file is not rewound after */ /* applying one delta */ /* indexes into dirtpname */ #define lockdirtp_index 0 #define newRCSdirtp_index bad_creat0 #define newworkdirtp_index (newRCSdirtp_index+1) #define DIRTEMPNAMES (newworkdirtp_index + 1) enum maker {notmade, real, effective}; struct buf dirtpname[DIRTEMPNAMES]; /* unlink these when done */ static enum maker volatile dirtpmaker[DIRTEMPNAMES]; /* if these are set */ #define lockname (dirtpname[lockdirtp_index].string) #define newRCSname (dirtpname[newRCSdirtp_index].string) #if has_NFS || bad_unlink int un_link(s) char const *s; /* * Remove S, even if it is unwritable. * Ignore unlink() ENOENT failures; NFS generates bogus ones. */ { # if bad_unlink if (unlink(s) == 0) return 0; else { int e = errno; /* * Forge ahead even if errno == ENOENT; some completely * brain-damaged hosts (e.g. PCTCP 2.2) yield ENOENT * even for existing unwritable files. */ if (chmod(s, S_IWUSR) != 0) { errno = e; return -1; } } # endif # if has_NFS return unlink(s)==0 || errno==ENOENT ? 0 : -1; # else return unlink(s); # endif } #endif #if !has_rename # if !has_NFS # define do_link(s,t) link(s,t) # else static int do_link P((char const*,char const*)); static int do_link(s, t) char const *s, *t; /* Link S to T, ignoring bogus EEXIST problems due to NFS failures. */ { struct stat sb, tb; if (link(s,t) == 0) return 0; if (errno != EEXIST) return -1; if ( stat(s, &sb) == 0 && stat(t, &tb) == 0 && same_file(sb, tb, 0) ) return 0; errno = EEXIST; return -1; } # endif #endif static void editEndsPrematurely() { fatserror("edit script ends prematurely"); } static void editLineNumberOverflow() { fatserror("edit script refers to line past end of file"); } #if large_memory #if has_memmove # define movelines(s1, s2, n) VOID memmove(s1, s2, (n)*sizeof(Iptr_type)) #else static void movelines P((Iptr_type*,Iptr_type const*,long)); static void movelines(s1, s2, n) register Iptr_type *s1; register Iptr_type const *s2; register long n; { if (s1 < s2) do { *s1++ = *s2++; } while (--n); else { s1 += n; s2 += n; do { *--s1 = *--s2; } while (--n); } } #endif static void deletelines P((long,long)); static void finisheditline P((RILE*,FILE*,Iptr_type,struct hshentry const*)); static void insertline P((long,Iptr_type)); static void snapshotline P((FILE*,Iptr_type)); /* * `line' contains pointers to the lines in the currently `edited' file. * It is a 0-origin array that represents linelim-gapsize lines. * line[0 .. gap-1] and line[gap+gapsize .. linelim-1] hold pointers to lines. * line[gap .. gap+gapsize-1] contains garbage. * * Any @s in lines are duplicated. * Lines are terminated by \n, or (for a last partial line only) by single @. */ static Iptr_type *line; static size_t gap, gapsize, linelim; static void insertline(n, l) long n; Iptr_type l; /* Before line N, insert line L. N is 0-origin. */ { if (linelim-gapsize < n) editLineNumberOverflow(); if (!gapsize) line = !linelim ? tnalloc(Iptr_type, linelim = gapsize = 1024) : ( gap = gapsize = linelim, trealloc(Iptr_type, line, linelim <<= 1) ); if (n < gap) movelines(line+n+gapsize, line+n, gap-n); else if (gap < n) movelines(line+gap, line+gap+gapsize, n-gap); line[n] = l; gap = n + 1; gapsize--; } static void deletelines(n, nlines) long n, nlines; /* Delete lines N through N+NLINES-1. N is 0-origin. */ { long l = n + nlines; if (linelim-gapsize < l || l < n) editLineNumberOverflow(); if (l < gap) movelines(line+l+gapsize, line+l, gap-l); else if (gap < n) movelines(line+gap, line+gap+gapsize, n-gap); gap = n; gapsize += nlines; } static void snapshotline(f, l) register FILE *f; register Iptr_type l; { register int c; do { if ((c = *l++) == SDELIM && *l++ != SDELIM) return; aputc_(c, f) } while (c != '\n'); } void snapshotedit(f) FILE *f; /* Copy the current state of the edits to F. */ { register Iptr_type *p, *lim, *l=line; for (p=l, lim=l+gap; pdate; RCSv = RCSversion; if (exp != VAL_EXPAND) aprintf(out, "%c%s", KDELIM, sp); if (exp != KEY_EXPAND) { if (exp != VAL_EXPAND) aprintf(out, "%c%c", VDELIM, marker==Log && RCSvauthor, out); break; case Date: aputs(date2str(date,datebuf), out); break; case Header: case Id: case LocalId: escape_string(out, marker!=Header || RCSvnum, date2str(date, datebuf), delta->author, RCSv==VERSION(3) && delta->lockedby ? "Locked" : delta->state ); if (delta->lockedby) if (VERSION(5) <= RCSv) { if (locker_expansion || exp==KEYVALLOCK_EXPAND) aprintf(out, " %s", delta->lockedby); } else if (RCSv == VERSION(4)) aprintf(out, " Locker: %s", delta->lockedby); break; case Locker: if (delta->lockedby) if ( locker_expansion || exp == KEYVALLOCK_EXPAND || RCSv <= VERSION(4) ) aputs(delta->lockedby, out); break; case Log: case RCSfile: escape_string(out, basename(RCSname)); break; case Name: if (delta->name) aputs(delta->name, out); break; case Revision: aputs(delta->num, out); break; case Source: escape_string(out, getfullRCSname()); break; case State: aputs(delta->state, out); break; default: break; } if (exp != VAL_EXPAND) afputc(' ', out); } if (exp != VAL_EXPAND) afputc(KDELIM, out); if (marker == Log && dolog) { struct buf leader; sp = delta->log.string; ls = delta->log.size; if (sizeof(ciklog)-1<=ls && !memcmp(sp,ciklog,sizeof(ciklog)-1)) return; bufautobegin(&leader); if (RCSversion < VERSION(5)) { cp = Comment.string; cs = Comment.size; } else { int kdelim_found = 0; declarecache; setupcache(infile); cache(infile); c = 0; /* Pacify `gcc -Wall'. */ /* * Back up to the start of the current input line, * setting CS to the number of characters before `$Log'. */ cs = 0; for (;;) { cacheunget_(infile, c, goto done_backing_up;) if (c == '\n') break; if (c == SDELIM && delimstuffed) { cacheunget_(infile, c, break;) if (c != SDELIM) { cacheget_(c) break; } } cs += kdelim_found; kdelim_found |= c==KDELIM; } cacheget_(c) done_backing_up:; /* Copy characters before `$Log' into LEADER. */ bufalloc(&leader, cs); cp = leader.string; for (cw = 0; cw < cs; cw++) { leader.string[cw] = c; if (c == SDELIM && delimstuffed) cacheget_(c) cacheget_(c) } /* Skip `$Log ... $' string. */ do { cacheget_(c) } while (c != KDELIM); uncache(infile); } afputc('\n', out); awrite(cp, cs, out); sp1 = date2str(date, datebuf); if (VERSION(5) <= RCSv) { aprintf(out, "Revision %s %s %s", delta->num, sp1, delta->author ); } else { /* oddity: 2 spaces between date and time, not 1 as usual */ sp1 = strchr(sp1, ' '); aprintf(out, "Revision %s %.*s %s %s", delta->num, (int)(sp1-datebuf), datebuf, sp1, delta->author ); } /* Do not include state: it may change and is not updated. */ cw = cs; if (VERSION(5) <= RCSv) for (; cw && (cp[cw-1]==' ' || cp[cw-1]=='\t'); --cw) continue; for (;;) { afputc('\n', out); awrite(cp, cw, out); if (!ls) break; --ls; c = *sp++; if (c != '\n') { awrite(cp+cw, cs-cw, out); do { afputc(c,out); if (!ls) break; --ls; c = *sp++; } while (c != '\n'); } } bufautoend(&leader); } } #if has_readlink static int resolve_symlink P((struct buf*)); static int resolve_symlink(L) struct buf *L; /* * If L is a symbolic link, resolve it to the name that it points to. * If unsuccessful, set errno and yield -1. * If it points to an existing file, yield 1. * Otherwise, set errno=ENOENT and yield 0. */ { char *b, a[SIZEABLE_PATH]; int e; size_t s; ssize_t r; struct buf bigbuf; int linkcount = MAXSYMLINKS; b = a; s = sizeof(a); bufautobegin(&bigbuf); while ((r = readlink(L->string,b,s)) != -1) if (r == s) { bufalloc(&bigbuf, s<<1); b = bigbuf.string; s = bigbuf.size; } else if (!linkcount--) { # ifndef ELOOP /* * Some pedantic Posix 1003.1-1990 hosts have readlink * but not ELOOP. Approximate ELOOP with EMLINK. */ # define ELOOP EMLINK # endif errno = ELOOP; return -1; } else { /* Splice symbolic link into L. */ b[r] = '\0'; L->string[ROOTPATH(b) ? (size_t)0 : dirlen(L->string)] = '\0'; bufscat(L, b); } e = errno; bufautoend(&bigbuf); errno = e; switch (e) { case readlink_isreg_errno: return 1; case ENOENT: return 0; default: return -1; } } #endif RILE * rcswriteopen(RCSbuf, status, mustread) struct buf *RCSbuf; struct stat *status; int mustread; /* * Create the lock file corresponding to RCSBUF. * Then try to open RCSBUF for reading and yield its RILE* descriptor. * Put its status into *STATUS too. * MUSTREAD is true if the file must already exist, too. * If all goes well, discard any previously acquired locks, * and set fdlock to the file descriptor of the RCS lockfile. */ { register char *tp; register char const *sp, *RCSpath, *x; RILE *f; size_t l; int e, exists, fdesc, r, waslocked; struct buf *dirt; struct stat statbuf; waslocked = 0 <= fdlock; exists = # if has_readlink resolve_symlink(RCSbuf); # else stat(RCSbuf->string, &statbuf) == 0 ? 1 : errno==ENOENT ? 0 : -1; # endif if (exists < (mustread|waslocked)) /* * There's an unusual problem with the RCS file; * or the RCS file doesn't exist, * and we must read or we already have a lock elsewhere. */ return 0; RCSpath = RCSbuf->string; sp = basename(RCSpath); l = sp - RCSpath; dirt = &dirtpname[waslocked]; bufscpy(dirt, RCSpath); tp = dirt->string + l; x = rcssuffix(RCSpath); # if has_readlink if (!x) { error("symbolic link to non RCS file `%s'", RCSpath); errno = EINVAL; return 0; } # endif if (*sp == *x) { error("RCS pathname `%s' incompatible with suffix `%s'", sp, x); errno = EINVAL; return 0; } /* Create a lock filename that is a function of the RCS filename. */ if (*x) { /* * The suffix is nonempty. * The lock filename is the first char of of the suffix, * followed by the RCS filename with last char removed. E.g.: * foo,v RCS filename with suffix ,v * ,foo, lock filename */ *tp++ = *x; while (*sp) *tp++ = *sp++; *--tp = 0; } else { /* * The suffix is empty. * The lock filename is the RCS filename * with last char replaced by '_'. */ while ((*tp++ = *sp++)) continue; tp -= 2; if (*tp == '_') { error("RCS pathname `%s' ends with `%c'", RCSpath, *tp); errno = EINVAL; return 0; } *tp = '_'; } sp = dirt->string; f = 0; /* * good news: * open(f, O_CREAT|O_EXCL|O_TRUNC|..., OPEN_CREAT_READONLY) * is atomic according to Posix 1003.1-1990. * bad news: * NFS ignores O_EXCL and doesn't comply with Posix 1003.1-1990. * good news: * (O_TRUNC,OPEN_CREAT_READONLY) normally guarantees atomicity * even with NFS. * bad news: * If you're root, (O_TRUNC,OPEN_CREAT_READONLY) doesn't * guarantee atomicity. * good news: * Root-over-the-wire NFS access is rare for security reasons. * This bug has never been reported in practice with RCS. * So we don't worry about this bug. * * An even rarer NFS bug can occur when clients retry requests. * This can happen in the usual case of NFS over UDP. * Suppose client A releases a lock by renaming ",f," to "f,v" at * about the same time that client B obtains a lock by creating ",f,", * and suppose A's first rename request is delayed, so A reissues it. * The sequence of events might be: * A sends rename(",f,", "f,v") * B sends create(",f,") * A sends retry of rename(",f,", "f,v") * server receives, does, and acknowledges A's first rename() * A receives acknowledgment, and its RCS program exits * server receives, does, and acknowledges B's create() * server receives, does, and acknowledges A's retry of rename() * This not only wrongly deletes B's lock, it removes the RCS file! * Most NFS implementations have idempotency caches that usually prevent * this scenario, but such caches are finite and can be overrun. * This problem afflicts not only RCS, which uses open() and rename() * to get and release locks; it also afflicts the traditional * Unix method of using link() and unlink() to get and release locks, * and the less traditional method of using mkdir() and rmdir(). * There is no easy workaround. * Any new method based on lockf() seemingly would be incompatible with * the old methods; besides, lockf() is notoriously buggy under NFS. * Since this problem afflicts scads of Unix programs, but is so rare * that nobody seems to be worried about it, we won't worry either. */ # if !open_can_creat # define create(f) creat(f, OPEN_CREAT_READONLY) # else # define create(f) open(f, OPEN_O_BINARY|OPEN_O_LOCK|O_CREAT|O_EXCL|O_TRUNC|O_WRONLY, OPEN_CREAT_READONLY) # endif catchints(); ignoreints(); /* * Create a lock file for an RCS file. This should be atomic, i.e. * if two processes try it simultaneously, at most one should succeed. */ seteid(); fdesc = create(sp); e = errno; setrid(); if (fdesc < 0) { if (e == EACCES && stat(sp,&statbuf) == 0) /* The RCS file is busy. */ e = EEXIST; } else { dirtpmaker[0] = effective; e = ENOENT; if (exists) { f = Iopen(RCSpath, FOPEN_R, status); e = errno; if (f && waslocked) { /* Discard the previous lock in favor of this one. */ ORCSclose(); seteid(); r = un_link(lockname); e = errno; setrid(); if (r != 0) enfaterror(e, lockname); bufscpy(&dirtpname[lockdirtp_index], sp); } } fdlock = fdesc; } restoreints(); errno = e; return f; } void keepdirtemp(name) char const *name; /* Do not unlink name, either because it's not there any more, * or because it has already been unlinked. */ { register int i; for (i=DIRTEMPNAMES; 0<=--i; ) if (dirtpname[i].string == name) { dirtpmaker[i] = notmade; return; } faterror("keepdirtemp"); } char const * makedirtemp(isworkfile) int isworkfile; /* * Create a unique pathname and store it into dirtpname. * Because of storage in tpnames, dirtempunlink() can unlink the file later. * Return a pointer to the pathname created. * If ISWORKFILE is 1, put it into the working file's directory; * if 0, put the unique file in RCSfile's directory. */ { register char *tp, *np; register size_t dl; register struct buf *bn; register char const *name = isworkfile ? workname : RCSname; dl = dirlen(name); bn = &dirtpname[newRCSdirtp_index + isworkfile]; bufalloc(bn, # if has_mktemp dl + 9 # else strlen(name) + 3 # endif ); bufscpy(bn, name); np = tp = bn->string; tp += dl; *tp++ = '_'; *tp++ = '0'+isworkfile; catchints(); # if has_mktemp VOID strcpy(tp, "XXXXXX"); if (!mktemp(np) || !*np) faterror("can't make temporary pathname `%.*s_%cXXXXXX'", (int)dl, name, '0'+isworkfile ); # else /* * Posix 1003.1-1990 has no reliable way * to create a unique file in a named directory. * We fudge here. If the filename is abcde, * the temp filename is _Ncde where N is a digit. */ name += dl; if (*name) name++; if (*name) name++; VOID strcpy(tp, name); # endif dirtpmaker[newRCSdirtp_index + isworkfile] = real; return np; } void dirtempunlink() /* Clean up makedirtemp() files. May be invoked by signal handler. */ { register int i; enum maker m; for (i = DIRTEMPNAMES; 0 <= --i; ) if ((m = dirtpmaker[i]) != notmade) { if (m == effective) seteid(); VOID un_link(dirtpname[i].string); if (m == effective) setrid(); dirtpmaker[i] = notmade; } } int #if has_prototypes chnamemod( FILE **fromp, char const *from, char const *to, int set_mode, mode_t mode, time_t mtime ) /* The `#if has_prototypes' is needed because mode_t might promote to int. */ #else chnamemod(fromp, from, to, set_mode, mode, mtime) FILE **fromp; char const *from,*to; int set_mode; mode_t mode; time_t mtime; #endif /* * Rename a file (with stream pointer *FROMP) from FROM to TO. * FROM already exists. * If 0 < SET_MODE, change the mode to MODE, before renaming if possible. * If MTIME is not -1, change its mtime to MTIME, before renaming if possible. * Close and clear *FROMP before renaming it. * Unlink TO if it already exists. * Return -1 on error (setting errno), 0 otherwise. */ { # if bad_a_rename /* * This host is brain damaged. A race condition is possible * while the lock file is temporarily writable. * There doesn't seem to be a workaround. */ mode_t mode_while_renaming; if (set_mode <= 0) { struct stat st; if (stat(from, &st) != 0) return -1; mode = st.st_mode; } mode_while_renaming = mode|S_IWUSR; if (mode != mode_while_renaming) set_mode = 1; # else # define mode_while_renaming mode # endif # if has_fchmod if (0nextlock) if (strcmp(getcaller(), next->login) == 0) { if (found) { rcserror("multiple revisions locked by %s; please specify one", getcaller()); return 2; } found = trail; } if (!found) return 0; next = *found; *target = next->delta; if (delete) { next->delta->lockedby = 0; *found = next->nextlock; } return 1; } int addlock(delta, verbose) struct hshentry * delta; int verbose; /* * Add a lock held by caller to DELTA and yield 1 if successful. * Print an error message if verbose and yield -1 if no lock is added because * DELTA is locked by somebody other than caller. * Return 0 if the caller already holds the lock. */ { register struct lock *next; for (next = Locks; next; next = next->nextlock) if (cmpnum(delta->num, next->delta->num) == 0) if (strcmp(getcaller(), next->login) == 0) return 0; else { if (verbose) rcserror("Revision %s is already locked by %s.", delta->num, next->login ); return -1; } next = ftalloc(struct lock); delta->lockedby = next->login = getcaller(); next->delta = delta; next->nextlock = Locks; Locks = next; return 1; } int addsymbol(num, name, rebind) char const *num, *name; int rebind; /* * Associate with revision NUM the new symbolic NAME. * If NAME already exists and REBIND is set, associate NAME with NUM; * otherwise, print an error message and return false; * Return -1 if unsuccessful, 0 if no change, 1 if change. */ { register struct assoc *next; for (next = Symbols; next; next = next->nextassoc) if (strcmp(name, next->symbol) == 0) if (strcmp(next->num,num) == 0) return 0; else if (rebind) { next->num = num; return 1; } else { rcserror("symbolic name %s already bound to %s", name, next->num ); return -1; } next = ftalloc(struct assoc); next->symbol = name; next->num = num; next->nextassoc = Symbols; Symbols = next; return 1; } char const * getcaller() /* Get the caller's login name. */ { # if has_setuid return getusername(euid()!=ruid()); # else return getusername(false); # endif } int checkaccesslist() /* * Return true if caller is the superuser, the owner of the * file, the access list is empty, or caller is on the access list. * Otherwise, print an error message and return false. */ { register struct access const *next; if (!AccessList || myself(RCSstat.st_uid) || strcmp(getcaller(),"root")==0) return true; next = AccessList; do { if (strcmp(getcaller(), next->login) == 0) return true; } while ((next = next->nextaccess)); rcserror("user %s not on the access list", getcaller()); return false; } int dorewrite(lockflag, changed) int lockflag, changed; /* * Do nothing if LOCKFLAG is zero. * Prepare to rewrite an RCS file if CHANGED is positive. * Stop rewriting if CHANGED is zero, because there won't be any changes. * Fail if CHANGED is negative. * Return 0 on success, -1 on failure. */ { int r = 0, e; if (lockflag) if (changed) { if (changed < 0) return -1; putadmin(); puttree(Head, frewrite); aprintf(frewrite, "\n\n%s%c", Kdesc, nextc); foutptr = frewrite; } else { # if bad_creat0 int nr = !!frewrite, ne = 0; # endif ORCSclose(); seteid(); ignoreints(); # if bad_creat0 if (nr) { nr = un_link(newRCSname); ne = errno; keepdirtemp(newRCSname); } # endif r = un_link(lockname); e = errno; keepdirtemp(lockname); restoreints(); setrid(); if (r != 0) enerror(e, lockname); # if bad_creat0 if (nr != 0) { enerror(ne, newRCSname); r = -1; } # endif } return r; } int donerewrite(changed, newRCStime) int changed; time_t newRCStime; /* * Finish rewriting an RCS file if CHANGED is nonzero. * Set its mode if CHANGED is positive. * Set its modification time to NEWRCSTIME unless it is -1. * Return 0 on success, -1 on failure. */ { int r = 0, e = 0; # if bad_creat0 int lr, le; # endif if (changed && !nerror) { if (finptr) { fastcopy(finptr, frewrite); Izclose(&finptr); } if (1 < RCSstat.st_nlink) rcswarn("breaking hard link"); aflush(frewrite); seteid(); ignoreints(); r = chnamemod( &frewrite, newRCSname, RCSname, changed, RCSstat.st_mode & (mode_t)~(S_IWUSR|S_IWGRP|S_IWOTH), newRCStime ); e = errno; keepdirtemp(newRCSname); # if bad_creat0 lr = un_link(lockname); le = errno; keepdirtemp(lockname); # endif restoreints(); setrid(); if (r != 0) { enerror(e, RCSname); error("saved in %s", newRCSname); } # if bad_creat0 if (lr != 0) { enerror(le, lockname); r = -1; } # endif } return r; } void aflush(f) FILE *f; { if (fflush(f) != 0) Oerror(); } void ORCSclose() { if (0 <= fdlock) { if (close(fdlock) != 0) efaterror(lockname); fdlock = -1; } Ozclose(&frewrite); } void ORCSerror() /* * Like ORCSclose, except we are cleaning up after an interrupt or fatal error. * Do not report errors, since this may loop. This is needed only because * some brain-damaged hosts (e.g. OS/2) cannot unlink files that are open, and * some nearly-Posix hosts (e.g. NFS) work better if the files are closed first. * This isn't a completely reliable away to work around brain-damaged hosts, * because of the gap between actual file opening and setting frewrite etc., * but it's better than nothing. */ { if (0 <= fdlock) VOID close(fdlock); if (frewrite) /* Avoid fclose, since stdio may not be reentrant. */ VOID close(fileno(frewrite)); }