1 /* Check in revisions of RCS files from working files.
2 
3    Copyright (C) 2010-2020 Thien-Thi Nguyen
4    Copyright (C) 1990, 1991, 1992, 1993, 1994, 1995 Paul Eggert
5    Copyright (C) 1982, 1988, 1989 Walter Tichy
6 
7    This file is part of GNU RCS.
8 
9    GNU RCS is free software: you can redistribute it and/or modify it
10    under the terms of the GNU General Public License as published by
11    the Free Software Foundation, either version 3 of the License, or
12    (at your option) any later version.
13 
14    GNU RCS is distributed in the hope that it will be useful,
15    but WITHOUT ANY WARRANTY; without even the implied warranty
16    of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17    See the GNU General Public License for more details.
18 
19    You should have received a copy of the GNU General Public License
20    along with this program.  If not, see <http://www.gnu.org/licenses/>.
21 */
22 
23 #include "base.h"
24 #include <string.h>
25 #include <errno.h>
26 #include <ctype.h>                      /* isdigit */
27 #include <stdlib.h>
28 #include <unistd.h>
29 #include "same-inode.h"
30 #include "stat-time.h"
31 #include "timespec.h"
32 #include "ci.help"
33 #include "b-complain.h"
34 #include "b-divvy.h"
35 #include "b-esds.h"
36 #include "b-excwho.h"
37 #include "b-fb.h"
38 #include "b-feph.h"
39 #include "b-fro.h"
40 #include "b-grok.h"
41 #include "b-isr.h"
42 #include "b-kwxout.h"
43 
44 struct reason
45 {
46   struct cbuf upfront;                  /* from -mMSG */
47   struct cbuf delayed;                  /* when the user is lazy */
48 };
49 
50 struct work
51 {
52   struct stat st;
53   struct fro *fro;
54   FILE *ex;                             /* expansion */
55 };
56 
57 struct bud                              /* new growth */
58 {
59   struct cbuf num;                      /* wip revision number */
60   struct delta d;                       /* to be inserted */
61   struct wlink br;                      /* branch to be inserted */
62   bool keep;
63   struct delta *target;
64   char getcurdate_buffer[DATESIZE];
65   struct timespec work_mtime;
66 };
67 
68 static void
cleanup(int * exitstatus,struct work * work)69 cleanup (int *exitstatus, struct work *work)
70 {
71   if (FLOW (erroneous))
72     *exitstatus = exit_failure;
73   fro_zclose (&FLOW (from));
74   fro_zclose (&work->fro);
75   Ozclose (&work->ex);
76   Ozclose (&FLOW (res));
77   ORCSclose ();
78   dirtempunlink ();
79 }
80 
81 #define ACCF(...)  accf (PLEXUS, __VA_ARGS__)
82 
83 #define OK(x)     (x)->string = finish_string (PLEXUS, &((x)->size))
84 #define JAM(x,s)  do { ACCF ("%s", s); OK (x); } while (0)
85 
86 #define ADD(x,s)  do                            \
87     {                                           \
88       accumulate_nbytes                         \
89         (PLEXUS, (x)->string, (x)->size);       \
90       JAM (x, s);                               \
91     }                                           \
92   while (0)
93 
94 static void
incnum(char const * onum,struct cbuf * nnum)95 incnum (char const *onum, struct cbuf *nnum)
96 /* Increment the last field of revision number ‘onum’
97    by one into a ‘PLEXUS’ string and point ‘nnum’ at it.  */
98 {
99   register char *tp, *np;
100   register size_t l;
101 
102   ACCF ("%s%c", onum, '\0');
103   np = finish_string (PLEXUS, &nnum->size);
104   nnum->string = np;
105   l = nnum->size - 1;
106   for (tp = np + l; np != tp;)
107     if (isdigit (*--tp))
108       {
109         if (*tp != '9')
110           {
111             ++*tp;
112             nnum->size--;
113             return;
114           }
115         *tp = '0';
116       }
117     else
118       {
119         tp++;
120         break;
121       }
122   /* We changed 999 to 000; now change it to 1000.  */
123   *tp = '1';
124   tp = np + l;
125   *tp++ = '0';
126   *tp = '\0';
127 }
128 
129 static int
removelock(struct delta * delta)130 removelock (struct delta *delta)
131 /* Find the lock held by caller on ‘delta’,
132    remove it, and return nonzero if successful.
133    Print an error message and return -1 if there is no such lock.
134    An exception is if ‘!strictly_locking’, and caller is the owner of
135    the RCS file.  If caller does not have a lock in this case,
136    return 0; return 1 if a lock is actually removed.  */
137 {
138   struct link box, *tp;
139   struct rcslock const *rl;
140   char const *num;
141 
142   num = delta->num;
143   box.next = GROK (locks);
144   if (! (tp = lock_delta_memq (&box, delta)))
145     {
146       if (!BE (strictly_locking) && stat_mine_p (&REPO (stat)))
147         return 0;
148       RERR ("no lock set by %s for revision %s", getcaller (), num);
149       return -1;
150     }
151   rl = tp->next->entry;
152   if (! caller_login_p (rl->login))
153     {
154       RERR ("revision %s locked by %s", num, rl->login);
155       return -1;
156     }
157   /* We found a lock on ‘delta’ by caller; delete it.  */
158   lock_drop (&box, tp);
159   return 1;
160 }
161 
162 static int
addbranch(struct delta * branchpoint,struct bud * bud,int removedlock,struct wlink ** tp_deltas)163 addbranch (struct delta *branchpoint, struct bud *bud,
164            int removedlock, struct wlink **tp_deltas)
165 /* Add a new branch and branch delta at ‘branchpoint’.
166    If ‘num’ is the null string, append the new branch, incrementing
167    the highest branch number (initially 1), and setting the level number to 1.
168    The new delta and branchhead are in ‘bud->d’ and ‘bud->br’, respectively.
169    The new number is placed into a ‘PLEXUS’ string with ‘num’ pointing to it.
170    Return -1 on error, 1 if a lock is removed, 0 otherwise.
171    If ‘removedlock’ is 1, a lock was already removed.  */
172 {
173   struct cbuf *num = &bud->num;
174   struct wlink **btrail;
175   struct delta *d;
176   int result;
177   int field, numlength;
178 
179   numlength = countnumflds (num->string);
180 
181   if (!branchpoint->branches)
182     {
183       /* Start first branch.  */
184       branchpoint->branches = &bud->br;
185       if (numlength == 0)
186         {
187           JAM (num, branchpoint->num);
188           ADD (num, ".1.1");
189         }
190       else if (ODDP (numlength))
191         ADD (num, ".1");
192       bud->br.next = NULL;
193     }
194   else if (numlength == 0)
195     {
196       struct wlink *bhead = branchpoint->branches;
197 
198       /* Append new branch to the end.  */
199       while (bhead->next)
200         bhead = bhead->next;
201       bhead->next = &bud->br;
202       d = bhead->entry;
203       incnum (BRANCHNO (d->num), num);
204       ADD (num, ".1");
205       bud->br.next = NULL;
206     }
207   else
208     {
209       /* Place the branch properly.  */
210       field = numlength - EVENP (numlength);
211       /* Field of branch number.  */
212       btrail = &branchpoint->branches;
213       while (d = (*btrail)->entry,
214              0 < (result = cmpnumfld (num->string, d->num, field)))
215         {
216           btrail = &(*btrail)->next;
217           if (!*btrail)
218             {
219               result = -1;
220               break;
221             }
222         }
223       if (result < 0)
224         {
225           /* Insert/append new branchhead.  */
226           bud->br.next = *btrail;
227           *btrail = &bud->br;
228           if (ODDP (numlength))
229             ADD (num, ".1");
230         }
231       else
232         {
233           /* Branch exists; append to end.  */
234           bud->target = gr_revno (BRANCHNO (num->string), tp_deltas);
235           if (!bud->target)
236             return -1;
237           if (!NUM_GT (num->string, bud->target->num))
238             {
239               RERR ("revision %s too low; must be higher than %s",
240                     num->string, bud->target->num);
241               return -1;
242             }
243           if (!removedlock && 0 <= (removedlock = removelock (bud->target)))
244             {
245               if (ODDP (numlength))
246                 incnum (bud->target->num, num);
247               bud->target->ilk = &bud->d;
248               bud->d.ilk = NULL;
249             }
250           return removedlock;
251           /* Don't do anything to ‘bud->br’.  */
252         }
253     }
254   bud->br.entry = &bud->d;
255   bud->d.ilk = NULL;
256   if (branchpoint->lockedby)
257     if (caller_login_p (branchpoint->lockedby))
258       return removelock (branchpoint);  /* This returns 1.  */
259   return removedlock;
260 }
261 
262 static void
prune(struct delta * wrong,struct delta * bp)263 prune (struct delta *wrong, struct delta *bp)
264 /* Remove reference to ‘wrong’ from the tree, starting the
265    search at ‘bp’.  As a side effect, clear ‘wrong->selector’.  */
266 {
267   struct wlink box, *tp;
268   struct delta *d;
269   int same = countnumflds (wrong->num) - 2;
270 
271   /* Unselecting ‘wrong’ is not strictly necessary,
272      but doing so is cleaner.  */
273   wrong->selector = false;
274 
275   /* On the trunk, no one points to ‘wrong’, so we are finished.  */
276   if (0 >= same)
277     return;
278 
279   /* If ‘wrong’ is the ‘bp’ successor, simply forget it.  */
280   if (wrong == bp->ilk)
281     {
282        bp->ilk = NULL;
283        return;
284     }
285 
286   /* If ‘wrong’ is the only revision on a branch, delete that branch.  */
287   box.next = bp->branches;
288   for (tp = &box; tp->next; tp = tp->next)
289     if (wrong == (d = tp->next->entry))
290       {
291         tp->next = tp->next->next;
292         bp->branches = box.next;
293         return;
294       }
295 
296   /* Otherwise, it must be on normal chain.  */
297   for (tp = bp->branches; tp; tp = tp->next)
298     {
299       d = tp->entry;
300       if (0 == compartial (wrong->num, d->num, same))
301         {
302           while (d->ilk != wrong)
303             d = d->ilk;
304           d->ilk = NULL;
305           return;
306         }
307     }
308 
309   /* Should never get here.  */
310   abort ();
311 }
312 
313 static int
addelta(struct wlink ** tp_deltas,struct bud * bud,bool rcsinitflag)314 addelta (struct wlink **tp_deltas, struct bud *bud, bool rcsinitflag)
315 /* Append a delta to the delta tree, whose number is given by ‘bud->num’.
316    Update ‘REPO (tip)’, ‘bud->num’ and the links in ‘bud->d’.
317    Return -1 on error, 1 if a lock is removed, 0 otherwise.  */
318 {
319   register char const *tp;
320   register int i;
321   int removedlock;
322   int newdnumlength;            /* actual length of new rev. num. */
323   struct delta *tip = REPO (tip);
324   char const *defbr = GROK (branch);
325 
326   newdnumlength = countnumflds (bud->num.string);
327 
328   if (rcsinitflag)
329     {
330       /* This covers non-existing RCS file,
331          and a file initialized with ‘rcs -i’.  */
332       if (newdnumlength == 0 && defbr)
333         {
334           JAM (&bud->num, defbr);
335           newdnumlength = countnumflds (defbr);
336         }
337       if (newdnumlength == 0)
338         JAM (&bud->num, "1.1");
339       else if (newdnumlength == 1)
340         ADD (&bud->num, ".1");
341       else if (newdnumlength > 2)
342         {
343           RERR ("Branch point doesn't exist for revision %s.",
344                 bud->num.string);
345           return -1;
346         }
347       /* (‘newdnumlength’ == 2 is OK.)  */
348       tip = REPO (tip) = &bud->d;
349       bud->d.ilk = NULL;
350       return 0;
351     }
352   if (newdnumlength == 0)
353     {
354       /* Derive new revision number from locks.  */
355       switch (findlock (true, &bud->target))
356         {
357 
358         default:
359           /* Found two or more old locks.  */
360           return -1;
361 
362         case 1:
363           /* Found an old lock.  Check whether locked revision exists.  */
364           if (!gr_revno (bud->target->num, tp_deltas))
365             return -1;
366           if (bud->target == tip)
367             {
368               /* Make new head.  */
369               bud->d.ilk = tip;
370               tip = REPO (tip) = &bud->d;
371             }
372           else if (!bud->target->ilk && countnumflds (bud->target->num) > 2)
373             {
374               /* New tip revision on side branch.  */
375               bud->target->ilk = &bud->d;
376               bud->d.ilk = NULL;
377             }
378           else
379             {
380               /* Middle revision; start a new branch.  */
381               JAM (&bud->num, "");
382               return addbranch (bud->target, bud, true, tp_deltas);
383             }
384           incnum (bud->target->num, &bud->num);
385           /* Successful use of existing lock.  */
386           return 1;
387 
388         case 0:
389           /* No existing lock; try ‘defbr’.  Update ‘bud->num’.  */
390           if (BE (strictly_locking) || !stat_mine_p (&REPO (stat)))
391             {
392               RERR ("no lock set by %s", getcaller ());
393               return -1;
394             }
395           if (defbr)
396             JAM (&bud->num, defbr);
397           else
398             {
399               incnum (tip->num, &bud->num);
400             }
401           newdnumlength = countnumflds (bud->num.string);
402           /* Now fall into next statement.  */
403         }
404     }
405   if (newdnumlength <= 2)
406     {
407       /* Add new head per given number.  */
408       if (newdnumlength == 1)
409         {
410           /* Make a two-field number out of it.  */
411           if (NUMF_EQ (1, bud->num.string, tip->num))
412             incnum (tip->num, &bud->num);
413           else
414             ADD (&bud->num, ".1");
415         }
416       if (!NUM_GT (bud->num.string, tip->num))
417         {
418           RERR ("revision %s too low; must be higher than %s",
419                 bud->num.string, tip->num);
420           return -1;
421         }
422       bud->target = tip;
423       if (0 <= (removedlock = removelock (tip)))
424         {
425           if (!gr_revno (tip->num, tp_deltas))
426             return -1;
427           bud->d.ilk = tip;
428           tip = REPO (tip) = &bud->d;
429         }
430       return removedlock;
431     }
432   else
433     {
434       struct cbuf old = bud->num;       /* sigh */
435 
436       /* Put new revision on side branch.  First, get branch point.  */
437       tp = old.string;
438       for (i = newdnumlength - EVENP (newdnumlength); --i;)
439         while (*tp++ != '.')
440           continue;
441       /* Ignore rest to get old delta.  */
442       old.string = SHSNIP (&old.size, old.string, tp - 1);
443       if (! (bud->target = gr_revno (old.string, tp_deltas)))
444         return -1;
445       if (!NUM_EQ (bud->target->num, old.string))
446         {
447           RERR ("can't find branch point %s", old.string);
448           return -1;
449         }
450       return addbranch (bud->target, bud, false, tp_deltas);
451     }
452 }
453 
454 static bool
addsyms(char const * num,struct link * ls)455 addsyms (char const *num, struct link *ls)
456 {
457   struct u_symdef const *ud;
458 
459   for (; ls; ls = ls->next)
460     {
461       ud = ls->entry;
462 
463       if (addsymbol (num, ud->u.meaningful, ud->override) < 0)
464         return false;
465     }
466   return true;
467 }
468 
469 static char const *
getcurdate(struct bud * bud)470 getcurdate (struct bud *bud)
471 /* Return a pointer to the current date.  */
472 {
473   if (!bud->getcurdate_buffer[0])
474     time2date (BE (now.tv_sec), bud->getcurdate_buffer);
475   return bud->getcurdate_buffer;
476 }
477 
478 static int
fixwork(mode_t newworkmode,const struct timespec mtime,struct work * work)479 fixwork (mode_t newworkmode, const struct timespec mtime, struct work *work)
480 {
481   char const *mani_filename = MANI (filename);
482   struct stat *st = &work->st;
483 
484   return
485     (1 < st->st_nlink
486      || (newworkmode & S_IWUSR && !stat_mine_p (st))
487      || PROB (setmtime (mani_filename, mtime)))
488     ? -1
489     : (st->st_mode == newworkmode
490        ? 0
491        : (!PROB (change_mode (work->fro->fd, newworkmode))
492           ? 0
493           : chmod (mani_filename, newworkmode)));
494 }
495 
496 static int
xpandfile(struct work * work,struct delta const * delta,char const ** exname,bool dolog)497 xpandfile (struct work *work, struct delta const *delta,
498            char const **exname, bool dolog)
499 /* Read ‘work->fro’ and copy it to a file, performing keyword
500    substitution with data from ‘delta’.
501    Return -1 if unsuccessful, 1 if expansion occurred, 0 otherwise.
502    If successful, store the name into ‘*exname’.  */
503 {
504   char const *targetname;
505   int e, r;
506 
507   targetname = makedirtemp (true);
508   if (!(work->ex = fopen_safer (targetname, FOPEN_W_WORK)))
509     {
510       syserror_errno (targetname);
511       MERR ("can't build working file");
512       return -1;
513     }
514   r = 0;
515   if (MIN_UNEXPAND <= BE (kws))
516     fro_spew (work->fro, work->ex);
517   else
518     {
519       struct expctx ctx = EXPCTX_1OUT (work->ex, work->fro, false, dolog);
520 
521       for (;;)
522         {
523           e = expandline (&ctx);
524           if (e < 0)
525             break;
526           r |= e;
527           if (e <= 1)
528             break;
529         }
530       FINISH_EXPCTX (&ctx);
531     }
532   *exname = targetname;
533   return ODDP (r);
534 }
535 
536 /* --------------------- G E T L O G M S G --------------------------------*/
537 
538 #define FIRST  "Initial revision"
539 
540 static struct cbuf
getlogmsg(struct reason * reason,struct bud * bud)541 getlogmsg (struct reason *reason, struct bud *bud)
542 /* Obtain and return a log message.
543    If a log message is given with ‘-m’, return that message.
544    If this is the initial revision, return a standard log message.
545    Otherwise, read a character string from the terminal.
546    Stop after reading EOF or a single '.' on a line.
547    Prompt the first time called for the log message; during all
548    later calls ask whether the previous log message can be reused.  */
549 {
550   const char *num;
551 
552   if (reason->upfront.size)
553     return reason->upfront;
554 
555   if (bud->keep)
556     {
557       char datebuf[FULLDATESIZE];
558 
559       /* Generate standard log message.  */
560       date2str (getcurdate (bud), datebuf);
561       ACCF ("%s%s at %s", TINYKS (ciklog), getcaller (), datebuf);
562       OK (&reason->delayed);
563       return reason->delayed;
564     }
565 
566   if (!bud->target
567       && bud->num.size
568       && (num = bud->num.string)
569       && (NUM_EQ (num, "1.1")
570           || NUM_EQ (num, "1.0")))
571     {
572       struct cbuf const initiallog =
573         {
574           .string = FIRST,
575           .size = sizeof (FIRST) - 1
576         };
577 
578       return initiallog;
579     }
580 
581   if (reason->delayed.size)
582     {
583       /*Previous log available.  */
584       if (yesorno (true, "reuse log message of previous file"))
585         return reason->delayed;
586     }
587 
588   /* Now read string from stdin.  */
589   reason->delayed = getsstdin ("m", "log message", "");
590 
591   /* Now check whether the log message is not empty.  */
592   if (!reason->delayed.size)
593     set_empty_log_message (&reason->delayed);
594   return reason->delayed;
595 }
596 
597 static char const *
first_meaningful_symbolic_name(struct link * ls)598 first_meaningful_symbolic_name (struct link *ls)
599 {
600   struct u_symdef const *ud;
601 
602   /* Find last link so that, e.g., "-nA -nB -nC" yields "A".
603      See: (search-forward "symbolic_names = prepend")  */
604   while (ls && ls->next)
605     ls = ls->next;
606 
607   ud = ls->entry;
608   return ud->u.meaningful;
609 }
610 
611 DECLARE_PROGRAM (ci, BOG_FULL);
612 
613 static int
ci_main(const char * cmd,int argc,char ** argv)614 ci_main (const char *cmd, int argc, char **argv)
615 {
616   int exitstatus = EXIT_SUCCESS;
617   struct reason reason;
618   char altdate[DATESIZE];
619   char olddate[DATESIZE];
620   char newdatebuf[FULLDATESIZE];
621   char targetdatebuf[FULLDATESIZE];
622   char *a, **newargv, *textfile;
623   char const *author, *krev, *rev, *state;
624   char const *diffname, *expname;
625   char const *newworkname;
626   struct work work = { .ex = NULL };
627   bool forceciflag = false;
628   bool keepworkingfile = false;
629   bool rcsinitflag = false;
630   bool initflag, mustread;
631   bool lockflag, lockthis, mtimeflag;
632   int removedlock;
633   bool Ttimeflag;
634   int r;
635   int changedRCS, changework;
636   bool dolog, newhead;
637   bool usestatdate;             /* Use mod time of file for -d.  */
638   mode_t newworkmode;           /* mode for working file */
639   struct timespec mtime, wtime;
640   struct bud bud;
641   struct delta *workdelta;
642   struct link *symbolic_names = NULL;
643   struct wlink *deltas;                 /* Deltas to be generated.  */
644 
645   CHECK_HV (cmd);
646   gnurcs_init (&program);
647 
648   /* This lameness is because constructing a proper initialization form for
649      ‘struct bud’ is too much hassle.  We do it here, after the ‘gnurcs_init’
650      instead of before, closer to the declaration (as would be more indicative
651      of its role) because perhaps Real Soon Now But Not Quite Yet ‘bud’ will
652      be changed to be heap-allocated (probably in ‘PLEXUS’), and this is the
653      place to do that.  */
654   memset (&bud, 0, sizeof (struct bud));
655   /* Likewise.  */
656   memset (&reason, 0, sizeof (reason));
657 
658   setrid ();
659 
660   author = rev = state = textfile = NULL;
661   initflag = lockflag = mustread = false;
662   mtimeflag = false;
663   Ttimeflag = false;
664   altdate[0] = '\0';            /* empty alternate date for -d */
665   usestatdate = false;
666 
667   argc = getRCSINIT (argc, argv, &newargv);
668   argv = newargv;
669   while (a = *++argv, 0 < --argc && *a++ == '-')
670     {
671       switch (*a++)
672         {
673 
674         case 'r':
675           if (*a)
676             goto revno;
677           keepworkingfile = lockflag = false;
678           break;
679 
680         case 'l':
681           keepworkingfile = lockflag = true;
682         revno:
683           chk_set_rev (&rev, a);
684           break;
685 
686         case 'u':
687           keepworkingfile = true;
688           lockflag = false;
689           goto revno;
690 
691         case 'i':
692           initflag = true;
693           goto revno;
694 
695         case 'j':
696           mustread = true;
697           goto revno;
698 
699         case 'I':
700           BE (interactive) = true;
701           goto revno;
702 
703         case 'q':
704           BE (quiet) = true;
705           goto revno;
706 
707         case 'f':
708           forceciflag = true;
709           goto revno;
710 
711         case 'k':
712           bud.keep = true;
713           goto revno;
714 
715         case 'm':
716           if (reason.upfront.size)
717             redefined ('m');
718           reason.upfront = cleanlogmsg (a, strlen (a));
719           if (!reason.upfront.size)
720             set_empty_log_message (&reason.upfront);
721           break;
722 
723         case 'n':
724         case 'N':
725           {
726             char option = a[-1];
727             struct u_symdef *ud;
728 
729             if (!*a)
730               {
731                 PERR ("missing symbolic name after -%c", option);
732                 break;
733               }
734             checkssym (a);
735             ud = ZLLOC (1, struct u_symdef);
736             ud->override = ('N' == option);
737             ud->u.meaningful = a;
738             symbolic_names = prepend (ud, symbolic_names, PLEXUS);
739           }
740           break;
741 
742         case 's':
743           if (*a)
744             {
745               if (state)
746                 redefined ('s');
747               checksid (a);
748               state = a;
749             }
750           else
751             PERR ("missing state for -s option");
752           break;
753 
754         case 't':
755           if (*a)
756             {
757               if (textfile)
758                 redefined ('t');
759               textfile = a;
760             }
761           break;
762 
763         case 'd':
764           if (altdate[0] || usestatdate)
765             redefined ('d');
766           altdate[0] = '\0';
767           if (!(usestatdate = !*a))
768             str2date (a, altdate);
769           break;
770 
771         case 'M':
772           mtimeflag = true;
773           goto revno;
774 
775         case 'w':
776           if (*a)
777             {
778               if (author)
779                 redefined ('w');
780               checksid (a);
781               author = a;
782             }
783           else
784             PERR ("missing author for -w option");
785           break;
786 
787         case 'x':
788           BE (pe) = a;
789           break;
790 
791         case 'V':
792           setRCSversion (*argv);
793           break;
794 
795         case 'z':
796           zone_set (a);
797           break;
798 
799         case 'T':
800           if (!*a)
801             {
802               Ttimeflag = true;
803               break;
804             }
805           /* fall into */
806         default:
807           bad_option (*argv);
808         };
809     }
810   /* (End processing of options.)  */
811 
812   /* Handle all filenames.  */
813   if (FLOW (erroneous))
814     cleanup (&exitstatus, &work);
815   else if (argc < 1)
816     PFATAL ("no input file");
817   else
818     for (; 0 < argc; cleanup (&exitstatus, &work), ++argv, --argc)
819       {
820         /* Use var instead of simple #define for fast identity compare.  */
821         char const *default_state = DEFAULTSTATE;
822         char const *mani_filename, *pv;
823         struct fro *from;
824         struct stat *repo_stat;
825         struct timespec fs_mtime;
826         FILE *frew;
827         struct delta *tip;
828         int kws;
829         struct cbuf newdesc =
830           {
831             .string = NULL,
832             .size = 0
833           };
834 
835         bud.target = NULL;
836         ffree ();
837 
838         switch (pairnames (argc, argv, rcswriteopen, mustread, false))
839           {
840 
841           case -1:
842             /* New RCS file.  */
843             if (currently_setuid_p ())
844               {
845                 MERR
846                   ("setuid initial checkin prohibited; use `rcs -i -a' first");
847                 continue;
848               }
849             rcsinitflag = true;
850             break;
851 
852           case 0:
853             /* Error.  */
854             continue;
855 
856           case 1:
857             /* Normal checkin with previous RCS file.  */
858             if (initflag)
859               {
860                 RERR ("already exists");
861                 continue;
862               }
863             rcsinitflag = !(tip = REPO (tip));
864           }
865 
866         /* ‘REPO (filename)’ contains the name of the RCS file,
867            and ‘MANI (filename)’ contains the name of the working file.
868            If the RCS file exists, ‘FLOW (from)’ contains the file
869            descriptor for the RCS file, and ‘REPO (stat)’ is set.
870            The admin node is initialized.  */
871         mani_filename = MANI (filename);
872         from = FLOW (from);
873         repo_stat = &REPO (stat);
874         kws = BE (kws);
875 
876         diagnose ("%s  <--  %s", REPO (filename), mani_filename);
877 
878         if (!(work.fro = fro_open (mani_filename, FOPEN_R_WORK, &work.st)))
879           {
880             syserror_errno (mani_filename);
881             continue;
882           }
883 
884         if (from)
885           {
886             if (SAME_INODE (REPO (stat), work.st))
887               {
888                 RERR ("RCS file is the same as working file %s.",
889                       mani_filename);
890                 continue;
891               }
892             if (!checkaccesslist ())
893               continue;
894           }
895 
896         krev = rev;
897         if (bud.keep)
898           {
899             /* Get keyword values from working file.  */
900             if (!getoldkeys (work.fro))
901               continue;
902             if (!rev && !(krev = PREV (rev)))
903               {
904                 MERR ("can't find a %s", ks_revno);
905                 continue;
906               }
907             if (!PREV (date) && *altdate == '\0' && usestatdate == false)
908               MWARN ("can't find a date");
909             if (!PREV (author) && !author)
910               MWARN ("can't find an author");
911             if (!PREV (state) && !state)
912               MWARN ("can't find a state");
913           }
914 
915         /* Expand symbolic revision number.  */
916         if (!fully_numeric (&bud.num, krev, work.fro))
917           continue;
918 
919         /* Splice new delta into tree.  */
920         if (PROB (removedlock = addelta (&deltas, &bud, rcsinitflag)))
921           continue;
922         tip = REPO (tip);
923 
924         bud.d.num = bud.num.string;
925         bud.d.branches = NULL;
926         /* This might be changed by ‘addlock’.  */
927         bud.d.lockedby = NULL;
928         bud.d.selector = true;
929         bud.d.name = NULL;
930 
931         /* Set author.  */
932         bud.d.author =
933           /* Given by ‘-w’.  */
934           (author
935            ? author
936            /* Preserve old author if possible.  */
937            : (bud.keep && (pv = PREV (author))
938               ? pv
939               /* Otherwise use caller's id.  */
940               : getcaller ()));
941 
942         /* Set state.  */
943         bud.d.state =
944           /* Given by ‘-s’.  */
945           (state
946            ? state
947            /* Preserve old state if possible.  */
948            : (bud.keep && (pv = PREV (state))
949               ? pv
950               /* default */
951               : default_state));
952 
953         /* Compute date.  */
954         bud.work_mtime = get_stat_mtime (&work.st);
955         if (usestatdate)
956           {
957             time2date (work.st.st_mtime, altdate);
958           }
959         if (*altdate != '\0')
960           /* Given by ‘-d’.  */
961           bud.d.date = altdate;
962         else if (bud.keep && (pv = PREV (date)))
963           {
964             /* Preserve old date if possible.  */
965             str2date (pv, olddate);
966             bud.d.date = olddate;
967           }
968         else
969           /* Use current date.  */
970           bud.d.date = getcurdate (&bud);
971         /* Now check validity of date -- needed because of ‘-d’ and ‘-k’.  */
972         if (bud.target && DATE_LT (bud.d.date, bud.target->date))
973           {
974             RERR ("Date %s precedes %s in revision %s.",
975                   date2str (bud.d.date, newdatebuf),
976                   date2str (bud.target->date, targetdatebuf),
977                   bud.target->num);
978             continue;
979           }
980 
981         if (lockflag && addlock (&bud.d, true) < 0)
982           continue;
983 
984         if (bud.keep && (pv = PREV (name)))
985           if (addsymbol (bud.d.num, pv, false) < 0)
986             continue;
987         if (!addsyms (bud.d.num, symbolic_names))
988           continue;
989 
990         putadmin ();
991         frew = FLOW (rewr);
992         puttree (tip, frew);
993         putdesc (&newdesc, false, textfile);
994 
995         changework = kws < MIN_UNCHANGED_EXPAND;
996         dolog = true;
997         lockthis = lockflag;
998         workdelta = &bud.d;
999 
1000         /* Build rest of file.  */
1001         if (rcsinitflag)
1002           {
1003             diagnose ("initial revision: %s", bud.d.num);
1004             /* Get logmessage.  */
1005             bud.d.pretty_log = getlogmsg (&reason, &bud);
1006             putdftext (&bud.d, work.fro, frew, false);
1007             repo_stat->st_mode = work.st.st_mode;
1008             repo_stat->st_nlink = 0;
1009             changedRCS = true;
1010             if (from)
1011               IGNORE_REST (from);
1012           }
1013         else
1014           {
1015             diffname = maketemp (0);
1016             newhead = tip == &bud.d;
1017             if (!newhead)
1018               FLOW (to) = frew;
1019             expname = buildrevision (deltas, bud.target, NULL, false);
1020             if (!forceciflag
1021                 && STR_SAME (bud.d.state, bud.target->state)
1022                 && ((changework = rcsfcmp (work.fro, &work.st, expname,
1023                                            bud.target))
1024                     <= 0))
1025               {
1026                 diagnose
1027                   ("file is unchanged; reverting to previous revision %s",
1028                    bud.target->num);
1029                 if (removedlock < lockflag)
1030                   {
1031                     diagnose
1032                       ("previous revision was not locked; ignoring -l option");
1033                     lockthis = 0;
1034                   }
1035                 dolog = false;
1036                 if (!(changedRCS = lockflag < removedlock || symbolic_names))
1037                   {
1038                     workdelta = bud.target;
1039                     SAME_AFTER (from, bud.target->text);
1040                   }
1041                 else
1042                   /* We have started to build the wrong new RCS file.
1043                      Start over from the beginning.  */
1044                   {
1045                     off_t hwm = ftello (frew);
1046                     bool bad_truncate;
1047 
1048                     rewind (frew);
1049                     bad_truncate = PROB (ftruncate (fileno (frew), (off_t) 0));
1050                     grok_resynch (REPO (r));
1051 
1052                     /* The ‘bud.d’ might still be linked in the tree,
1053                        so prune it now.  (Unfortunately, ‘grok_resynch’
1054                        did not restore the tree completely, as its name
1055                        might imply.)  */
1056                     prune (&bud.d, bud.target);
1057 
1058                     if (! (workdelta = gr_revno (bud.target->num, &deltas)))
1059                       continue;
1060                     workdelta->pretty_log = bud.target->pretty_log;
1061                     if (bud.d.state != default_state)
1062                       workdelta->state = bud.d.state;
1063                     if (lockthis < removedlock && removelock (workdelta) < 0)
1064                       continue;
1065                     if (!addsyms (workdelta->num, symbolic_names))
1066                       continue;
1067                     if (PROB (dorewrite (true, true)))
1068                       continue;
1069                     VERBATIM (from, GROK (neck));
1070                     fro_spew (from, frew);
1071                     if (bad_truncate)
1072                       while (ftello (frew) < hwm)
1073                         /* White out any earlier mistake with '\n's.
1074                            This is unlikely.  */
1075                         newline (frew);
1076                   }
1077               }
1078             else
1079               {
1080                 int wfd = work.fro->fd;
1081                 struct stat checkworkstat;
1082                 char const *diffv[6 + !!OPEN_O_BINARY], **diffp;
1083 
1084                 diagnose ("new revision: %s; previous revision: %s",
1085                           bud.d.num, bud.target->num);
1086                 SAME_AFTER (from, bud.target->text);
1087                 bud.d.pretty_log = getlogmsg (&reason, &bud);
1088 
1089 		/* Make sure diff(1) reads from the beginning.  */
1090                 if (PROB (lseek (wfd, 0, SEEK_SET)))
1091                   Ierror ();
1092 
1093                 diffp = diffv;
1094                 *++diffp = prog_diff;
1095                 *++diffp = diff_flags;
1096                 if (OPEN_O_BINARY
1097                     && kws == kwsub_b)
1098                   *++diffp = "--binary";
1099                 *++diffp = newhead ? "-" : expname;
1100                 *++diffp = newhead ? expname : "-";
1101                 *++diffp = NULL;
1102                 if (DIFF_TROUBLE == runv (wfd, diffname, diffv))
1103                   RFATAL ("diff failed");
1104 
1105                 /* "Rewind" ‘work.fro’ only after feeding it to
1106 		   diff(1).  This is needed to keep the stream
1107 		   buffer state in sync with the fd. */
1108                 fro_bob (work.fro);
1109 
1110                 if (newhead)
1111                   {
1112                     fro_bob (work.fro);
1113                     putdftext (&bud.d, work.fro, frew, false);
1114                     if (!putdtext (bud.target, diffname, frew, true))
1115                       continue;
1116                   }
1117                 else if (!putdtext (&bud.d, diffname, frew, true))
1118                   continue;
1119 
1120                 /* Check whether the working file changed during checkin,
1121                    to avoid producing an inconsistent RCS file.  */
1122                 if (PROB (fstat (wfd, &checkworkstat))
1123                     || 0 != timespec_cmp (get_stat_mtime (&checkworkstat),
1124                                           bud.work_mtime)
1125                     || work.st.st_size != checkworkstat.st_size)
1126                   {
1127                     MERR ("file changed during checkin");
1128                     continue;
1129                   }
1130 
1131                 changedRCS = true;
1132               }
1133           }
1134 
1135         /* Deduce timestamp of new revision if it is needed later.  */
1136         wtime = (mtimeflag | Ttimeflag)
1137           ? (usestatdate
1138              ? bud.work_mtime
1139              : make_timespec (date2time (workdelta->date),
1140                               ZERO_NANOSECONDS))
1141           : unspecified_timespec ();
1142 
1143         if (Ttimeflag)
1144           fs_mtime = file_mtime (from, repo_stat);
1145 
1146         if (PROB (donerewrite (changedRCS, !Ttimeflag
1147                                ? unspecified_timespec ()
1148                                : (PROB (timespec_cmp (wtime, fs_mtime))
1149                                   /* File is newer.  */
1150                                   ? fs_mtime
1151                                   /* Delta is newer.  */
1152                                   : wtime))))
1153           continue;
1154 
1155         if (!keepworkingfile)
1156           {
1157             fro_zclose (&work.fro);
1158             /* Get rid of old file.  */
1159             r = un_link (mani_filename);
1160           }
1161         else
1162           {
1163             newworkmode = WORKMODE (repo_stat->st_mode,
1164                                     !(kws == kwsub_v
1165                                       || lockthis < BE (strictly_locking)));
1166             mtime = mtimeflag
1167               ? wtime
1168               : unspecified_timespec ();
1169 
1170             /* Expand if it might change or if we can't fix mode, time.  */
1171             if (changework || PROB (r = fixwork (newworkmode, mtime, &work)))
1172               {
1173                 fro_bob (work.fro);
1174                 /* Expand keywords in file.  */
1175                 BE (inclusive_of_Locker_in_Id_val) = lockthis;
1176                 workdelta->name =
1177                   namedrev (symbolic_names
1178                             ? first_meaningful_symbolic_name (symbolic_names)
1179                             : (bud.keep && (pv = PREV (name))
1180                                ? pv
1181                                : rev),
1182                             workdelta);
1183                 switch (xpandfile (&work, workdelta, &newworkname, dolog))
1184                   {
1185                   default:
1186                     continue;
1187 
1188                   case 0:
1189                     /* No expansion occurred; try to reuse working file
1190                        unless we already tried and failed.  */
1191                     if (changework)
1192                       if ((r = fixwork (newworkmode, mtime, &work)) == 0)
1193                         break;
1194                     /* fall into */
1195                   case 1:
1196                     fro_zclose (&work.fro);
1197                     aflush (work.ex);
1198                     IGNOREINTS ();
1199                     r = chnamemod (&work.ex, newworkname, mani_filename,
1200                                    1, newworkmode, mtime);
1201                     keepdirtemp (newworkname);
1202                     RESTOREINTS ();
1203                   }
1204               }
1205           }
1206         if (PROB (r))
1207           {
1208             syserror_errno (mani_filename);
1209             continue;
1210           }
1211         diagnose ("done");
1212 
1213       }
1214 
1215   tempunlink ();
1216   gnurcs_goodbye ();
1217   return exitstatus;
1218 }
1219 
1220 static const uint8_t ci_aka[19] =
1221 {
1222   3 /* count */,
1223   2,'c','i',
1224   7,'c','h','e','c','k','i','n',
1225   6,'c','o','m','m','i','t'
1226 };
1227 
1228 YET_ANOTHER_COMMAND (ci);
1229 
1230 /*:help
1231 [options] file...
1232 Options:
1233   -f[REV]       Force new entry, even if no content changed.
1234   -I[REV]       Interactive.
1235   -i[REV]       Initial checkin; error if RCS file already exists.
1236   -j[REV]       Just checkin, don't init; error if RCS file does not exist.
1237   -k[REV]       Compute revision from working file keywords.
1238   -q[REV]       Quiet mode.
1239   -r[REV]       Do normal checkin, if REV is specified;
1240                 otherwise, release lock and delete working file.
1241   -l[REV]       Like -r, but immediately checkout locked (co -l) afterwards.
1242   -u[REV]       Like -l, but checkout unlocked (co -u).
1243   -M[REV]       Reset working file mtime (relevant for -l, -u).
1244   -d[DATE]      Use DATE (or working file mtime).
1245   -mMSG         Use MSG as the log message.
1246   -nNAME        Assign symbolic NAME to the entry; NAME must be new.
1247   -NNAME        Like -n, but overwrite any previous assignment.
1248   -sSTATE       Set state to STATE.
1249   -t-TEXT       Set description to TEXT.
1250   -tFILENAME    Set description from text read from FILENAME.
1251   -T            Set the RCS file's modification time to the new
1252                 revision's time if the former precedes the latter and there
1253                 is a new revision; preserve the RCS file's modification
1254                 time otherwise.
1255   -V            Obsolete; do not use.
1256   -VN           Emulate RCS version N.
1257   -wWHO         Use WHO as the author.
1258   -xSUFF        Specify SUFF as a slash-separated list of suffixes
1259                 used to identify RCS file names.
1260   -zZONE        Specify date output format in keyword-substitution
1261                 and also the default timezone for -dDATE.
1262 
1263 Multiple flags in {fiIjklMqru} may be used, except for -r, -l, -u, which are
1264 mutually exclusive.  If specified, REV can be symbolic, numeric, or mixed:
1265   symbolic      Must have been defined previously (see -n, -N).
1266   $             Determine from keyword values in the working file.
1267   .N            Prepend default branch => DEFBR.N
1268   BR.N          Use this, but N must be greater than any existing
1269                 on BR, or BR must be new.
1270   BR            Latest rev on branch BR + 1 => BR.(L+1), or BR.1 if new branch.
1271 If REV is omitted, compute it from the last lock (co -l), perhaps
1272 starting a new branch.  If there is no lock, use DEFBR.(L+1).
1273 */
1274 
1275 /* ci.c ends here */
1276