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: stable/9/gnu/usr.bin/rcs/ci/ci.c 50472 1999-08-27 23:37:10Z peter $")
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
cleanup()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
exiterr()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
addelta()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
addbranch(branchpoint,num,removedlock)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
addsyms(num)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
incnum(onum,nnum)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
removelock(delta)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 *
getcurdate()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
fixwork(mode_t newworkmode,time_t mtime)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
xpandfile(unexfile,delta,exname,dolog)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
getlogmsg()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
addassoclst(flag,sp)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 }
1319