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