home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 22 gnu / 22-gnu.zip / rcs57pc3.zip / rcs / src / rcsedit.c < prev    next >
C/C++ Source or Header  |  1999-03-14  |  50KB  |  1,951 lines

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