Add the DragonFly cvs id and perform general cleanups on cvs/rcs/sccs ids. Most
[dragonfly.git] / gnu / usr.bin / rcs / ci / ci.c
1 /* Check in revisions of RCS files from working files.  */
2
3 /* Copyright 1982, 1988, 1989 Walter Tichy
4    Copyright 1990, 1991, 1992, 1993, 1994, 1995 Paul Eggert
5    Distributed under license by the Free Software Foundation, Inc.
6
7 This file is part of RCS.
8
9 RCS is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2, or (at your option)
12 any later version.
13
14 RCS is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with RCS; see the file COPYING.
21 If not, write to the Free Software Foundation,
22 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23
24 Report problems and direct all questions to:
25
26     rcs-bugs@cs.purdue.edu
27
28 */
29
30 /*
31  * $FreeBSD: src/gnu/usr.bin/rcs/ci/ci.c,v 1.7 1999/08/27 23:36:38 peter Exp $
32  * $DragonFly: src/gnu/usr.bin/rcs/ci/ci.c,v 1.2 2003/06/17 04:25:47 dillon Exp $
33  *
34  * Revision 5.30  1995/06/16 06:19:24  eggert
35  * Update FSF address.
36  *
37  * Revision 5.29  1995/06/01 16:23:43  eggert
38  * (main): Add -kb.
39  * Use `cmpdate', not `cmpnum', to compare dates.
40  * This is for MKS RCS's incompatible 20th-century date format.
41  * Don't worry about errno after ftruncate fails.
42  * Fix input file rewinding bug when large_memory && !maps_memory
43  * and checking in a branch tip.
44  *
45  * (fixwork): Fall back on chmod if fchmod fails, since it might be ENOSYS.
46  *
47  * Revision 5.28  1994/03/20 04:52:58  eggert
48  * Do not generate a corrupted RCS file if the user modifies the working file
49  * while `ci' is running.
50  * Do not remove the lock when `ci -l' reverts.
51  * Move buffer-flushes out of critical sections, since they aren't critical.
52  * Use ORCSerror to clean up after a fatal error.
53  * Specify subprocess input via file descriptor, not file name.
54  *
55  * Revision 5.27  1993/11/09 17:40:15  eggert
56  * -V now prints version on stdout and exits.  Don't print usage twice.
57  *
58  * Revision 5.26  1993/11/03 17:42:27  eggert
59  * Add -z.  Don't subtract from RCS file timestamp even if -T.
60  * Scan for and use Name keyword if -k.
61  * Don't discard ignored phrases.  Improve quality of diagnostics.
62  *
63  * Revision 5.25  1992/07/28  16:12:44  eggert
64  * Add -i, -j, -V.  Check that working and RCS files are distinct.
65  *
66  * Revision 5.24  1992/02/17  23:02:06  eggert
67  * `-rREV' now just specifies a revision REV; only bare `-r' reverts to default.
68  * Add -T.
69  *
70  * Revision 5.23  1992/01/27  16:42:51  eggert
71  * Always unlock branchpoint if caller has a lock.
72  * Add support for bad_chmod_close, bad_creat0.  lint -> RCS_lint
73  *
74  * Revision 5.22  1992/01/06  02:42:34  eggert
75  * Invoke utime() before chmod() to keep some buggy systems happy.
76  *
77  * Revision 5.21  1991/11/20  17:58:07  eggert
78  * Don't read the delta tree from a nonexistent RCS file.
79  *
80  * Revision 5.20  1991/10/07  17:32:46  eggert
81  * Fix log bugs.  Remove lint.
82  *
83  * Revision 5.19  1991/09/26  23:10:30  eggert
84  * Plug file descriptor leak.
85  *
86  * Revision 5.18  1991/09/18  07:29:10  eggert
87  * Work around a common ftruncate() bug.
88  *
89  * Revision 5.17  1991/09/10  22:15:46  eggert
90  * Fix test for redirected stdin.
91  *
92  * Revision 5.16  1991/08/19  23:17:54  eggert
93  * When there are no changes, revert to previous revision instead of aborting.
94  * Add piece tables, -M, -r$.  Tune.
95  *
96  * Revision 5.15  1991/04/21  11:58:14  eggert
97  * Ensure that working file is newer than RCS file after ci -[lu].
98  * Add -x, RCSINIT, MS-DOS support.
99  *
100  * Revision 5.14  1991/02/28  19:18:47  eggert
101  * Don't let a setuid ci create a new RCS file; rcs -i -a must be run first.
102  * Fix ci -ko -l mode bug.  Open work file at most once.
103  *
104  * Revision 5.13  1991/02/25  07:12:33  eggert
105  * getdate -> getcurdate (SVR4 name clash)
106  *
107  * Revision 5.12  1990/12/31  01:00:12  eggert
108  * Don't use uninitialized storage when handling -{N,n}.
109  *
110  * Revision 5.11  1990/12/04  05:18:36  eggert
111  * Use -I for prompts and -q for diagnostics.
112  *
113  * Revision 5.10  1990/11/05  20:30:10  eggert
114  * Don't remove working file when aborting due to no changes.
115  *
116  * Revision 5.9  1990/11/01  05:03:23  eggert
117  * Add -I and new -t behavior.  Permit arbitrary data in logs.
118  *
119  * Revision 5.8  1990/10/04  06:30:09  eggert
120  * Accumulate exit status across files.
121  *
122  * Revision 5.7  1990/09/25  20:11:46  hammer
123  * fixed another small typo
124  *
125  * Revision 5.6  1990/09/24  21:48:50  hammer
126  * added cleanups from Paul Eggert.
127  *
128  * Revision 5.5  1990/09/21  06:16:38  hammer
129  * made it handle multiple -{N,n}'s.  Also, made it treat re-directed stdin
130  * the same as the terminal
131  *
132  * Revision 5.4  1990/09/20  02:38:51  eggert
133  * ci -k now checks dates more thoroughly.
134  *
135  * Revision 5.3  1990/09/11  02:41:07  eggert
136  * Fix revision bug with `ci -k file1 file2'.
137  *
138  * Revision 5.2  1990/09/04  08:02:10  eggert
139  * Permit adjacent revisions with identical time stamps (possible on fast hosts).
140  * Improve incomplete line handling.  Standardize yes-or-no procedure.
141  *
142  * Revision 5.1  1990/08/29  07:13:44  eggert
143  * Expand locker value like co.  Clean old log messages too.
144  *
145  * Revision 5.0  1990/08/22  08:10:00  eggert
146  * Don't require a final newline.
147  * Make lock and temp files faster and safer.
148  * Remove compile-time limits; use malloc instead.
149  * Permit dates past 1999/12/31.  Switch to GMT.
150  * Add setuid support.  Don't pass +args to diff.  Check diff's output.
151  * Ansify and Posixate.  Add -k, -V.  Remove snooping.  Tune.
152  * Check diff's output.
153  *
154  * Revision 4.9  89/05/01  15:10:54  narten
155  * changed copyright header to reflect current distribution rules
156  *
157  * Revision 4.8  88/11/08  13:38:23  narten
158  * changes from root@seismo.CSS.GOV (Super User)
159  * -d with no arguments uses the mod time of the file it is checking in
160  *
161  * Revision 4.7  88/08/09  19:12:07  eggert
162  * Make sure workfile is a regular file; use its mode if RCSfile doesn't have one.
163  * Use execv(), not system(); allow cc -R; remove lint.
164  * isatty(fileno(stdin)) -> ttystdin()
165  *
166  * Revision 4.6  87/12/18  11:34:41  narten
167  * lint cleanups (from Guy Harris)
168  *
169  * Revision 4.5  87/10/18  10:18:48  narten
170  * Updating version numbers. Changes relative to revision 1.1 are actually
171  * relative to 4.3
172  *
173  * Revision 1.3  87/09/24  13:57:19  narten
174  * Sources now pass through lint (if you ignore printf/sprintf/fprintf
175  * warnings)
176  *
177  * Revision 1.2  87/03/27  14:21:33  jenkins
178  * Port to suns
179  *
180  * Revision 4.3  83/12/15  12:28:54  wft
181  * ci -u and ci -l now set mode of working file properly.
182  *
183  * Revision 4.2  83/12/05  13:40:54  wft
184  * Merged with 3.9.1.1: added calls to clearerr(stdin).
185  * made rewriteflag external.
186  *
187  * Revision 4.1  83/05/10  17:03:06  wft
188  * Added option -d and -w, and updated assingment of date, etc. to new delta.
189  * Added handling of default branches.
190  * Option -k generates std. log message; fixed undef. pointer in reading of log.
191  * Replaced getlock() with findlock(), link--unlink with rename(),
192  * getpwuid() with getcaller().
193  * Moved all revision number generation to new routine addelta().
194  * Removed calls to stat(); now done by pairfilenames().
195  * Changed most calls to catchints() with restoreints().
196  * Directed all interactive messages to stderr.
197  *
198  * Revision 3.9.1.1  83/10/19  04:21:03  lepreau
199  * Added clearerr(stdin) to getlogmsg() for re-reading stdin.
200  *
201  * Revision 3.9  83/02/15  15:25:44  wft
202  * 4.2 prerelease
203  *
204  * Revision 3.9  83/02/15  15:25:44  wft
205  * Added call to fastcopy() to copy remainder of RCS file.
206  *
207  * Revision 3.8  83/01/14  15:34:05  wft
208  * Added ignoring of interrupts while new RCS file is renamed;
209  * Avoids deletion of RCS files by interrupts.
210  *
211  * Revision 3.7  82/12/10  16:09:20  wft
212  * Corrected checking of return code from diff.
213  *
214  * Revision 3.6  82/12/08  21:34:49  wft
215  * Using DATEFORM to prepare date of checked-in revision;
216  * Fixed return from addbranch().
217  *
218  * Revision 3.5  82/12/04  18:32:42  wft
219  * Replaced getdelta() with gettree(), SNOOPDIR with SNOOPFILE. Updated
220  * field lockedby in removelock(), moved getlogmsg() before calling diff.
221  *
222  * Revision 3.4  82/12/02  13:27:13  wft
223  * added option -k.
224  *
225  * Revision 3.3  82/11/28  20:53:31  wft
226  * Added mustcheckin() to check for redundant checkins.
227  * Added xpandfile() to do keyword expansion for -u and -l;
228  * -m appends linefeed to log message if necessary.
229  * getlogmsg() suppresses prompt if stdin is not a terminal.
230  * Replaced keeplock with lockflag, fclose() with ffclose(),
231  * %02d with %.2d, getlogin() with getpwuid().
232  *
233  * Revision 3.2  82/10/18  20:57:23  wft
234  * An RCS file inherits its mode during the first ci from the working file,
235  * otherwise it stays the same, except that write permission is removed.
236  * Fixed ci -l, added ci -u (both do an implicit co after the ci).
237  * Fixed call to getlogin(), added call to getfullRCSname(), added check
238  * for write error.
239  * Changed conflicting identifiers.
240  *
241  * Revision 3.1  82/10/13  16:04:59  wft
242  * fixed type of variables receiving from getc() (char -> int).
243  * added include file dbm.h for getting BYTESIZ. This is used
244  * to check the return code from diff portably.
245  */
246
247 #include "rcsbase.h"
248
249 struct Symrev {
250        char const *ssymbol;
251        int override;
252        struct Symrev * nextsym;
253 };
254
255 static char const *getcurdate P((void));
256 static int addbranch P((struct hshentry*,struct buf*,int));
257 static int addelta P((void));
258 static int addsyms P((char const*));
259 static int fixwork P((mode_t,time_t));
260 static int removelock P((struct hshentry*));
261 static int xpandfile P((RILE*,struct hshentry const*,char const**,int));
262 static struct cbuf getlogmsg P((void));
263 static void cleanup P((void));
264 static void incnum P((char const*,struct buf*));
265 static void addassoclst P((int,char const*));
266
267 static FILE *exfile;
268 static RILE *workptr;                   /* working file pointer         */
269 static struct buf newdelnum;            /* new revision number          */
270 static struct cbuf msg;
271 static int exitstatus;
272 static int forceciflag;                 /* forces check in              */
273 static int keepflag, keepworkingfile, rcsinitflag;
274 static struct hshentries *gendeltas;    /* deltas to be generated       */
275 static struct hshentry *targetdelta;    /* old delta to be generated    */
276 static struct hshentry newdelta;        /* new delta to be inserted     */
277 static struct stat workstat;
278 static struct Symrev *assoclst, **nextassoc;
279
280 mainProg(ciId, "ci", "$DragonFly: src/gnu/usr.bin/rcs/ci/ci.c,v 1.2 2003/06/17 04:25:47 dillon Exp $")
281 {
282         static char const cmdusage[] =
283                 "\nci usage: ci -{fIklMqru}[rev] -d[date] -mmsg -{nN}name -sstate -ttext -T -Vn -wwho -xsuff -zzone file ...";
284         static char const default_state[] = DEFAULTSTATE;
285
286         char altdate[datesize];
287         char olddate[datesize];
288         char newdatebuf[datesize + zonelenmax];
289         char targetdatebuf[datesize + zonelenmax];
290         char *a, **newargv, *textfile;
291         char const *author, *krev, *rev, *state;
292         char const *diffname, *expname;
293         char const *newworkname;
294         int initflag, mustread;
295         int lockflag, lockthis, mtimeflag, removedlock, Ttimeflag;
296         int r;
297         int changedRCS, changework, dolog, newhead;
298         int usestatdate; /* Use mod time of file for -d.  */
299         mode_t newworkmode; /* mode for working file */
300         time_t mtime, wtime;
301         struct hshentry *workdelta;
302
303         setrid();
304
305         author = rev = state = textfile = 0;
306         initflag = lockflag = mustread = false;
307         mtimeflag = false;
308         Ttimeflag = false;
309         altdate[0]= '\0'; /* empty alternate date for -d */
310         usestatdate=false;
311         suffixes = X_DEFAULT;
312         nextassoc = &assoclst;
313
314         argc = getRCSINIT(argc, argv, &newargv);
315         argv = newargv;
316         while (a = *++argv,  0<--argc && *a++=='-') {
317                 switch (*a++) {
318
319                 case 'r':
320                         if (*a)
321                                 goto revno;
322                         keepworkingfile = lockflag = false;
323                         break;
324
325                 case 'l':
326                         keepworkingfile = lockflag = true;
327                 revno:
328                         if (*a) {
329                                 if (rev) warn("redefinition of revision number");
330                                 rev = a;
331                         }
332                         break;
333
334                 case 'u':
335                         keepworkingfile=true; lockflag=false;
336                         goto revno;
337
338                 case 'i':
339                         initflag = true;
340                         goto revno;
341
342                 case 'j':
343                         mustread = true;
344                         goto revno;
345
346                 case 'I':
347                         interactiveflag = true;
348                         goto revno;
349
350                 case 'q':
351                         quietflag=true;
352                         goto revno;
353
354                 case 'f':
355                         forceciflag=true;
356                         goto revno;
357
358                 case 'k':
359                         keepflag=true;
360                         goto revno;
361
362                 case 'm':
363                         if (msg.size) redefined('m');
364                         msg = cleanlogmsg(a, strlen(a));
365                         if (!msg.size)
366                                 error("missing message for -m option");
367                         break;
368
369                 case 'n':
370                         if (!*a) {
371                                 error("missing symbolic name after -n");
372                                 break;
373                         }
374                         checkssym(a);
375                         addassoclst(false, a);
376                         break;
377
378                 case 'N':
379                         if (!*a) {
380                                 error("missing symbolic name after -N");
381                                 break;
382                         }
383                         checkssym(a);
384                         addassoclst(true, a);
385                         break;
386
387                 case 's':
388                         if (*a) {
389                                 if (state) redefined('s');
390                                 checksid(a);
391                                 state = a;
392                         } else
393                                 error("missing state for -s option");
394                         break;
395
396                 case 't':
397                         if (*a) {
398                                 if (textfile) redefined('t');
399                                 textfile = a;
400                         }
401                         break;
402
403                 case 'd':
404                         if (altdate[0] || usestatdate)
405                                 redefined('d');
406                         altdate[0] = '\0';
407                         if (!(usestatdate = !*a))
408                                 str2date(a, altdate);
409                         break;
410
411                 case 'M':
412                         mtimeflag = true;
413                         goto revno;
414
415                 case 'w':
416                         if (*a) {
417                                 if (author) redefined('w');
418                                 checksid(a);
419                                 author = a;
420                         } else
421                                 error("missing author for -w option");
422                         break;
423
424                 case 'x':
425                         suffixes = a;
426                         break;
427
428                 case 'V':
429                         setRCSversion(*argv);
430                         break;
431
432                 case 'z':
433                         zone_set(a);
434                         break;
435
436                 case 'T':
437                         if (!*a) {
438                                 Ttimeflag = true;
439                                 break;
440                         }
441                         /* fall into */
442                 default:
443                         error("unknown option: %s%s", *argv, cmdusage);
444                 };
445         }  /* end processing of options */
446
447         /* Handle all pathnames.  */
448         if (nerror) cleanup();
449         else if (argc < 1) faterror("no input file%s", cmdusage);
450         else for (;  0 < argc;  cleanup(), ++argv, --argc) {
451         targetdelta = 0;
452         ffree();
453
454         switch (pairnames(argc, argv, rcswriteopen, mustread, false)) {
455
456         case -1:                /* New RCS file */
457 #               if has_setuid && has_getuid
458                     if (euid() != ruid()) {
459                         workerror("setuid initial checkin prohibited; use `rcs -i -a' first");
460                         continue;
461                     }
462 #               endif
463                 rcsinitflag = true;
464                 break;
465
466         case 0:                 /* Error */
467                 continue;
468
469         case 1:                 /* Normal checkin with prev . RCS file */
470                 if (initflag) {
471                         rcserror("already exists");
472                         continue;
473                 }
474                 rcsinitflag = !Head;
475         }
476
477         /*
478          * RCSname contains the name of the RCS file, and
479          * workname contains the name of the working file.
480          * If the RCS file exists, finptr contains the file descriptor for the
481          * RCS file, and RCSstat is set. The admin node is initialized.
482          */
483
484         diagnose("%s  <--  %s\n", RCSname, workname);
485
486         if (!(workptr = Iopen(workname, FOPEN_R_WORK, &workstat))) {
487                 eerror(workname);
488                 continue;
489         }
490
491         if (finptr) {
492                 if (same_file(RCSstat, workstat, 0)) {
493                         rcserror("RCS file is the same as working file %s.",
494                                 workname
495                         );
496                         continue;
497                 }
498                 if (!checkaccesslist())
499                         continue;
500         }
501
502         krev = rev;
503         if (keepflag) {
504                 /* get keyword values from working file */
505                 if (!getoldkeys(workptr)) continue;
506                 if (!rev  &&  !*(krev = prevrev.string)) {
507                         workerror("can't find a revision number");
508                         continue;
509                 }
510                 if (!*prevdate.string && *altdate=='\0' && usestatdate==false)
511                         workwarn("can't find a date");
512                 if (!*prevauthor.string && !author)
513                         workwarn("can't find an author");
514                 if (!*prevstate.string && !state)
515                         workwarn("can't find a state");
516         } /* end processing keepflag */
517
518         /* Read the delta tree.  */
519         if (finptr)
520             gettree();
521
522         /* expand symbolic revision number */
523         if (!fexpandsym(krev, &newdelnum, workptr))
524             continue;
525
526         /* splice new delta into tree */
527         if ((removedlock = addelta()) < 0)
528             continue;
529
530         newdelta.num = newdelnum.string;
531         newdelta.branches = 0;
532         newdelta.lockedby = 0; /* This might be changed by addlock().  */
533         newdelta.selector = true;
534         newdelta.name = 0;
535         clear_buf(&newdelta.ig);
536         clear_buf(&newdelta.igtext);
537         /* set author */
538         if (author)
539                 newdelta.author=author;     /* set author given by -w         */
540         else if (keepflag && *prevauthor.string)
541                 newdelta.author=prevauthor.string; /* preserve old author if possible*/
542         else    newdelta.author=getcaller();/* otherwise use caller's id      */
543         newdelta.state = default_state;
544         if (state)
545                 newdelta.state=state;       /* set state given by -s          */
546         else if (keepflag && *prevstate.string)
547                 newdelta.state=prevstate.string;   /* preserve old state if possible */
548         if (usestatdate) {
549             time2date(workstat.st_mtime, altdate);
550         }
551         if (*altdate!='\0')
552                 newdelta.date=altdate;      /* set date given by -d           */
553         else if (keepflag && *prevdate.string) {
554                 /* Preserve old date if possible.  */
555                 str2date(prevdate.string, olddate);
556                 newdelta.date = olddate;
557         } else
558                 newdelta.date = getcurdate();  /* use current date */
559         /* now check validity of date -- needed because of -d and -k          */
560         if (targetdelta &&
561             cmpdate(newdelta.date,targetdelta->date) < 0) {
562                 rcserror("Date %s precedes %s in revision %s.",
563                         date2str(newdelta.date, newdatebuf),
564                         date2str(targetdelta->date, targetdatebuf),
565                         targetdelta->num
566                 );
567                 continue;
568         }
569
570
571         if (lockflag  &&  addlock(&newdelta, true) < 0) continue;
572
573         if (keepflag && *prevname.string)
574             if (addsymbol(newdelta.num, prevname.string, false)  <  0)
575                 continue;
576         if (!addsyms(newdelta.num))
577             continue;
578
579
580         putadmin();
581         puttree(Head,frewrite);
582         putdesc(false,textfile);
583
584         changework = Expand < MIN_UNCHANGED_EXPAND;
585         dolog = true;
586         lockthis = lockflag;
587         workdelta = &newdelta;
588
589         /* build rest of file */
590         if (rcsinitflag) {
591                 diagnose("initial revision: %s\n", newdelta.num);
592                 /* get logmessage */
593                 newdelta.log=getlogmsg();
594                 putdftext(&newdelta, workptr, frewrite, false);
595                 RCSstat.st_mode = workstat.st_mode;
596                 RCSstat.st_nlink = 0;
597                 changedRCS = true;
598         } else {
599                 diffname = maketemp(0);
600                 newhead  =  Head == &newdelta;
601                 if (!newhead)
602                         foutptr = frewrite;
603                 expname = buildrevision(
604                         gendeltas, targetdelta, (FILE*)0, false
605                 );
606                 if (
607                     !forceciflag  &&
608                     strcmp(newdelta.state, targetdelta->state) == 0  &&
609                     (changework = rcsfcmp(
610                         workptr, &workstat, expname, targetdelta
611                     )) <= 0
612                 ) {
613                     diagnose("file is unchanged; reverting to previous revision %s\n",
614                         targetdelta->num
615                     );
616                     if (removedlock < lockflag) {
617                         diagnose("previous revision was not locked; ignoring -l option\n");
618                         lockthis = 0;
619                     }
620                     dolog = false;
621                     if (! (changedRCS = lockflag<removedlock || assoclst))
622                         workdelta = targetdelta;
623                     else {
624                         /*
625                          * We have started to build the wrong new RCS file.
626                          * Start over from the beginning.
627                          */
628                         long hwm = ftell(frewrite);
629                         int bad_truncate;
630                         Orewind(frewrite);
631
632                         /*
633                         * Work around a common ftruncate() bug:
634                         * NFS won't let you truncate a file that you
635                         * currently lack permissions for, even if you
636                         * had permissions when you opened it.
637                         * Also, Posix 1003.1b-1993 sec 5.6.7.2 p 128 l 1022
638                         * says ftruncate might fail because it's not supported.
639                         */
640 #                       if !has_ftruncate
641 #                           undef ftruncate
642 #                           define ftruncate(fd,length) (-1)
643 #                       endif
644                         bad_truncate = ftruncate(fileno(frewrite), (off_t)0);
645
646                         Irewind(finptr);
647                         Lexinit();
648                         getadmin();
649                         gettree();
650                         if (!(workdelta = genrevs(
651                             targetdelta->num, (char*)0, (char*)0, (char*)0,
652                             &gendeltas
653                         )))
654                             continue;
655                         workdelta->log = targetdelta->log;
656                         if (newdelta.state != default_state)
657                             workdelta->state = newdelta.state;
658                         if (lockthis<removedlock && removelock(workdelta)<0)
659                             continue;
660                         if (!addsyms(workdelta->num))
661                             continue;
662                         if (dorewrite(true, true) != 0)
663                             continue;
664                         fastcopy(finptr, frewrite);
665                         if (bad_truncate)
666                             while (ftell(frewrite) < hwm)
667                                 /* White out any earlier mistake with '\n's.  */
668                                 /* This is unlikely.  */
669                                 afputc('\n', frewrite);
670                     }
671                 } else {
672                     int wfd = Ifileno(workptr);
673                     struct stat checkworkstat;
674                     char const *diffv[6 + !!OPEN_O_BINARY], **diffp;
675 #                   if large_memory && !maps_memory
676                         FILE *wfile = workptr->stream;
677                         long wfile_off;
678 #                   endif
679 #                   if !has_fflush_input && !(large_memory && maps_memory)
680                         off_t wfd_off;
681 #                   endif
682
683                     diagnose("new revision: %s; previous revision: %s\n",
684                         newdelta.num, targetdelta->num
685                     );
686                     newdelta.log = getlogmsg();
687 #                   if !large_memory
688                         Irewind(workptr);
689 #                       if has_fflush_input
690                             if (fflush(workptr) != 0)
691                                 Ierror();
692 #                       endif
693 #                   else
694 #                       if !maps_memory
695                             if (
696                                 (wfile_off = ftell(wfile)) == -1
697                              || fseek(wfile, 0L, SEEK_SET) != 0
698 #                            if has_fflush_input
699                              || fflush(wfile) != 0
700 #                            endif
701                             )
702                                 Ierror();
703 #                       endif
704 #                   endif
705 #                   if !has_fflush_input && !(large_memory && maps_memory)
706                         wfd_off = lseek(wfd, (off_t)0, SEEK_CUR);
707                         if (wfd_off == -1
708                             || (wfd_off != 0
709                                 && lseek(wfd, (off_t)0, SEEK_SET) != 0))
710                             Ierror();
711 #                   endif
712                     diffp = diffv;
713                     *++diffp = DIFF;
714                     *++diffp = DIFFFLAGS;
715 #                   if OPEN_O_BINARY
716                         if (Expand == BINARY_EXPAND)
717                             *++diffp = "--binary";
718 #                   endif
719                     *++diffp = newhead ? "-" : expname;
720                     *++diffp = newhead ? expname : "-";
721                     *++diffp = 0;
722                     switch (runv(wfd, diffname, diffv)) {
723                         case DIFF_FAILURE: case DIFF_SUCCESS: break;
724                         default: rcsfaterror("diff failed");
725                     }
726 #                   if !has_fflush_input && !(large_memory && maps_memory)
727                         if (lseek(wfd, wfd_off, SEEK_CUR) == -1)
728                             Ierror();
729 #                   endif
730 #                   if large_memory && !maps_memory
731                         if (fseek(wfile, wfile_off, SEEK_SET) != 0)
732                             Ierror();
733 #                   endif
734                     if (newhead) {
735                         Irewind(workptr);
736                         putdftext(&newdelta, workptr, frewrite, false);
737                         if (!putdtext(targetdelta,diffname,frewrite,true)) continue;
738                     } else
739                         if (!putdtext(&newdelta,diffname,frewrite,true)) continue;
740
741                     /*
742                     * Check whether the working file changed during checkin,
743                     * to avoid producing an inconsistent RCS file.
744                     */
745                     if (
746                         fstat(wfd, &checkworkstat) != 0
747                      || workstat.st_mtime != checkworkstat.st_mtime
748                      || workstat.st_size != checkworkstat.st_size
749                     ) {
750                         workerror("file changed during checkin");
751                         continue;
752                     }
753
754                     changedRCS = true;
755                 }
756         }
757
758         /* Deduce time_t of new revision if it is needed later.  */
759         wtime = (time_t)-1;
760         if (mtimeflag | Ttimeflag)
761                 wtime = date2time(workdelta->date);
762
763         if (donerewrite(changedRCS,
764                 !Ttimeflag ? (time_t)-1
765                 : finptr && wtime < RCSstat.st_mtime ? RCSstat.st_mtime
766                 : wtime
767         ) != 0)
768                 continue;
769
770         if (!keepworkingfile) {
771                 Izclose(&workptr);
772                 r = un_link(workname); /* Get rid of old file */
773         } else {
774                 newworkmode = WORKMODE(RCSstat.st_mode,
775                         !   (Expand==VAL_EXPAND  ||  lockthis < StrictLocks)
776                 );
777                 mtime = mtimeflag ? wtime : (time_t)-1;
778
779                 /* Expand if it might change or if we can't fix mode, time.  */
780                 if (changework  ||  (r=fixwork(newworkmode,mtime)) != 0) {
781                     Irewind(workptr);
782                     /* Expand keywords in file.  */
783                     locker_expansion = lockthis;
784                     workdelta->name =
785                         namedrev(
786                                 assoclst ? assoclst->ssymbol
787                                 : keepflag && *prevname.string ? prevname.string
788                                 : rev,
789                                 workdelta
790                         );
791                     switch (xpandfile(
792                         workptr, workdelta, &newworkname, dolog
793                     )) {
794                         default:
795                             continue;
796
797                         case 0:
798                             /*
799                              * No expansion occurred; try to reuse working file
800                              * unless we already tried and failed.
801                              */
802                             if (changework)
803                                 if ((r=fixwork(newworkmode,mtime)) == 0)
804                                     break;
805                             /* fall into */
806                         case 1:
807                             Izclose(&workptr);
808                             aflush(exfile);
809                             ignoreints();
810                             r = chnamemod(&exfile, newworkname,
811                                     workname, 1, newworkmode, mtime
812                             );
813                             keepdirtemp(newworkname);
814                             restoreints();
815                     }
816                 }
817         }
818         if (r != 0) {
819             eerror(workname);
820             continue;
821         }
822         diagnose("done\n");
823
824         }
825
826         tempunlink();
827         exitmain(exitstatus);
828 }       /* end of main (ci) */
829
830         static void
831 cleanup()
832 {
833         if (nerror) exitstatus = EXIT_FAILURE;
834         Izclose(&finptr);
835         Izclose(&workptr);
836         Ozclose(&exfile);
837         Ozclose(&fcopy);
838         ORCSclose();
839         dirtempunlink();
840 }
841
842 #if RCS_lint
843 #       define exiterr ciExit
844 #endif
845         void
846 exiterr()
847 {
848         ORCSerror();
849         dirtempunlink();
850         tempunlink();
851         _exit(EXIT_FAILURE);
852 }
853
854 /*****************************************************************/
855 /* the rest are auxiliary routines                               */
856
857
858         static int
859 addelta()
860 /* Function: Appends a delta to the delta tree, whose number is
861  * given by newdelnum.  Updates Head, newdelnum, newdelnumlength,
862  * and the links in newdelta.
863  * Return -1 on error, 1 if a lock is removed, 0 otherwise.
864  */
865 {
866         register char *tp;
867         register int i;
868         int removedlock;
869         int newdnumlength;  /* actual length of new rev. num. */
870
871         newdnumlength = countnumflds(newdelnum.string);
872
873         if (rcsinitflag) {
874                 /* this covers non-existing RCS file and a file initialized with rcs -i */
875                 if (newdnumlength==0 && Dbranch) {
876                         bufscpy(&newdelnum, Dbranch);
877                         newdnumlength = countnumflds(Dbranch);
878                 }
879                 if (newdnumlength==0) bufscpy(&newdelnum, "1.1");
880                 else if (newdnumlength==1) bufscat(&newdelnum, ".1");
881                 else if (newdnumlength>2) {
882                     rcserror("Branch point doesn't exist for revision %s.",
883                         newdelnum.string
884                     );
885                     return -1;
886                 } /* newdnumlength == 2 is OK;  */
887                 Head = &newdelta;
888                 newdelta.next = 0;
889                 return 0;
890         }
891         if (newdnumlength==0) {
892                 /* derive new revision number from locks */
893                 switch (findlock(true, &targetdelta)) {
894
895                   default:
896                     /* found two or more old locks */
897                     return -1;
898
899                   case 1:
900                     /* found an old lock */
901                     /* check whether locked revision exists */
902                     if (!genrevs(targetdelta->num,(char*)0,(char*)0,(char*)0,&gendeltas))
903                         return -1;
904                     if (targetdelta==Head) {
905                         /* make new head */
906                         newdelta.next=Head;
907                         Head= &newdelta;
908                     } else if (!targetdelta->next && countnumflds(targetdelta->num)>2) {
909                         /* new tip revision on side branch */
910                         targetdelta->next= &newdelta;
911                         newdelta.next = 0;
912                     } else {
913                         /* middle revision; start a new branch */
914                         bufscpy(&newdelnum, "");
915                         return addbranch(targetdelta, &newdelnum, 1);
916                     }
917                     incnum(targetdelta->num, &newdelnum);
918                     return 1; /* successful use of existing lock */
919
920                   case 0:
921                     /* no existing lock; try Dbranch */
922                     /* update newdelnum */
923                     if (StrictLocks || !myself(RCSstat.st_uid)) {
924                         rcserror("no lock set by %s", getcaller());
925                         return -1;
926                     }
927                     if (Dbranch) {
928                         bufscpy(&newdelnum, Dbranch);
929                     } else {
930                         incnum(Head->num, &newdelnum);
931                     }
932                     newdnumlength = countnumflds(newdelnum.string);
933                     /* now fall into next statement */
934                 }
935         }
936         if (newdnumlength<=2) {
937                 /* add new head per given number */
938                 if(newdnumlength==1) {
939                     /* make a two-field number out of it*/
940                     if (cmpnumfld(newdelnum.string,Head->num,1)==0)
941                         incnum(Head->num, &newdelnum);
942                     else
943                         bufscat(&newdelnum, ".1");
944                 }
945                 if (cmpnum(newdelnum.string,Head->num) <= 0) {
946                     rcserror("revision %s too low; must be higher than %s",
947                           newdelnum.string, Head->num
948                     );
949                     return -1;
950                 }
951                 targetdelta = Head;
952                 if (0 <= (removedlock = removelock(Head))) {
953                     if (!genrevs(Head->num,(char*)0,(char*)0,(char*)0,&gendeltas))
954                         return -1;
955                     newdelta.next = Head;
956                     Head = &newdelta;
957                 }
958                 return removedlock;
959         } else {
960                 /* put new revision on side branch */
961                 /*first, get branch point */
962                 tp = newdelnum.string;
963                 for (i = newdnumlength - ((newdnumlength&1) ^ 1);  --i;  )
964                         while (*tp++ != '.')
965                                 continue;
966                 *--tp = 0; /* Kill final dot to get old delta temporarily. */
967                 if (!(targetdelta=genrevs(newdelnum.string,(char*)0,(char*)0,(char*)0,&gendeltas)))
968                     return -1;
969                 if (cmpnum(targetdelta->num, newdelnum.string) != 0) {
970                     rcserror("can't find branch point %s", newdelnum.string);
971                     return -1;
972                 }
973                 *tp = '.'; /* Restore final dot. */
974                 return addbranch(targetdelta, &newdelnum, 0);
975         }
976 }
977
978
979
980         static int
981 addbranch(branchpoint, num, removedlock)
982         struct hshentry *branchpoint;
983         struct buf *num;
984         int removedlock;
985 /* adds a new branch and branch delta at branchpoint.
986  * If num is the null string, appends the new branch, incrementing
987  * the highest branch number (initially 1), and setting the level number to 1.
988  * the new delta and branchhead are in globals newdelta and newbranch, resp.
989  * the new number is placed into num.
990  * Return -1 on error, 1 if a lock is removed, 0 otherwise.
991  * If REMOVEDLOCK is 1, a lock was already removed.
992  */
993 {
994         struct branchhead *bhead, **btrail;
995         struct buf branchnum;
996         int result;
997         int field, numlength;
998         static struct branchhead newbranch;  /* new branch to be inserted */
999
1000         numlength = countnumflds(num->string);
1001
1002         if (!branchpoint->branches) {
1003                 /* start first branch */
1004                 branchpoint->branches = &newbranch;
1005                 if (numlength==0) {
1006                         bufscpy(num, branchpoint->num);
1007                         bufscat(num, ".1.1");
1008                 } else if (numlength&1)
1009                         bufscat(num, ".1");
1010                 newbranch.nextbranch = 0;
1011
1012         } else if (numlength==0) {
1013                 /* append new branch to the end */
1014                 bhead=branchpoint->branches;
1015                 while (bhead->nextbranch) bhead=bhead->nextbranch;
1016                 bhead->nextbranch = &newbranch;
1017                 bufautobegin(&branchnum);
1018                 getbranchno(bhead->hsh->num, &branchnum);
1019                 incnum(branchnum.string, num);
1020                 bufautoend(&branchnum);
1021                 bufscat(num, ".1");
1022                 newbranch.nextbranch = 0;
1023         } else {
1024                 /* place the branch properly */
1025                 field = numlength - ((numlength&1) ^ 1);
1026                 /* field of branch number */
1027                 btrail = &branchpoint->branches;
1028                 while (0 < (result=cmpnumfld(num->string,(*btrail)->hsh->num,field))) {
1029                         btrail = &(*btrail)->nextbranch;
1030                         if (!*btrail) {
1031                                 result = -1;
1032                                 break;
1033                         }
1034                 }
1035                 if (result < 0) {
1036                         /* insert/append new branchhead */
1037                         newbranch.nextbranch = *btrail;
1038                         *btrail = &newbranch;
1039                         if (numlength&1) bufscat(num, ".1");
1040                 } else {
1041                         /* branch exists; append to end */
1042                         bufautobegin(&branchnum);
1043                         getbranchno(num->string, &branchnum);
1044                         targetdelta = genrevs(
1045                                 branchnum.string, (char*)0, (char*)0, (char*)0,
1046                                 &gendeltas
1047                         );
1048                         bufautoend(&branchnum);
1049                         if (!targetdelta)
1050                             return -1;
1051                         if (cmpnum(num->string,targetdelta->num) <= 0) {
1052                                 rcserror("revision %s too low; must be higher than %s",
1053                                       num->string, targetdelta->num
1054                                 );
1055                                 return -1;
1056                         }
1057                         if (!removedlock
1058                             && 0 <= (removedlock = removelock(targetdelta))
1059                         ) {
1060                             if (numlength&1)
1061                                 incnum(targetdelta->num,num);
1062                             targetdelta->next = &newdelta;
1063                             newdelta.next = 0;
1064                         }
1065                         return removedlock;
1066                         /* Don't do anything to newbranch.  */
1067                 }
1068         }
1069         newbranch.hsh = &newdelta;
1070         newdelta.next = 0;
1071         if (branchpoint->lockedby)
1072             if (strcmp(branchpoint->lockedby, getcaller()) == 0)
1073                 return removelock(branchpoint); /* This returns 1.  */
1074         return removedlock;
1075 }
1076
1077         static int
1078 addsyms(num)
1079         char const *num;
1080 {
1081         register struct Symrev *p;
1082
1083         for (p = assoclst;  p;  p = p->nextsym)
1084                 if (addsymbol(num, p->ssymbol, p->override)  <  0)
1085                         return false;
1086         return true;
1087 }
1088
1089
1090         static void
1091 incnum(onum,nnum)
1092         char const *onum;
1093         struct buf *nnum;
1094 /* Increment the last field of revision number onum by one and
1095  * place the result into nnum.
1096  */
1097 {
1098         register char *tp, *np;
1099         register size_t l;
1100
1101         l = strlen(onum);
1102         bufalloc(nnum, l+2);
1103         np = tp = nnum->string;
1104         VOID strcpy(np, onum);
1105         for (tp = np + l;  np != tp;  )
1106                 if (isdigit(*--tp)) {
1107                         if (*tp != '9') {
1108                                 ++*tp;
1109                                 return;
1110                         }
1111                         *tp = '0';
1112                 } else {
1113                         tp++;
1114                         break;
1115                 }
1116         /* We changed 999 to 000; now change it to 1000.  */
1117         *tp = '1';
1118         tp = np + l;
1119         *tp++ = '0';
1120         *tp = 0;
1121 }
1122
1123
1124
1125         static int
1126 removelock(delta)
1127 struct hshentry * delta;
1128 /* function: Finds the lock held by caller on delta,
1129  * removes it, and returns nonzero if successful.
1130  * Print an error message and return -1 if there is no such lock.
1131  * An exception is if !StrictLocks, and caller is the owner of
1132  * the RCS file. If caller does not have a lock in this case,
1133  * return 0; return 1 if a lock is actually removed.
1134  */
1135 {
1136         register struct rcslock *next, **trail;
1137         char const *num;
1138
1139         num=delta->num;
1140         for (trail = &Locks;  (next = *trail);  trail = &next->nextlock)
1141             if (next->delta == delta)
1142                 if (strcmp(getcaller(), next->login) == 0) {
1143                     /* We found a lock on delta by caller; delete it.  */
1144                     *trail = next->nextlock;
1145                     delta->lockedby = 0;
1146                     return 1;
1147                 } else {
1148                     rcserror("revision %s locked by %s", num, next->login);
1149                     return -1;
1150                 }
1151         if (!StrictLocks && myself(RCSstat.st_uid))
1152             return 0;
1153         rcserror("no lock set by %s for revision %s", getcaller(), num);
1154         return -1;
1155 }
1156
1157
1158
1159         static char const *
1160 getcurdate()
1161 /* Return a pointer to the current date.  */
1162 {
1163         static char buffer[datesize]; /* date buffer */
1164
1165         if (!buffer[0])
1166                 time2date(now(), buffer);
1167         return buffer;
1168 }
1169
1170         static int
1171 #if has_prototypes
1172 fixwork(mode_t newworkmode, time_t mtime)
1173   /* The `#if has_prototypes' is needed because mode_t might promote to int.  */
1174 #else
1175   fixwork(newworkmode, mtime)
1176         mode_t newworkmode;
1177         time_t mtime;
1178 #endif
1179 {
1180         return
1181                         1 < workstat.st_nlink
1182                     ||  (newworkmode&S_IWUSR && !myself(workstat.st_uid))
1183                     ||  setmtime(workname, mtime) != 0
1184                 ?   -1
1185             :   workstat.st_mode == newworkmode  ?  0
1186 #if has_fchmod
1187             :   fchmod(Ifileno(workptr), newworkmode) == 0  ?  0
1188 #endif
1189 #if bad_chmod_close
1190             :   -1
1191 #else
1192             :   chmod(workname, newworkmode)
1193 #endif
1194         ;
1195 }
1196
1197         static int
1198 xpandfile(unexfile, delta, exname, dolog)
1199         RILE *unexfile;
1200         struct hshentry const *delta;
1201         char const **exname;
1202         int dolog;
1203 /*
1204  * Read unexfile and copy it to a
1205  * file, performing keyword substitution with data from delta.
1206  * Return -1 if unsuccessful, 1 if expansion occurred, 0 otherwise.
1207  * If successful, stores the stream descriptor into *EXFILEP
1208  * and its name into *EXNAME.
1209  */
1210 {
1211         char const *targetname;
1212         int e, r;
1213
1214         targetname = makedirtemp(1);
1215         if (!(exfile = fopenSafer(targetname, FOPEN_W_WORK))) {
1216                 eerror(targetname);
1217                 workerror("can't build working file");
1218                 return -1;
1219         }
1220         r = 0;
1221         if (MIN_UNEXPAND <= Expand)
1222                 fastcopy(unexfile,exfile);
1223         else {
1224                 for (;;) {
1225                         e = expandline(
1226                                 unexfile, exfile, delta, false, (FILE*)0, dolog
1227                         );
1228                         if (e < 0)
1229                                 break;
1230                         r |= e;
1231                         if (e <= 1)
1232                                 break;
1233                 }
1234         }
1235         *exname = targetname;
1236         return r & 1;
1237 }
1238
1239
1240
1241
1242 /* --------------------- G E T L O G M S G --------------------------------*/
1243
1244
1245         static struct cbuf
1246 getlogmsg()
1247 /* Obtain and yield a log message.
1248  * If a log message is given with -m, yield that message.
1249  * If this is the initial revision, yield a standard log message.
1250  * Otherwise, reads a character string from the terminal.
1251  * Stops after reading EOF or a single '.' on a
1252  * line. getlogmsg prompts the first time it is called for the
1253  * log message; during all later calls it asks whether the previous
1254  * log message can be reused.
1255  */
1256 {
1257         static char const
1258                 emptych[] = EMPTYLOG,
1259                 initialch[] = "Initial revision";
1260         static struct cbuf const
1261                 emptylog = { emptych, sizeof(emptych)-sizeof(char) },
1262                 initiallog = { initialch, sizeof(initialch)-sizeof(char) };
1263         static struct buf logbuf;
1264         static struct cbuf logmsg;
1265
1266         register char *tp;
1267         register size_t i;
1268         char const *caller;
1269
1270         if (msg.size) return msg;
1271
1272         if (keepflag) {
1273                 /* generate std. log message */
1274                 caller = getcaller();
1275                 i = sizeof(ciklog)+strlen(caller)+3;
1276                 bufalloc(&logbuf, i + datesize + zonelenmax);
1277                 tp = logbuf.string;
1278                 VOID sprintf(tp, "%s%s at ", ciklog, caller);
1279                 VOID date2str(getcurdate(), tp+i);
1280                 logmsg.string = tp;
1281                 logmsg.size = strlen(tp);
1282                 return logmsg;
1283         }
1284
1285         if (!targetdelta && (
1286                 cmpnum(newdelnum.string,"1.1")==0 ||
1287                 cmpnum(newdelnum.string,"1.0")==0
1288         ))
1289                 return initiallog;
1290
1291         if (logmsg.size) {
1292                 /*previous log available*/
1293             if (yesorno(true, "reuse log message of previous file? [yn](y): "))
1294                 return logmsg;
1295         }
1296
1297         /* now read string from stdin */
1298         logmsg = getsstdin("m", "log message", "", &logbuf);
1299
1300         /* now check whether the log message is not empty */
1301         if (logmsg.size)
1302                 return logmsg;
1303         return emptylog;
1304 }
1305
1306 /*  Make a linked list of Symbolic names  */
1307
1308         static void
1309 addassoclst(flag, sp)
1310         int flag;
1311         char const *sp;
1312 {
1313         struct Symrev *pt;
1314
1315         pt = talloc(struct Symrev);
1316         pt->ssymbol = sp;
1317         pt->override = flag;
1318         pt->nextsym = 0;
1319         *nextassoc = pt;
1320         nextassoc = &pt->nextsym;
1321 }