📄 rcsedit.c
字号:
* If all goes well, discard any previously acquired locks, * and set frewrite to the FILE* descriptor of the lock file, * which will eventually turn into the new RCS file. */{ register char *tp; register char const *sp, *RCSname, *x; RILE *f; size_t l; int e, exists, fdesc, previouslock, r; struct buf *dirt; struct stat statbuf; previouslock = frewrite != 0; exists =# if has_readlink resolve_symlink(RCSbuf);# else stat(RCSbuf->string, &statbuf) == 0 ? 1 : errno==ENOENT ? 0 : -1;# endif if (exists < (mustread|previouslock)) /* * 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; RCSname = RCSbuf->string; sp = basename(RCSname); l = sp - RCSname; dirt = &dirtfname[previouslock]; bufscpy(dirt, RCSname); tp = dirt->string + l; x = rcssuffix(RCSname);# if has_readlink if (!x) { error("symbolic link to non RCS filename `%s'", RCSname); errno = EINVAL; return 0; }# endif if (*sp == *x) { error("RCS filename `%s' incompatible with suffix `%s'", sp, x); errno = EINVAL; return 0; } /* Create a lock file whose name 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++)) ; tp -= 2; if (*tp == '_') { error("RCS filename `%s' ends with `%c'", RCSname, *tp); errno = EINVAL; return 0; } *tp = '_'; } sp = tp = dirt->string; f = 0; /* * good news: * open(f, O_CREAT|O_EXCL|O_TRUNC|O_WRONLY, 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,READONLY) normally guarantees atomicity even with NFS. * bad news: * If you're root, (O_TRUNC,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. * Suppose client A renames the lock file ",f," to "f,v" * at about the same time that client B creates ",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 programs that use the traditional * Unix method of using link() and unlink() to get and release locks, * as well as RCS's method of using open() and rename(). * There is no easy workaround for either link-unlink or open-rename. * 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. */# define READONLY (S_IRUSR|S_IRGRP|S_IROTH)# if !open_can_creat# define create(f) creat(f, READONLY)# else# define create(f) open(f, O_BINARY|O_CREAT|O_EXCL|O_TRUNC|O_WRONLY, 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(tp,&statbuf) == 0) /* The RCS file is busy. */ e = EEXIST; } else { dirtfmaker[0] = effective; e = ENOENT; if (exists) { f = Iopen(RCSname, FOPEN_R, status); e = errno; if (f && previouslock) { /* Discard the previous lock in favor of this one. */ Ozclose(&frewrite); seteid(); if ((r = un_link(newRCSfilename)) != 0) e = errno; setrid(); if (r != 0) enfaterror(e, newRCSfilename); bufscpy(&dirtfname[0], tp); } } if (!(frewrite = fdopen(fdesc, FOPEN_W))) { efaterror(newRCSfilename); } } restoreints(); errno = e; return f;} voidkeepdirtemp(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 (dirtfname[i].string == name) { dirtfmaker[i] = notmade; return; } faterror("keepdirtemp");} char const *makedirtemp(name, n) register char const *name; int n;/* * Have maketemp() do all the work if name is null. * Otherwise, create a unique filename in name's dir using n and name * and store it into the dirtfname[n]. * Because of storage in tfnames, dirtempunlink() can unlink the file later. * Return a pointer to the filename created. */{ register char *tp, *np; register size_t dl; register struct buf *bn; if (!name) return maketemp(n); dl = dirlen(name); bn = &dirtfname[n]; bufalloc(bn,# if has_mktemp dl + 9# else strlen(name) + 3# endif ); bufscpy(bn, name); np = tp = bn->string; tp += dl; *tp++ = '_'; *tp++ = '0'+n; catchints();# if has_mktemp VOID strcpy(tp, "XXXXXX"); if (!mktemp(np) || !*np) faterror("can't make temporary file name `%.*s%c_%cXXXXXX'", (int)dl, name, SLASH, '0'+n );# else /* * Posix 1003.1-1990 has no reliable way * to create a unique file in a named directory. * We fudge here. If the working file name 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 dirtfmaker[n] = real; return np;} voiddirtempunlink()/* Clean up makedirtemp() files. May be invoked by signal handler. */{ register int i; enum maker m; for (i = DIRTEMPNAMES; 0 <= --i; ) if ((m = dirtfmaker[i]) != notmade) { if (m == effective) seteid(); VOID un_link(dirtfname[i].string); if (m == effective) setrid(); dirtfmaker[i] = notmade; }} int#if has_prototypeschnamemod(FILE **fromp, char const *from, char const *to, mode_t mode) /* The `#if has_prototypes' is needed because mode_t might promote to int. */#else chnamemod(fromp,from,to,mode) FILE **fromp; char const *from,*to; mode_t mode;#endif/* * Rename a file (with optional stream pointer *FROMP) from FROM to TO. * FROM already exists. * Change its mode to MODE, before renaming if possible. * If FROMP, 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 = mode|S_IWUSR;# else# define mode_while_renaming mode# endif if (fromp) {# if has_fchmod if (fchmod(fileno(*fromp), mode_while_renaming) != 0) return -1;# endif Ozclose(fromp); }# if has_fchmod else# endif if (chmod(from, mode_while_renaming) != 0) return -1;# if !has_rename || bad_b_rename VOID un_link(to); /* * We need not check the result; * link() or rename() will catch it. * No harm is done if TO does not exist. * However, there's a short window of inconsistency * during which TO does not exist. */# endif return# if !has_rename do_link(from,to) != 0 ? -1 : un_link(from)# else rename(from, to) != 0# if has_NFS && errno != ENOENT# endif ? -1# if bad_a_rename : mode != mode_while_renaming ? chmod(to, mode)# endif : 0# endif ;# undef mode_while_renaming} intfindlock(delete, target) int delete; struct hshentry **target;/* * Find the first lock held by caller and return a pointer * to the locked delta; also removes the lock if DELETE. * If one lock, put it into *TARGET. * Return 0 for no locks, 1 for one, 2 for two or more. */{ register struct lock *next, **trail, **found; found = 0; for (trail = &Locks; (next = *trail); trail = &next->nextlock) if (strcmp(getcaller(), next->login) == 0) { if (found) { error("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 = nil; *found = next->nextlock; } return 1;} intaddlock(delta) struct hshentry * delta;/* * Add a lock held by caller to DELTA and yield 1 if successful. * Print an error message 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; next=Locks; for (next = Locks; next; next = next->nextlock) if (cmpnum(delta->num, next->delta->num) == 0) if (strcmp(getcaller(), next->login) == 0) return 0; else { error("revision %s 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;} intaddsymbol(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 true if successful. */{ register struct assoc *next; for (next = Symbols; next; next = next->nextassoc) if (strcmp(name, next->symbol) == 0) if (rebind || strcmp(next->num,num) == 0) { next->num = num; return true; } else { error("symbolic name %s already bound to %s", name, next->num ); return false; } next = ftalloc(struct assoc); next->symbol = name; next->num = num; next->nextassoc = Symbols; Symbols = next; return true;} char const *getcaller()/* Get the caller's login name. */{# if has_setuid return getusername(euid()!=ruid());# else return getusername(false);# endif} intcheckaccesslist()/* * 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)); error("user %s not on the access list", getcaller()); return false;} intdorewrite(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 true on success. */{ int r, e; if (lockflag) if (changed) { if (changed < 0) return false; putadmin(frewrite); puttree(Head, frewrite); aprintf(frewrite, "\n\n%s%c", Kdesc, nextc); foutptr = frewrite; } else { Ozclose(&frewrite); seteid(); ignoreints(); r = un_link(newRCSfilename); e = errno; keepdirtemp(newRCSfilename); restoreints(); setrid(); if (r != 0) { enerror(e, RCSfilename); return false; } } return true;} intdonerewrite(changed) int changed;/* * Finish rewriting an RCS file if CHANGED is nonzero. * Return true on success. */{ int r, e; if (changed && !nerror) { if (finptr) { fastcopy(finptr, frewrite); Izclose(&finptr); } if (1 < RCSstat.st_nlink) warn("breaking hard link to %s", RCSfilename); seteid(); ignoreints(); r = chnamemod(&frewrite, newRCSfilename, RCSfilename, RCSstat.st_mode & ~(S_IWUSR|S_IWGRP|S_IWOTH) ); e = errno; keepdirtemp(newRCSfilename); restoreints(); setrid(); if (r != 0) { enerror(e, RCSfilename); error("saved in %s", newRCSfilename); dirtempunlink(); return false; } } return true;} voidaflush(f) FILE *f;{ if (fflush(f) != 0) Oerror();}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -