home *** CD-ROM | disk | FTP | other *** search
/ Frozen Fish 1: Amiga / FrozenFish-Apr94.iso / bbs / gnu / rcs-5.6.0.1.tar.gz / rcs-5.6.0.1.tar / rcs5.6.0.1 / src / rcsedit.c < prev    next >
C/C++ Source or Header  |  1991-11-22  |  40KB  |  1,657 lines

  1. /*
  2.  *                     RCS stream editor
  3.  */
  4. /**********************************************************************************
  5.  *                       edits the input file according to a
  6.  *                       script from stdin, generated by diff -n
  7.  *                       performs keyword expansion
  8.  **********************************************************************************
  9.  */
  10.  
  11. /* Copyright (C) 1982, 1988, 1989 Walter Tichy
  12.    Copyright 1990, 1991 by Paul Eggert
  13.    Distributed under license by the Free Software Foundation, Inc.
  14.  
  15. This file is part of RCS.
  16.  
  17. RCS is free software; you can redistribute it and/or modify
  18. it under the terms of the GNU General Public License as published by
  19. the Free Software Foundation; either version 2, or (at your option)
  20. any later version.
  21.  
  22. RCS is distributed in the hope that it will be useful,
  23. but WITHOUT ANY WARRANTY; without even the implied warranty of
  24. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  25. GNU General Public License for more details.
  26.  
  27. You should have received a copy of the GNU General Public License
  28. along with RCS; see the file COPYING.  If not, write to
  29. the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
  30.  
  31. Report problems and direct all questions to:
  32.  
  33.     rcs-bugs@cs.purdue.edu
  34.  
  35. */
  36.  
  37.  
  38. /* $Log: rcsedit.c,v $
  39.  * Revision 5.11  1991/11/03  01:11:44  eggert
  40.  * Move the warning about link breaking to where they're actually being broken.
  41.  *
  42.  * Revision 5.10  1991/10/07  17:32:46  eggert
  43.  * Support piece tables even if !has_mmap.  Fix rare NFS bugs.
  44.  *
  45.  * Revision 5.9  1991/09/17  19:07:40  eggert
  46.  * SGI readlink() yields ENXIO, not EINVAL, for nonlinks.
  47.  *
  48.  * Revision 5.8  1991/08/19  03:13:55  eggert
  49.  * Add piece tables, NFS bug workarounds.  Catch odd filenames.  Tune.
  50.  *
  51.  * Revision 5.7  1991/04/21  11:58:21  eggert
  52.  * Fix errno bugs.  Add -x, RCSINIT, MS-DOS support.
  53.  *
  54.  * Revision 5.6  1991/02/25  07:12:40  eggert
  55.  * Fix setuid bug.  Support new link behavior.  Work around broken "w+" fopen.
  56.  *
  57.  * Revision 5.5  1990/12/30  05:07:35  eggert
  58.  * Fix report of busy RCS files when !defined(O_CREAT) | !defined(O_EXCL).
  59.  *
  60.  * Revision 5.4  1990/11/01  05:03:40  eggert
  61.  * Permit arbitrary data in comment leaders.
  62.  *
  63.  * Revision 5.3  1990/09/11  02:41:13  eggert
  64.  * Tune expandline().
  65.  *
  66.  * Revision 5.2  1990/09/04  08:02:21  eggert
  67.  * Count RCS lines better.  Improve incomplete line handling.
  68.  *
  69.  * Revision 5.1  1990/08/29  07:13:56  eggert
  70.  * Add -kkvl.
  71.  * Fix bug when getting revisions to files ending in incomplete lines.
  72.  * Fix bug in comment leader expansion.
  73.  *
  74.  * Revision 5.0  1990/08/22  08:12:47  eggert
  75.  * Don't require final newline.
  76.  * Don't append "checked in with -k by " to logs,
  77.  * so that checking in a program with -k doesn't change it.
  78.  * Don't generate trailing white space for empty comment leader.
  79.  * Remove compile-time limits; use malloc instead.  Add -k, -V.
  80.  * Permit dates past 1999/12/31.  Make lock and temp files faster and safer.
  81.  * Ansify and Posixate.  Check diff's output.
  82.  *
  83.  * Revision 4.8  89/05/01  15:12:35  narten
  84.  * changed copyright header to reflect current distribution rules
  85.  * 
  86.  * Revision 4.7  88/11/08  13:54:14  narten
  87.  * misplaced semicolon caused infinite loop
  88.  * 
  89.  * Revision 4.6  88/08/09  19:12:45  eggert
  90.  * Shrink stdio code size; allow cc -R.
  91.  * 
  92.  * Revision 4.5  87/12/18  11:38:46  narten
  93.  * Changes from the 43. version. Don't know the significance of the
  94.  * first change involving "rewind". Also, additional "lint" cleanup.
  95.  * (Guy Harris)
  96.  * 
  97.  * Revision 4.4  87/10/18  10:32:21  narten
  98.  * Updating version numbers. Changes relative to version 1.1 actually
  99.  * relative to 4.1
  100.  * 
  101.  * Revision 1.4  87/09/24  13:59:29  narten
  102.  * Sources now pass through lint (if you ignore printf/sprintf/fprintf 
  103.  * warnings)
  104.  * 
  105.  * Revision 1.3  87/09/15  16:39:39  shepler
  106.  * added an initializatin of the variables editline and linecorr
  107.  * this will be done each time a file is processed.
  108.  * (there was an obscure bug where if co was used to retrieve multiple files
  109.  *  it would dump)
  110.  * fix attributed to  Roy Morris @FileNet Corp ...!felix!roy
  111.  * 
  112.  * Revision 1.2  87/03/27  14:22:17  jenkins
  113.  * Port to suns
  114.  * 
  115.  * Revision 4.1  83/05/12  13:10:30  wft
  116.  * Added new markers Id and RCSfile; added locker to Header and Id.
  117.  * Overhauled expandline completely() (problem with $01234567890123456789@).
  118.  * Moved trymatch() and marker table to rcskeys.c.
  119.  * 
  120.  * Revision 3.7  83/05/12  13:04:39  wft
  121.  * Added retry to expandline to resume after failed match which ended in $.
  122.  * Fixed truncation problem for $19chars followed by@@.
  123.  * Log no longer expands full path of RCS file.
  124.  * 
  125.  * Revision 3.6  83/05/11  16:06:30  wft
  126.  * added retry to expandline to resume after failed match which ended in $.
  127.  * Fixed truncation problem for $19chars followed by@@.
  128.  * 
  129.  * Revision 3.5  82/12/04  13:20:56  wft
  130.  * Added expansion of keyword Locker.
  131.  *
  132.  * Revision 3.4  82/12/03  12:26:54  wft
  133.  * Added line number correction in case editing does not start at the
  134.  * beginning of the file.
  135.  * Changed keyword expansion to always print a space before closing KDELIM;
  136.  * Expansion for Header shortened.
  137.  *
  138.  * Revision 3.3  82/11/14  14:49:30  wft
  139.  * removed Suffix from keyword expansion. Replaced fclose with ffclose.
  140.  * keyreplace() gets log message from delta, not from curlogmsg.
  141.  * fixed expression overflow in while(c=putc(GETC....
  142.  * checked nil printing.
  143.  *
  144.  * Revision 3.2  82/10/18  21:13:39  wft
  145.  * I added checks for write errors during the co process, and renamed
  146.  * expandstring() to xpandstring().
  147.  *
  148.  * Revision 3.1  82/10/13  15:52:55  wft
  149.  * changed type of result of getc() from char to int.
  150.  * made keyword expansion loop in expandline() portable to machines
  151.  * without sign-extension.
  152.  */
  153.  
  154.  
  155. #include "rcsbase.h"
  156.  
  157. libId(editId, "$Id: rcsedit.c,v 5.11 1991/11/03 01:11:44 eggert Exp $")
  158.  
  159. static void keyreplace P((enum markers,struct hshentry const*,FILE*));
  160.  
  161.  
  162. FILE *fcopy;         /* result file descriptor                */
  163. char const *resultfile;  /* result file name                    */
  164. int locker_expansion;     /* should the locker name be appended to Id val?   */
  165. #if !large_memory
  166.     static RILE *fedit; /* edit file descriptor */
  167.     static char const *editfile; /* edit pathname */
  168. #endif
  169. static unsigned long editline; /* edit line counter; #lines before cursor   */
  170. static long linecorr; /* #adds - #deletes in each edit run.            */
  171.                /*used to correct editline in case file is not rewound after */
  172.                /* applying one delta                                        */
  173.  
  174. #define DIRTEMPNAMES 2
  175. enum maker {notmade, real, effective};
  176. struct buf dirtfname[DIRTEMPNAMES];        /* unlink these when done */
  177. static enum maker volatile dirtfmaker[DIRTEMPNAMES];    /* if these are set */
  178.  
  179.  
  180. #if has_NFS || bad_unlink
  181.     int
  182. un_link(s)
  183.     char const *s;
  184. /*
  185.  * Remove S, even if it is unwritable.
  186.  * Ignore unlink() ENOENT failures; NFS generates bogus ones.
  187.  */
  188. {
  189. #    if bad_unlink
  190.         int e;
  191.         if (unlink(s) == 0)
  192.             return 0;
  193.         e = errno;
  194. #        if has_NFS
  195.             if (e == ENOENT)
  196.                 return 0;
  197. #        endif
  198.         if (chmod(s, S_IWUSR) != 0) {
  199.             errno = e;
  200.             return -1;
  201.         }
  202. #    endif
  203. #    if has_NFS
  204.         return unlink(s)==0 || errno==ENOENT  ?  0  :  -1;
  205. #    else
  206.         return unlink(s);
  207. #    endif
  208. }
  209. #endif
  210.  
  211. #if !has_rename
  212. #  if !has_NFS
  213. #    define do_link(s,t) link(s,t)
  214. #  else
  215.     static int
  216. do_link(s, t)
  217.     char const *s, *t;
  218. /* Link S to T, ignoring bogus EEXIST problems due to NFS failures.  */
  219. {
  220.     struct stat sb, tb;
  221.  
  222.     if (link(s,t) == 0)
  223.         return 0;
  224.     if (errno != EEXIST)
  225.         return -1;
  226.     if (
  227.         stat(s, &sb) == 0  &&
  228.         stat(t, &tb) == 0  &&
  229.         sb.st_ino == tb.st_ino  &&
  230.         sb.st_dev == tb.st_dev
  231.     )
  232.         return 0;
  233.     errno = EEXIST;
  234.     return -1;
  235. }
  236. #  endif
  237. #endif
  238.  
  239.  
  240.     static exiting void
  241. editEndsPrematurely()
  242. {
  243.     fatserror("edit script ends prematurely");
  244. }
  245.  
  246.     static exiting void
  247. editLineNumberOverflow()
  248. {
  249.     fatserror("edit script refers to line past end of file");
  250. }
  251.  
  252.  
  253. #if large_memory
  254.  
  255. #if has_memmove
  256. #    define movelines(s1, s2, n) VOID memmove(s1, s2, (n)*sizeof(Iptr_type))
  257. #else
  258.     static void
  259. movelines(s1, s2, n)
  260.     register Iptr_type *s1;
  261.     register Iptr_type const *s2;
  262.     register unsigned long n;
  263. {
  264.     if (s1 < s2)
  265.         do {
  266.             *s1++ = *s2++;
  267.         } while (--n);
  268.     else {
  269.         s1 += n;
  270.         s2 += n;
  271.         do {
  272.             *--s1 = *--s2;
  273.         } while (--n);
  274.     }
  275. }
  276. #endif
  277.  
  278. /*
  279.  * `line' contains pointers to the lines in the currently `edited' file.
  280.  * It is a 0-origin array that represents linelim-gapsize lines.
  281.  * line[0..gap-1] and line[gap+gapsize..linelim-1] contain pointers to lines.
  282.  * line[gap..gap+gapsize-1] contains garbage.
  283.  *
  284.  * Any @s in lines are duplicated.
  285.  * Lines are terminated by \n, or (for a last partial line only) by single @.
  286.  */
  287. static Iptr_type *line;
  288. static unsigned long gap, gapsize, linelim;
  289.  
  290.  
  291.     static void
  292. insertline(n, l)
  293.     unsigned long n;
  294.     Iptr_type l;
  295. /* Before line N, insert line L.  N is 0-origin.  */
  296. {
  297.     if (linelim-gapsize < n)
  298.         editLineNumberOverflow();
  299.     if (!gapsize)
  300.         line =
  301.         !linelim ?
  302.             tnalloc(Iptr_type, linelim = gapsize = 1024)
  303.         : (
  304.             gap = gapsize = linelim,
  305.             trealloc(Iptr_type, line, linelim <<= 1)
  306.         );
  307.     if (n < gap)
  308.         movelines(line+n+gapsize, line+n, gap-n);
  309.     else if (gap < n)
  310.         movelines(line+gap, line+gap+gapsize, n-gap);
  311.  
  312.     line[n] = l;
  313.     gap = n + 1;
  314.     gapsize--;
  315. }
  316.  
  317.     static void
  318. deletelines(n, nlines)
  319.     unsigned long n, nlines;
  320. /* Delete lines N through N+NLINES-1.  N is 0-origin.  */
  321. {
  322.     unsigned long l = n + nlines;
  323.     if (linelim-gapsize < l  ||  l < n)
  324.         editLineNumberOverflow();
  325.     if (l < gap)
  326.         movelines(line+l+gapsize, line+l, gap-l);
  327.     else if (gap < n)
  328.         movelines(line+gap, line+gap+gapsize, n-gap);
  329.  
  330.     gap = n;
  331.     gapsize += nlines;
  332. }
  333.  
  334.     static void
  335. snapshotline(f, l)
  336.     register FILE *f;
  337.     register Iptr_type l;
  338. {
  339.     register int c;
  340.     do {
  341.         if ((c = *l++) == SDELIM  &&  *l++ != SDELIM)
  342.             return;
  343.         aputc(c, f);
  344.     } while (c != '\n');
  345. }
  346.  
  347.     void
  348. snapshotedit(f)
  349.     FILE *f;
  350. /* Copy the current state of the edits to F.  */
  351. {
  352.     register Iptr_type *p, *lim, *l=line;
  353.     for (p=l, lim=l+gap;  p<lim;  )
  354.         snapshotline(f, *p++);
  355.     for (p+=gapsize, lim=l+linelim;  p<lim;  )
  356.         snapshotline(f, *p++);
  357. }
  358.  
  359.     static void
  360. finisheditline(fin, fout, l, delta)
  361.     RILE *fin;
  362.     FILE *fout;
  363.     Iptr_type l;
  364.     struct hshentry const *delta;
  365. {
  366.     Iseek(fin, l);
  367.     if (expandline(fin, fout, delta, true, (FILE*)0)  <  0)
  368.         faterror("finisheditline internal error");
  369. }
  370.  
  371.     void
  372. finishedit(delta, outfile, done)
  373.     struct hshentry const *delta;
  374.     FILE *outfile;
  375.     int done;
  376. /*
  377.  * Doing expansion if DELTA is set, output the state of the edits to OUTFILE.
  378.  * But do nothing unless DONE is set (which means we are on the last pass).
  379.  */
  380. {
  381.     if (done) {
  382.         openfcopy(outfile);
  383.         outfile = fcopy;
  384.         if (!delta)
  385.             snapshotedit(outfile);
  386.         else {
  387.             register Iptr_type *p, *lim, *l = line;
  388.             register RILE *fin = finptr;
  389.             Iptr_type here = Itell(fin);
  390.             for (p=l, lim=l+gap;  p<lim;  )
  391.                 finisheditline(fin, outfile, *p++, delta);
  392.             for (p+=gapsize, lim=l+linelim;  p<lim;  )
  393.                 finisheditline(fin, outfile, *p++, delta);
  394.             Iseek(fin, here);
  395.         }
  396.     }
  397. }
  398.  
  399. /* Open a temporary FILENAME for output, truncating any previous contents.  */
  400. #   define fopen_update_truncate(filename) fopen(filename, FOPEN_W_WORK)
  401. #else /* !large_memory */
  402.     static FILE *
  403. fopen_update_truncate(filename)
  404.     char const *filename;
  405. {
  406. #    if bad_fopen_wplus
  407.         if (un_link(filename) != 0)
  408.             efaterror(filename);
  409. #    endif
  410.     return fopen(filename, FOPEN_WPLUS_WORK);
  411. }
  412. #endif
  413.  
  414.  
  415.     void
  416. openfcopy(f)
  417.     FILE *f;
  418. {
  419.     if (!(fcopy = f)) {
  420.         if (!resultfile)
  421.             resultfile = maketemp(2);
  422.         if (!(fcopy = fopen_update_truncate(resultfile)))
  423.             efaterror(resultfile);
  424.     }
  425. }
  426.  
  427.  
  428. #if !large_memory
  429.  
  430.     static void
  431. swapeditfiles(outfile)
  432.     FILE *outfile;
  433. /* Function: swaps resultfile and editfile, assigns fedit=fcopy,
  434.  * and rewinds fedit for reading.  Set fcopy to outfile if nonnull;
  435.  * otherwise, set fcopy to be resultfile opened for reading and writing.
  436.  */
  437. {
  438.     char const *tmpptr;
  439.  
  440.     editline = 0;  linecorr = 0;
  441.     if (fseek(fcopy, 0L, SEEK_SET) != 0)
  442.         Oerror();
  443.     fedit = fcopy;
  444.         tmpptr=editfile; editfile=resultfile; resultfile=tmpptr;
  445.     openfcopy(outfile);
  446. }
  447.  
  448.     void
  449. snapshotedit(f)
  450.     FILE *f;
  451. /* Copy the current state of the edits to F.  */
  452. {
  453.     finishedit((struct hshentry *)nil, (FILE*)0, false);
  454.     fastcopy(fedit, f);
  455.     Irewind(fedit);
  456. }
  457.  
  458.     void
  459. finishedit(delta, outfile, done)
  460.     struct hshentry const *delta;
  461.     FILE *outfile;
  462.     int done;
  463. /* copy the rest of the edit file and close it (if it exists).
  464.  * if delta!=nil, perform keyword substitution at the same time.
  465.  * If DONE is set, we are finishing the last pass.
  466.  */
  467. {
  468.     register RILE *fe;
  469.     register FILE *fc;
  470.  
  471.     fe = fedit;
  472.     if (fe) {
  473.         fc = fcopy;
  474.                 if (delta!=nil) {
  475.             while (1 < expandline(fe,fc,delta,false,(FILE*)0))
  476.                 ;
  477.                 } else {
  478.             fastcopy(fe,fc);
  479.                 }
  480.         Ifclose(fe);
  481.         }
  482.     if (!done)
  483.         swapeditfiles(outfile);
  484. }
  485. #endif
  486.  
  487.  
  488.  
  489. #if large_memory
  490. #    define copylines(upto,delta) (editline = (upto))
  491. #else
  492.     static void
  493. copylines(upto,delta)
  494.     register unsigned long upto;
  495.     struct hshentry const *delta;
  496. /*
  497.  * Copy input lines editline+1..upto from fedit to fcopy.
  498.  * If delta != nil, keyword expansion is done simultaneously.
  499.  * editline is updated. Rewinds a file only if necessary.
  500.  */
  501. {
  502.     register int c;
  503.     declarecache;
  504.     register FILE *fc;
  505.     register RILE *fe;
  506.  
  507.     if (upto < editline) {
  508.                 /* swap files */
  509.         finishedit((struct hshentry *)nil, (FILE*)0, false);
  510.                 /* assumes edit only during last pass, from the beginning*/
  511.         }
  512.     fe = fedit;
  513.     fc = fcopy;
  514.     if (editline < upto)
  515.         if (delta)
  516.         do {
  517.             if (expandline(fe,fc,delta,false,(FILE*)0) <= 1)
  518.                 editLineNumberOverflow();
  519.         } while (++editline < upto);
  520.         else {
  521.         setupcache(fe); cache(fe);
  522.         do {
  523.             do {
  524.                 cachegeteof(c, editLineNumberOverflow(););
  525.                 aputc(c, fc);
  526.             } while (c != '\n');
  527.         } while (++editline < upto);
  528.         uncache(fe);
  529.         }
  530. }
  531. #endif
  532.  
  533.  
  534.  
  535.     void
  536. xpandstring(delta)
  537.     struct hshentry const *delta;
  538. /* Function: Reads a string terminated by SDELIM from finptr and writes it
  539.  * to fcopy. Double SDELIM is replaced with single SDELIM.
  540.  * Keyword expansion is performed with data from delta.
  541.  * If foutptr is nonnull, the string is also copied unchanged to foutptr.
  542.  */
  543. {
  544.     while (1 < expandline(finptr,fcopy,delta,true,foutptr))
  545.         ;
  546. }
  547.  
  548.  
  549.     void
  550. copystring()
  551. /* Function: copies a string terminated with a single SDELIM from finptr to
  552.  * fcopy, replacing all double SDELIM with a single SDELIM.
  553.  * If foutptr is nonnull, the string also copied unchanged to foutptr.
  554.  * editline is incremented by the number of lines copied.
  555.  * Assumption: next character read is first string character.
  556.  */
  557. {    register c;
  558.     declarecache;
  559.     register FILE *frew, *fcop;
  560.     register int amidline;
  561.     register RILE *fin;
  562.  
  563.     fin = finptr;
  564.     setupcache(fin); cache(fin);
  565.     frew = foutptr;
  566.     fcop = fcopy;
  567.     amidline = false;
  568.     for (;;) {
  569.         GETC(frew,c);
  570.         switch (c) {
  571.             case '\n':
  572.             ++editline;
  573.             ++rcsline;
  574.             amidline = false;
  575.             break;
  576.             case SDELIM:
  577.             GETC(frew,c);
  578.             if (c != SDELIM) {
  579.                 /* end of string */
  580.                 nextc = c;
  581.                 editline += amidline;
  582.                 uncache(fin);
  583.                 return;
  584.             }
  585.             /* fall into */
  586.             default:
  587.             amidline = true;
  588.             break;
  589.                 }
  590.         aputc(c,fcop);
  591.         }
  592. }
  593.  
  594.  
  595.     void
  596. enterstring()
  597. /* Like copystring, except the string is put into the edit data structure.  */
  598. {
  599. #if !large_memory
  600.     editfile = 0;
  601.     fedit = 0;
  602.     editline = linecorr = 0;
  603.     resultfile = maketemp(1);
  604.     if (!(fcopy = fopen_update_truncate(resultfile)))
  605.         efaterror(resultfile);
  606.     copystring();
  607. #else
  608.     register int c;
  609.     declarecache;
  610.     register FILE *frew;
  611.     register unsigned long e, oe;
  612.     register int amidline, oamidline;
  613.     register Iptr_type optr;
  614.     register RILE *fin;
  615.  
  616.     e = 0;
  617.     gap = 0;
  618.     gapsize = linelim;
  619.     fin = finptr;
  620.     setupcache(fin); cache(fin);
  621.     advise_access(fin, MADV_NORMAL);
  622.     frew = foutptr;
  623.     amidline = false;
  624.     for (;;) {
  625.         optr = cachetell();
  626.         GETC(frew,c);
  627.         oamidline = amidline;
  628.         oe = e;
  629.         switch (c) {
  630.             case '\n':
  631.             ++e;
  632.             ++rcsline;
  633.             amidline = false;
  634.             break;
  635.             case SDELIM:
  636.             GETC(frew,c);
  637.             if (c != SDELIM) {
  638.                 /* end of string */
  639.                 nextc = c;
  640.                 editline = e + amidline;
  641.                 linecorr = 0;
  642.                 uncache(fin);
  643.                 return;
  644.             }
  645.             /* fall into */
  646.             default:
  647.             amidline = true;
  648.             break;
  649.         }
  650.         if (!oamidline)
  651.             insertline(oe, optr);
  652.     }
  653. #endif
  654. }
  655.  
  656.  
  657.  
  658.  
  659.     void
  660. #if large_memory
  661. edit_string()
  662. #else
  663.   editstring(delta)
  664.     struct hshentry const *delta;
  665. #endif
  666. /*
  667.  * Read an edit script from finptr and applies it to the edit file.
  668. #if !large_memory
  669.  * The result is written to fcopy.
  670.  * If delta!=nil, keyword expansion is performed simultaneously.
  671.  * If running out of lines in fedit, fedit and fcopy are swapped.
  672.  * editfile is the name of the file that goes with fedit.
  673. #endif
  674.  * If foutptr is set, the edit script is also copied verbatim to foutptr.
  675.  * Assumes that all these files are open.
  676.  * resultfile is the name of the file that goes with fcopy.
  677.  * Assumes the next input character from finptr is the first character of
  678.  * the edit script. Resets nextc on exit.
  679.  */
  680. {
  681.         int ed; /* editor command */
  682.         register int c;
  683.     declarecache;
  684.     register FILE *frew;
  685. #    if !large_memory
  686.         register FILE *f;
  687.         unsigned long line_lim = ULONG_MAX;
  688.         register RILE *fe;
  689. #    endif
  690.     register unsigned long i;
  691.     register RILE *fin;
  692. #    if large_memory
  693.         register unsigned long j;
  694. #    endif
  695.     struct diffcmd dc;
  696.  
  697.         editline += linecorr; linecorr=0; /*correct line number*/
  698.     frew = foutptr;
  699.     fin = finptr;
  700.     setupcache(fin);
  701.     initdiffcmd(&dc);
  702.     while (0  <=  (ed = getdiffcmd(fin,true,frew,&dc)))
  703. #if !large_memory
  704.         if (line_lim <= dc.line1)
  705.             editLineNumberOverflow();
  706.         else
  707. #endif
  708.         if (!ed) {
  709.             copylines(dc.line1-1, delta);
  710.                         /* skip over unwanted lines */
  711.             i = dc.nlines;
  712.             linecorr -= i;
  713.             editline += i;
  714. #            if large_memory
  715.                 deletelines(editline+linecorr, i);
  716. #            else
  717.                 fe = fedit;
  718.                 do {
  719.                                 /*skip next line*/
  720.                 do {
  721.                     Igeteof(fe, c, { if (i!=1) editLineNumberOverflow(); line_lim = dc.dafter; break; } );
  722.                 } while (c != '\n');
  723.                 } while (--i);
  724. #            endif
  725.         } else {
  726.             copylines(dc.line1, delta); /*copy only; no delete*/
  727.             i = dc.nlines;
  728. #            if large_memory
  729.                 j = editline+linecorr;
  730. #            endif
  731.             linecorr += i;
  732. #if !large_memory
  733.             f = fcopy;
  734.             if (delta)
  735.                 do {
  736.                 switch (expandline(fin,f,delta,true,frew)) {
  737.                     case 0: case 1:
  738.                     if (i==1)
  739.                         return;
  740.                     /* fall into */
  741.                     case -1:
  742.                     editEndsPrematurely();
  743.                 }
  744.                 } while (--i);
  745.             else
  746. #endif
  747.             {
  748.                 cache(fin);
  749.                 do {
  750. #                if large_memory
  751.                     insertline(j++, cachetell());
  752. #                endif
  753.                 for (;;) {
  754.                     GETC(frew, c);
  755. #                    if !large_memory
  756.                     aputc(c, f);
  757. #                    endif
  758.                     if (c == '\n')
  759.                     break;
  760.                     if (c==SDELIM) {
  761.                     GETC(frew, c);
  762.                     if (c!=SDELIM) {
  763.                         if (--i)
  764.                         editEndsPrematurely();
  765.                         nextc = c;
  766.                         uncache(fin);
  767.                         return;
  768.                     }
  769.                     }
  770.                 }
  771.                 ++rcsline;
  772.                 } while (--i);
  773.                 uncache(fin);
  774.             }
  775.                 }
  776. }
  777.  
  778.  
  779.  
  780. /* The rest is for keyword expansion */
  781.  
  782.  
  783.  
  784.     int
  785. expandline(infile, outfile, delta, delimstuffed, frewfile)
  786.     RILE *infile;
  787.     FILE *outfile, *frewfile;
  788.     struct hshentry const *delta;
  789.     int delimstuffed;
  790. /*
  791.  * Read a line from INFILE and write it to OUTFILE.
  792.  * If DELIMSTUFFED is true, double SDELIM is replaced with single SDELIM.
  793.  * Keyword expansion is performed with data from delta.
  794.  * If FREWFILE is set, copy the line unchanged to FREWFILE.
  795.  * DELIMSTUFFED must be true if FREWFILE is set.
  796.  * Yields -1 if no data is copied, 0 if an incomplete line is copied,
  797.  * 2 if a complete line is copied; adds 1 to yield if expansion occurred.
  798.  */
  799. {
  800.     register c;
  801.     declarecache;
  802.     register FILE *out, *frew;
  803.     register char * tp;
  804.     register int e, ds, r;
  805.     char const *tlim;
  806.     static struct buf keyval;
  807.         enum markers matchresult;
  808.  
  809.     setupcache(infile); cache(infile);
  810.     out = outfile;
  811.     frew = frewfile;
  812.     ds = delimstuffed;
  813.     bufalloc(&keyval, keylength+3);
  814.     e = 0;
  815.     r = -1;
  816.  
  817.         for (;;) {
  818.         if (ds) {
  819.         GETC(frew, c);
  820.         } else
  821.         cachegeteof(c, goto uncache_exit;);
  822.         for (;;) {
  823.         switch (c) {
  824.             case SDELIM:
  825.             if (ds) {
  826.                 GETC(frew, c);
  827.                 if (c != SDELIM) {
  828.                                 /* end of string */
  829.                                 nextc=c;
  830.                 goto uncache_exit;
  831.                 }
  832.             }
  833.             /* fall into */
  834.             default:
  835.             aputc(c,out);
  836.             r = 0;
  837.             break;
  838.  
  839.             case '\n':
  840.             rcsline += ds;
  841.             aputc(c,out);
  842.             r = 2;
  843.             goto uncache_exit;
  844.  
  845.             case KDELIM:
  846.             r = 0;
  847.                         /* check for keyword */
  848.                         /* first, copy a long enough string into keystring */
  849.             tp = keyval.string;
  850.             *tp++ = KDELIM;
  851.             for (;;) {
  852.                 if (ds) {
  853.                 GETC(frew, c);
  854.                 } else
  855.                 cachegeteof(c, goto keystring_eof;);
  856.                 if (tp < keyval.string+keylength+1)
  857.                 switch (ctab[c]) {
  858.                     case LETTER: case Letter:
  859.                     *tp++ = c;
  860.                     continue;
  861.                     default:
  862.                     break;
  863.                 }
  864.                 break;
  865.                         }
  866.             *tp++ = c; *tp = '\0';
  867.             matchresult = trymatch(keyval.string+1);
  868.             if (matchresult==Nomatch) {
  869.                 tp[-1] = 0;
  870.                 aputs(keyval.string, out);
  871.                 continue;   /* last c handled properly */
  872.             }
  873.  
  874.             /* Now we have a keyword terminated with a K/VDELIM */
  875.             if (c==VDELIM) {
  876.                   /* try to find closing KDELIM, and replace value */
  877.                   tlim = keyval.string + keyval.size;
  878.                   for (;;) {
  879.                       if (ds) {
  880.                     GETC(frew, c);
  881.                       } else
  882.                     cachegeteof(c, goto keystring_eof;);
  883.                       if (c=='\n' || c==KDELIM)
  884.                     break;
  885.                       *tp++ =c;
  886.                       if (tlim <= tp)
  887.                       tp = bufenlarge(&keyval, &tlim);
  888.                       if (c==SDELIM && ds) { /*skip next SDELIM */
  889.                         GETC(frew, c);
  890.                         if (c != SDELIM) {
  891.                             /* end of string before closing KDELIM or newline */
  892.                             nextc = c;
  893.                             goto keystring_eof;
  894.                         }
  895.                       }
  896.                   }
  897.                   if (c!=KDELIM) {
  898.                     /* couldn't find closing KDELIM -- give up */
  899.                     *tp = 0;
  900.                     aputs(keyval.string, out);
  901.                     continue;   /* last c handled properly */
  902.                   }
  903.             }
  904.             /* now put out the new keyword value */
  905.             keyreplace(matchresult,delta,out);
  906.             e = 1;
  907.             break;
  908.                 }
  909.         break;
  910.         }
  911.         }
  912.  
  913.     keystring_eof:
  914.     *tp = 0;
  915.     aputs(keyval.string, out);
  916.     uncache_exit:
  917.     uncache(infile);
  918.     return r + e;
  919. }
  920.  
  921.  
  922. char const ciklog[ciklogsize] = "checked in with -k by ";
  923.  
  924.     static void
  925. keyreplace(marker,delta,out)
  926.     enum markers marker;
  927.     register struct hshentry const *delta;
  928.     register FILE *out;
  929. /* function: outputs the keyword value(s) corresponding to marker.
  930.  * Attributes are derived from delta.
  931.  */
  932. {
  933.     register char const *sp, *cp, *date;
  934.     register char c;
  935.     register size_t cs, cw, ls;
  936.     char const *sp1;
  937.     char datebuf[datesize];
  938.     int RCSv;
  939.  
  940.     sp = Keyword[(int)marker];
  941.  
  942.     if (Expand == KEY_EXPAND) {
  943.         aprintf(out, "%c%s%c", KDELIM, sp, KDELIM);
  944.         return;
  945.     }
  946.  
  947.         date= delta->date;
  948.     RCSv = RCSversion;
  949.  
  950.     if (Expand == KEYVAL_EXPAND  ||  Expand == KEYVALLOCK_EXPAND)
  951.         aprintf(out, "%c%s%c%c", KDELIM, sp, VDELIM,
  952.             marker==Log && RCSv<VERSION(5)  ?  '\t'  :  ' '
  953.         );
  954.  
  955.         switch (marker) {
  956.         case Author:
  957.         aputs(delta->author, out);
  958.                 break;
  959.         case Date:
  960.         aputs(date2str(date,datebuf), out);
  961.                 break;
  962.         case Id:
  963.     case Header:
  964.         aprintf(out, "%s %s %s %s %s",
  965.               marker==Id || RCSv<VERSION(4)
  966.             ? basename(RCSfilename)
  967.             : getfullRCSname(),
  968.             delta->num,
  969.             date2str(date, datebuf),
  970.             delta->author,
  971.               RCSv==VERSION(3) && delta->lockedby ? "Locked"
  972.             : delta->state
  973.         );
  974.         if (delta->lockedby!=nil)
  975.             if (VERSION(5) <= RCSv) {
  976.             if (locker_expansion || Expand==KEYVALLOCK_EXPAND)
  977.                 aprintf(out, " %s", delta->lockedby);
  978.             } else if (RCSv == VERSION(4))
  979.             aprintf(out, " Locker: %s", delta->lockedby);
  980.                 break;
  981.         case Locker:
  982.         if (delta->lockedby)
  983.             if (
  984.                 locker_expansion
  985.             ||    Expand == KEYVALLOCK_EXPAND
  986.             ||    RCSv <= VERSION(4)
  987.             )
  988.             aputs(delta->lockedby, out);
  989.                 break;
  990.         case Log:
  991.         case RCSfile:
  992.         aputs(basename(RCSfilename), out);
  993.                 break;
  994.         case Revision:
  995.         aputs(delta->num, out);
  996.                 break;
  997.         case Source:
  998.         aputs(getfullRCSname(), out);
  999.                 break;
  1000.         case State:
  1001.         aputs(delta->state, out);
  1002.                 break;
  1003.     default:
  1004.         break;
  1005.         }
  1006.     if (Expand == KEYVAL_EXPAND  ||  Expand == KEYVALLOCK_EXPAND) {
  1007.         afputc(' ', out);
  1008.         afputc(KDELIM, out);
  1009.     }
  1010.     if (marker == Log) {
  1011.         sp = delta->log.string;
  1012.         ls = delta->log.size;
  1013.         if (sizeof(ciklog)-1<=ls && !memcmp(sp,ciklog,sizeof(ciklog)-1))
  1014.             return;
  1015.         afputc('\n', out);
  1016.         cp = Comment.string;
  1017.         cw = cs = Comment.size;
  1018.         awrite(cp, cs, out);
  1019.         /* oddity: 2 spaces between date and time, not 1 as usual */
  1020.         sp1 = strchr(date2str(date,datebuf), ' ');
  1021.         aprintf(out, "Revision %s  %.*s %s  %s",
  1022.             delta->num, (int)(sp1-datebuf), datebuf, sp1, delta->author
  1023.         );
  1024.         /* Do not include state: it may change and is not updated.  */
  1025.         /* Comment is the comment leader.  */
  1026.         if (VERSION(5) <= RCSv)
  1027.             for (;  cw && (cp[cw-1]==' ' || cp[cw-1]=='\t');  --cw)
  1028.             ;
  1029.         for (;;) {
  1030.             afputc('\n', out);
  1031.             awrite(cp, cw, out);
  1032.             if (!ls)
  1033.             break;
  1034.             --ls;
  1035.             c = *sp++;
  1036.             if (c != '\n') {
  1037.             awrite(cp+cw, cs-cw, out);
  1038.             do {
  1039.                 afputc(c,out);
  1040.                 if (!ls)
  1041.                 break;
  1042.                 --ls;
  1043.                 c = *sp++;
  1044.             } while (c != '\n');
  1045.             }
  1046.         }
  1047.     }
  1048. }
  1049.  
  1050. #if has_readlink
  1051.     static int
  1052. resolve_symlink(L)
  1053.     struct buf *L;
  1054. /*
  1055.  * If L is a symbolic link, resolve it to the name that it points to.
  1056.  * If unsuccessful, set errno and yield -1.
  1057.  * If it points to an existing file, yield 1.
  1058.  * Otherwise, set errno=ENOENT and yield 0.
  1059.  */
  1060. {
  1061.     char *b, a[SIZEABLE_PATH];
  1062.     int e;
  1063.     size_t s;
  1064.     ssize_t r;
  1065.     struct buf bigbuf;
  1066.     unsigned linkcount = MAXSYMLINKS + 1;
  1067.  
  1068.     b = a;
  1069.     s = sizeof(a);
  1070.     bufautobegin(&bigbuf);
  1071.     while ((r = readlink(L->string,b,s))  !=  -1)
  1072.         if (r == s) {
  1073.         bufalloc(&bigbuf, s<<1);
  1074.         b = bigbuf.string;
  1075.         s = bigbuf.size;
  1076.         } else if (!--linkcount) {
  1077.         errno = ELOOP;
  1078.         return -1;
  1079.         } else {
  1080.         /* Splice symbolic link into L.  */
  1081.         b[r] = '\0';
  1082.         L->string[ROOTPATH(b) ? (size_t)0 : dirlen(L->string)]  =  '\0';
  1083.         bufscat(L, b);
  1084.         }
  1085.     e = errno;
  1086.     bufautoend(&bigbuf);
  1087.     errno = e;
  1088.     switch (e) {
  1089.         case ENXIO:
  1090.         case EINVAL: return 1;
  1091.         case ENOENT: return 0;
  1092.         default: return -1;
  1093.     }
  1094. }
  1095. #endif
  1096.  
  1097.     RILE *
  1098. rcswriteopen(RCSbuf, status, mustread)
  1099.     struct buf *RCSbuf;
  1100.     struct stat *status;
  1101.     int mustread;
  1102. /*
  1103.  * Create the lock file corresponding to RCSNAME.
  1104.  * Then try to open RCSNAME for reading and yield its FILE* descriptor.
  1105.  * Put its status into *STATUS too.
  1106.  * MUSTREAD is true if the file must already exist, too.
  1107.  * If all goes well, discard any previously acquired locks,
  1108.  * and set frewrite to the FILE* descriptor of the lock file,
  1109.  * which will eventually turn into the new RCS file.
  1110.  */
  1111. {
  1112.     register char *tp;
  1113.     register char const *sp, *RCSname, *x;
  1114.     RILE *f;
  1115.     size_t l;
  1116.     int e, exists, fdesc, previouslock, r;
  1117.     struct buf *dirt;
  1118.     struct stat statbuf;
  1119.  
  1120.     previouslock  =  frewrite != 0;
  1121.     exists =
  1122. #        if has_readlink
  1123.             resolve_symlink(RCSbuf);
  1124. #        else
  1125.                 stat(RCSbuf->string, &statbuf) == 0  ?  1
  1126.             :   errno==ENOENT ? 0 : -1;
  1127. #        endif
  1128.     if (exists < (mustread|previouslock))
  1129.         /*
  1130.          * There's an unusual problem with the RCS file;
  1131.          * or the RCS file doesn't exist,
  1132.          * and we must read or we already have a lock elsewhere.
  1133.          */
  1134.         return 0;
  1135.  
  1136.     RCSname = RCSbuf->string;
  1137.     sp = basename(RCSname);
  1138.     l = sp - RCSname;
  1139.     dirt = &dirtfname[previouslock];
  1140.     bufscpy(dirt, RCSname);
  1141.     tp = dirt->string + l;
  1142.     x = rcssuffix(RCSname);
  1143. #    if has_readlink
  1144.         if (!x) {
  1145.         error("symbolic link to non RCS filename `%s'", RCSname);
  1146.         errno = EINVAL;
  1147.         return 0;
  1148.         }
  1149. #    endif
  1150.     if (*sp == *x) {
  1151.         error("RCS filename `%s' incompatible with suffix `%s'", sp, x);
  1152.         errno = EINVAL;
  1153.         return 0;
  1154.     }
  1155.     /* Create a lock file whose name is a function of the RCS filename.  */
  1156.     if (*x) {
  1157.         /*
  1158.          * The suffix is nonempty.
  1159.          * The lock filename is the first char of of the suffix,
  1160.          * followed by the RCS filename with last char removed.  E.g.:
  1161.          *    foo,v    RCS filename with suffix ,v
  1162.          *    ,foo,    lock filename
  1163.          */
  1164.         *tp++ = *x;
  1165.         while (*sp)
  1166.             *tp++ = *sp++;
  1167.         *--tp = 0;
  1168.     } else {
  1169.         /*
  1170.          * The suffix is empty.
  1171.          * The lock filename is the RCS filename
  1172.          * with last char replaced by '_'.
  1173.          */
  1174.         while ((*tp++ = *sp++))
  1175.             ;
  1176.         tp -= 2;
  1177.         if (*tp == '_') {
  1178.             error("RCS filename `%s' ends with `%c'", RCSname, *tp);
  1179.             errno = EINVAL;
  1180.             return 0;
  1181.         }
  1182.         *tp = '_';
  1183.     }
  1184.  
  1185.     sp = tp = dirt->string;
  1186.  
  1187.     f = 0;
  1188.  
  1189.     /*
  1190.     * good news:
  1191.     *    open(f, O_CREAT|O_EXCL|O_TRUNC|O_WRONLY, READONLY) is atomic
  1192.     *    according to Posix 1003.1-1990.
  1193.     * bad news:
  1194.     *    NFS ignores O_EXCL and doesn't comply with Posix 1003.1-1990.
  1195.     * good news:
  1196.     *    (O_TRUNC,READONLY) normally guarantees atomicity even with NFS.
  1197.     * bad news:
  1198.     *    If you're root, (O_TRUNC,READONLY) doesn't guarantee atomicity.
  1199.     * good news:
  1200.     *    Root-over-the-wire NFS access is rare for security reasons.
  1201.     *    This bug has never been reported in practice with RCS.
  1202.     * So we don't worry about this bug.
  1203.     *
  1204.     * An even rarer NFS bug can occur when clients retry requests.
  1205.     * Suppose client A renames the lock file ",f," to "f,v"
  1206.     * at about the same time that client B creates ",f,",
  1207.     * and suppose A's first rename request is delayed, so A reissues it.
  1208.     * The sequence of events might be:
  1209.     *    A sends rename(",f,", "f,v")
  1210.     *    B sends create(",f,")
  1211.     *    A sends retry of rename(",f,", "f,v")
  1212.     *    server receives, does, and acknowledges A's first rename()
  1213.     *    A receives acknowledgment, and its RCS program exits
  1214.     *    server receives, does, and acknowledges B's create()
  1215.     *    server receives, does, and acknowledges A's retry of rename()
  1216.     * This not only wrongly deletes B's lock, it removes the RCS file!
  1217.     * Most NFS implementations have idempotency caches that usually prevent
  1218.     * this scenario, but such caches are finite and can be overrun.
  1219.     * This problem afflicts programs that use the traditional
  1220.     * Unix method of using link() and unlink() to get and release locks,
  1221.     * as well as RCS's method of using open() and rename().
  1222.     * There is no easy workaround for either link-unlink or open-rename.
  1223.     * Any new method based on lockf() seemingly would be incompatible with
  1224.     * the old methods; besides, lockf() is notoriously buggy under NFS.
  1225.     * Since this problem afflicts scads of Unix programs, but is so rare
  1226.     * that nobody seems to be worried about it, we won't worry either.
  1227.     */
  1228. #    define READONLY (S_IRUSR|S_IRGRP|S_IROTH)
  1229. #    if !open_can_creat
  1230. #        define create(f) creat(f, READONLY)
  1231. #    else
  1232. #        define create(f) open(f, O_BINARY|O_CREAT|O_EXCL|O_TRUNC|O_WRONLY, READONLY)
  1233. #    endif
  1234.  
  1235.     catchints();
  1236.     ignoreints();
  1237.  
  1238.     /*
  1239.      * Create a lock file for an RCS file.  This should be atomic, i.e.
  1240.      * if two processes try it simultaneously, at most one should succeed.
  1241.      */
  1242.     seteid();
  1243.     fdesc = create(sp);
  1244.     e = errno;
  1245.     setrid();
  1246.  
  1247.     if (fdesc < 0) {
  1248.         if (e == EACCES  &&  stat(tp,&statbuf) == 0)
  1249.             /* The RCS file is busy.  */
  1250.             e = EEXIST;
  1251.     } else {
  1252.         dirtfmaker[0] = effective;
  1253.         e = ENOENT;
  1254.         if (exists) {
  1255.             f = Iopen(RCSname, FOPEN_R, status);
  1256.             e = errno;
  1257.             if (f && previouslock) {
  1258.             /* Discard the previous lock in favor of this one.  */
  1259.             Ozclose(&frewrite);
  1260.             seteid();
  1261.             if ((r = un_link(newRCSfilename)) != 0)
  1262.                 e = errno;
  1263.             setrid();
  1264.             if (r != 0)
  1265.                 enfaterror(e, newRCSfilename);
  1266.             bufscpy(&dirtfname[0], tp);
  1267.             }
  1268.         }
  1269.         if (!(frewrite = fdopen(fdesc, FOPEN_W))) {
  1270.             efaterror(newRCSfilename);
  1271.         }
  1272.     }
  1273.  
  1274.     restoreints();
  1275.  
  1276.     errno = e;
  1277.     return f;
  1278. }
  1279.  
  1280.     void
  1281. keepdirtemp(name)
  1282.     char const *name;
  1283. /* Do not unlink name, either because it's not there any more,
  1284.  * or because it has already been unlinked.
  1285.  */
  1286. {
  1287.     register int i;
  1288.     for (i=DIRTEMPNAMES; 0<=--i; )
  1289.         if (dirtfname[i].string == name) {
  1290.             dirtfmaker[i] = notmade;
  1291.             return;
  1292.         }
  1293.     faterror("keepdirtemp");
  1294. }
  1295.  
  1296.     char const *
  1297. makedirtemp(name, n)
  1298.     register char const *name;
  1299.     int n;
  1300. /*
  1301.  * Have maketemp() do all the work if name is null.
  1302.  * Otherwise, create a unique filename in name's dir using n and name
  1303.  * and store it into the dirtfname[n].
  1304.  * Because of storage in tfnames, dirtempunlink() can unlink the file later.
  1305.  * Return a pointer to the filename created.
  1306.  */
  1307. {
  1308.     register char *tp, *np;
  1309.     register size_t dl;
  1310.     register struct buf *bn;
  1311.  
  1312.     if (!name)
  1313.         return maketemp(n);
  1314.     dl = dirlen(name);
  1315.     bn = &dirtfname[n];
  1316.     bufalloc(bn,
  1317. #        if has_mktemp
  1318.             dl + 9
  1319. #        else
  1320.             strlen(name) + 3
  1321. #        endif
  1322.     );
  1323.     bufscpy(bn, name);
  1324.     np = tp = bn->string;
  1325.     tp += dl;
  1326.     *tp++ = '_';
  1327.     *tp++ = '0'+n;
  1328.     catchints();
  1329. #    if has_mktemp
  1330.         VOID strcpy(tp, "XXXXXX");
  1331.         if (!mktemp(np) || !*np)
  1332.             faterror("can't make temporary file name `%.*s%c_%cXXXXXX'",
  1333.             (int)dl, name, SLASH, '0'+n
  1334.             );
  1335. #    else
  1336.         /*
  1337.          * Posix 1003.1-1990 has no reliable way
  1338.          * to create a unique file in a named directory.
  1339.          * We fudge here.  If the working file name is abcde,
  1340.          * the temp filename is _Ncde where N is a digit.
  1341.          */
  1342.         name += dl;
  1343.         if (*name) name++;
  1344.         if (*name) name++;
  1345.         VOID strcpy(tp, name);
  1346. #    endif
  1347.     dirtfmaker[n] = real;
  1348.     return np;
  1349. }
  1350.  
  1351.     void
  1352. dirtempunlink()
  1353. /* Clean up makedirtemp() files.  May be invoked by signal handler. */
  1354. {
  1355.     register int i;
  1356.     enum maker m;
  1357.  
  1358.     for (i = DIRTEMPNAMES;  0 <= --i;  )
  1359.         if ((m = dirtfmaker[i]) != notmade) {
  1360.         if (m == effective)
  1361.             seteid();
  1362.         VOID un_link(dirtfname[i].string);
  1363.         if (m == effective)
  1364.             setrid();
  1365.         dirtfmaker[i] = notmade;
  1366.         }
  1367. }
  1368.  
  1369.  
  1370.     int
  1371. #if has_prototypes
  1372. chnamemod(FILE **fromp, char const *from, char const *to, mode_t mode)
  1373.   /* The `#if has_prototypes' is needed because mode_t might promote to int.  */
  1374. #else
  1375.   chnamemod(fromp,from,to,mode) FILE **fromp; char const *from,*to; mode_t mode;
  1376. #endif
  1377. /*
  1378.  * Rename a file (with optional stream pointer *FROMP) from FROM to TO.
  1379.  * FROM already exists.
  1380.  * Change its mode to MODE, before renaming if possible.
  1381.  * If FROMP, close and clear *FROMP before renaming it.
  1382.  * Unlink TO if it already exists.
  1383.  * Return -1 on error (setting errno), 0 otherwise.
  1384.  */
  1385. {
  1386. #    if bad_a_rename
  1387.         /*
  1388.          * This host is brain damaged.  A race condition is possible
  1389.          * while the lock file is temporarily writable.
  1390.          * There doesn't seem to be a workaround.
  1391.          */
  1392.         mode_t mode_while_renaming = mode|S_IWUSR;
  1393. #    else
  1394. #        define mode_while_renaming mode
  1395. #    endif
  1396.     if (fromp) {
  1397. #        if has_fchmod
  1398.             if (fchmod(fileno(*fromp), mode_while_renaming) != 0)
  1399.                 return -1;
  1400. #        endif
  1401.         Ozclose(fromp);
  1402.     }
  1403. #    if has_fchmod
  1404.         else
  1405. #    endif
  1406.         if (chmod(from, mode_while_renaming) != 0)
  1407.         return -1;
  1408.  
  1409. #    if !has_rename || bad_b_rename
  1410.         VOID un_link(to);
  1411.         /*
  1412.          * We need not check the result;
  1413.          * link() or rename() will catch it.
  1414.          * No harm is done if TO does not exist.
  1415.          * However, there's a short window of inconsistency
  1416.          * during which TO does not exist.
  1417.          */
  1418. #    endif
  1419.  
  1420.     return
  1421. #        if !has_rename
  1422.         do_link(from,to) != 0  ?  -1  :  un_link(from)
  1423. #        else
  1424.             rename(from, to) != 0
  1425. #            if has_NFS
  1426.             && errno != ENOENT
  1427. #            endif
  1428.         ?  -1
  1429. #        if bad_a_rename
  1430.         :  mode != mode_while_renaming  ?  chmod(to, mode)
  1431. #        endif
  1432.         :  0
  1433. #        endif
  1434.     ;
  1435.  
  1436. #    undef mode_while_renaming
  1437. }
  1438.  
  1439.  
  1440.  
  1441.     int
  1442. findlock(delete, target)
  1443.     int delete;
  1444.     struct hshentry **target;
  1445. /*
  1446.  * Find the first lock held by caller and return a pointer
  1447.  * to the locked delta; also removes the lock if DELETE.
  1448.  * If one lock, put it into *TARGET.
  1449.  * Return 0 for no locks, 1 for one, 2 for two or more.
  1450.  */
  1451. {
  1452.     register struct lock *next, **trail, **found;
  1453.  
  1454.     found = 0;
  1455.     for (trail = &Locks;  (next = *trail);  trail = &next->nextlock)
  1456.         if (strcmp(getcaller(), next->login)  ==  0) {
  1457.             if (found) {
  1458.                 error("multiple revisions locked by %s; please specify one", getcaller());
  1459.                 return 2;
  1460.             }
  1461.             found = trail;
  1462.         }
  1463.     if (!found)
  1464.         return 0;
  1465.     next = *found;
  1466.     *target = next->delta;
  1467.     if (delete) {
  1468.         next->delta->lockedby = nil;
  1469.         *found = next->nextlock;
  1470.     }
  1471.     return 1;
  1472. }
  1473.  
  1474.     int
  1475. addlock(delta)
  1476.     struct hshentry * delta;
  1477. /*
  1478.  * Add a lock held by caller to DELTA and yield 1 if successful.
  1479.  * Print an error message and yield -1 if no lock is added because
  1480.  * DELTA is locked by somebody other than caller.
  1481.  * Return 0 if the caller already holds the lock.
  1482.  */
  1483. {
  1484.     register struct lock *next;
  1485.  
  1486.     next=Locks;
  1487.     for (next = Locks;  next;  next = next->nextlock)
  1488.         if (cmpnum(delta->num, next->delta->num) == 0)
  1489.             if (strcmp(getcaller(), next->login) == 0)
  1490.                 return 0;
  1491.             else {
  1492.                 error("revision %s already locked by %s",
  1493.                       delta->num, next->login
  1494.                 );
  1495.                 return -1;
  1496.             }
  1497.     next = ftalloc(struct lock);
  1498.     delta->lockedby = next->login = getcaller();
  1499.     next->delta = delta;
  1500.     next->nextlock = Locks;
  1501.     Locks = next;
  1502.     return 1;
  1503. }
  1504.  
  1505.  
  1506.     int
  1507. addsymbol(num, name, rebind)
  1508.     char const *num, *name;
  1509.     int rebind;
  1510. /*
  1511.  * Associate with revision NUM the new symbolic NAME.
  1512.  * If NAME already exists and REBIND is set, associate NAME with NUM;
  1513.  * otherwise, print an error message and return false;
  1514.  * Return true if successful.
  1515.  */
  1516. {
  1517.     register struct assoc *next;
  1518.  
  1519.     for (next = Symbols;  next;  next = next->nextassoc)
  1520.         if (strcmp(name, next->symbol)  ==  0)
  1521.             if (rebind  ||  strcmp(next->num,num) == 0) {
  1522.                 next->num = num;
  1523.                 return true;
  1524.             } else {
  1525.                 error("symbolic name %s already bound to %s",
  1526.                     name, next->num
  1527.                 );
  1528.                 return false;
  1529.             }
  1530.     next = ftalloc(struct assoc);
  1531.     next->symbol = name;
  1532.     next->num = num;
  1533.     next->nextassoc = Symbols;
  1534.     Symbols = next;
  1535.     return true;
  1536. }
  1537.  
  1538.  
  1539.  
  1540.     char const *
  1541. getcaller()
  1542. /* Get the caller's login name.  */
  1543. {
  1544. #    if has_setuid
  1545.         return getusername(euid()!=ruid());
  1546. #    else
  1547.         return getusername(false);
  1548. #    endif
  1549. }
  1550.  
  1551.  
  1552.     int
  1553. checkaccesslist()
  1554. /*
  1555.  * Return true if caller is the superuser, the owner of the
  1556.  * file, the access list is empty, or caller is on the access list.
  1557.  * Otherwise, print an error message and return false.
  1558.  */
  1559. {
  1560.     register struct access const *next;
  1561.  
  1562.     if (!AccessList || myself(RCSstat.st_uid) || strcmp(getcaller(),"root")==0)
  1563.         return true;
  1564.  
  1565.     next = AccessList;
  1566.     do {
  1567.         if (strcmp(getcaller(), next->login)  ==  0)
  1568.             return true;
  1569.     } while ((next = next->nextaccess));
  1570.  
  1571.     error("user %s not on the access list", getcaller());
  1572.     return false;
  1573. }
  1574.  
  1575.  
  1576.     int
  1577. dorewrite(lockflag, changed)
  1578.     int lockflag, changed;
  1579. /*
  1580.  * Do nothing if LOCKFLAG is zero.
  1581.  * Prepare to rewrite an RCS file if CHANGED is positive.
  1582.  * Stop rewriting if CHANGED is zero, because there won't be any changes.
  1583.  * Fail if CHANGED is negative.
  1584.  * Return true on success.
  1585.  */
  1586. {
  1587.     int r, e;
  1588.  
  1589.     if (lockflag)
  1590.         if (changed) {
  1591.             if (changed < 0)
  1592.                 return false;
  1593.             putadmin(frewrite);
  1594.             puttree(Head, frewrite);
  1595.             aprintf(frewrite, "\n\n%s%c", Kdesc, nextc);
  1596.             foutptr = frewrite;
  1597.         } else {
  1598.             Ozclose(&frewrite);
  1599.             seteid();
  1600.             ignoreints();
  1601.             r = un_link(newRCSfilename);
  1602.             e = errno;
  1603.             keepdirtemp(newRCSfilename);
  1604.             restoreints();
  1605.             setrid();
  1606.             if (r != 0) {
  1607.                 enerror(e, RCSfilename);
  1608.                 return false;
  1609.             }
  1610.         }
  1611.     return true;
  1612. }
  1613.  
  1614.     int
  1615. donerewrite(changed)
  1616.     int changed;
  1617. /*
  1618.  * Finish rewriting an RCS file if CHANGED is nonzero.
  1619.  * Return true on success.
  1620.  */
  1621. {
  1622.     int r, e;
  1623.  
  1624.     if (changed && !nerror) {
  1625.         if (finptr) {
  1626.             fastcopy(finptr, frewrite);
  1627.             Izclose(&finptr);
  1628.         }
  1629.         if (1 < RCSstat.st_nlink)
  1630.             warn("breaking hard link to %s", RCSfilename);
  1631.         seteid();
  1632.         ignoreints();
  1633.         r = chnamemod(&frewrite, newRCSfilename, RCSfilename,
  1634.             RCSstat.st_mode & ~(S_IWUSR|S_IWGRP|S_IWOTH)
  1635.         );
  1636.         e = errno;
  1637.         keepdirtemp(newRCSfilename);
  1638.         restoreints();
  1639.         setrid();
  1640.         if (r != 0) {
  1641.             enerror(e, RCSfilename);
  1642.             error("saved in %s", newRCSfilename);
  1643.             dirtempunlink();
  1644.             return false;
  1645.         }
  1646.     }
  1647.     return true;
  1648. }
  1649.  
  1650.     void
  1651. aflush(f)
  1652.     FILE *f;
  1653. {
  1654.     if (fflush(f) != 0)
  1655.         Oerror();
  1656. }
  1657.