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