xref: /dragonfly/gnu/usr.bin/rcs/ci/ci.c (revision 19fe1c42)
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
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
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
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
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
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
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
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 	if (!StrictLocks && myself(RCSstat.st_uid))
1198 	    return 0;
1199 	rcserror("no lock set by %s for revision %s", getcaller(), num);
1200 	return -1;
1201 }
1202 
1203 
1204 
1205 	static char const *
1206 getcurdate()
1207 /* Return a pointer to the current date.  */
1208 {
1209 	static char buffer[datesize]; /* date buffer */
1210 
1211 	if (!buffer[0])
1212 		time2date(now(), buffer);
1213         return buffer;
1214 }
1215 
1216 	static int
1217 #if has_prototypes
1218 fixwork(mode_t newworkmode, time_t mtime)
1219   /* The `#if has_prototypes' is needed because mode_t might promote to int.  */
1220 #else
1221   fixwork(newworkmode, mtime)
1222 	mode_t newworkmode;
1223 	time_t mtime;
1224 #endif
1225 {
1226 	return
1227 			1 < workstat.st_nlink
1228 		    ||	(newworkmode&S_IWUSR && !myself(workstat.st_uid))
1229 		    ||	setmtime(workname, mtime) != 0
1230 		?   -1
1231 	    :	workstat.st_mode == newworkmode  ?  0
1232 #if has_fchmod
1233 	    :	fchmod(Ifileno(workptr), newworkmode) == 0  ?  0
1234 #endif
1235 #if bad_chmod_close
1236 	    :	-1
1237 #else
1238 	    :	chmod(workname, newworkmode)
1239 #endif
1240 	;
1241 }
1242 
1243 	static int
1244 xpandfile(unexfile, delta, exname, dolog)
1245 	RILE *unexfile;
1246 	struct hshentry const *delta;
1247 	char const **exname;
1248 	int dolog;
1249 /*
1250  * Read unexfile and copy it to a
1251  * file, performing keyword substitution with data from delta.
1252  * Return -1 if unsuccessful, 1 if expansion occurred, 0 otherwise.
1253  * If successful, stores the stream descriptor into *EXFILEP
1254  * and its name into *EXNAME.
1255  */
1256 {
1257 	char const *targetname;
1258 	int e, r;
1259 
1260 	targetname = makedirtemp(1);
1261 	if (!(exfile = fopenSafer(targetname, FOPEN_W_WORK))) {
1262 		eerror(targetname);
1263 		workerror("can't build working file");
1264 		return -1;
1265         }
1266 	r = 0;
1267 	if (MIN_UNEXPAND <= Expand)
1268 		fastcopy(unexfile,exfile);
1269 	else {
1270 		for (;;) {
1271 			e = expandline(
1272 				unexfile, exfile, delta, false, (FILE*)0, dolog
1273 			);
1274 			if (e < 0)
1275 				break;
1276 			r |= e;
1277 			if (e <= 1)
1278 				break;
1279 		}
1280 	}
1281 	*exname = targetname;
1282 	return r & 1;
1283 }
1284 
1285 
1286 
1287 
1288 /* --------------------- G E T L O G M S G --------------------------------*/
1289 
1290 
1291 	static struct cbuf
1292 getlogmsg()
1293 /* Obtain and yield a log message.
1294  * If a log message is given with -m, yield that message.
1295  * If this is the initial revision, yield a standard log message.
1296  * Otherwise, reads a character string from the terminal.
1297  * Stops after reading EOF or a single '.' on a
1298  * line. getlogmsg prompts the first time it is called for the
1299  * log message; during all later calls it asks whether the previous
1300  * log message can be reused.
1301  */
1302 {
1303 	static char const
1304 		emptych[] = EMPTYLOG,
1305 		initialch[] = "Initial revision";
1306 	static struct cbuf const
1307 		emptylog = { emptych, sizeof(emptych)-sizeof(char) },
1308 		initiallog = { initialch, sizeof(initialch)-sizeof(char) };
1309 	static struct buf logbuf;
1310 	static struct cbuf logmsg;
1311 
1312 	register char *tp;
1313 	register size_t i;
1314 	char const *caller;
1315 
1316 	if (msg.size) return msg;
1317 
1318 	if (keepflag) {
1319 		/* generate std. log message */
1320 		caller = getcaller();
1321 		i = sizeof(ciklog)+strlen(caller)+3;
1322 		bufalloc(&logbuf, i + datesize + zonelenmax);
1323 		tp = logbuf.string;
1324 		VOID sprintf(tp, "%s%s at ", ciklog, caller);
1325 		VOID date2str(getcurdate(), tp+i);
1326 		logmsg.string = tp;
1327 		logmsg.size = strlen(tp);
1328 		return logmsg;
1329 	}
1330 
1331 	if (!targetdelta && (
1332 		cmpnum(newdelnum.string,"1.1")==0 ||
1333 		cmpnum(newdelnum.string,"1.0")==0
1334 	))
1335 		return initiallog;
1336 
1337 	if (logmsg.size) {
1338                 /*previous log available*/
1339 	    if (yesorno(true, "reuse log message of previous file? [yn](y): "))
1340 		return logmsg;
1341         }
1342 
1343         /* now read string from stdin */
1344 	logmsg = getsstdin("m", "log message", "", &logbuf);
1345 
1346         /* now check whether the log message is not empty */
1347 	if (logmsg.size)
1348 		return logmsg;
1349 	return emptylog;
1350 }
1351 
1352 /*  Make a linked list of Symbolic names  */
1353 
1354         static void
1355 addassoclst(flag, sp)
1356 	int flag;
1357 	char const *sp;
1358 {
1359         struct Symrev *pt;
1360 
1361 	pt = talloc(struct Symrev);
1362 	pt->ssymbol = sp;
1363 	pt->override = flag;
1364 	pt->nextsym = 0;
1365 	*nextassoc = pt;
1366 	nextassoc = &pt->nextsym;
1367 }
1368 
1369 static char const alphabet[62] =
1370   "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1371 
1372 /* Divide BUF by D, returning the remainder.  Replace BUF by the
1373    quotient.  BUF[0] is the most significant part of BUF.
1374    D must not exceed UINT_MAX >> CHAR_BIT.  */
1375 static unsigned int
1376 divide_by (unsigned char buf[COMMITID_RAW_SIZE], unsigned int d)
1377 {
1378   unsigned int carry = 0;
1379   int i;
1380   for (i = 0; i < COMMITID_RAW_SIZE; i++)
1381     {
1382       unsigned int byte = buf[i];
1383       unsigned int dividend = (carry << CHAR_BIT) + byte;
1384       buf[i] = dividend / d;
1385       carry = dividend % d;
1386     }
1387   return carry;
1388 }
1389 
1390 static void
1391 convert (char const input[COMMITID_RAW_SIZE], char *output)
1392 {
1393   static char const zero[COMMITID_RAW_SIZE] = { 0, };
1394   unsigned char buf[COMMITID_RAW_SIZE];
1395   size_t o = 0;
1396   memcpy (buf, input, COMMITID_RAW_SIZE);
1397   while (memcmp (buf, zero, COMMITID_RAW_SIZE) != 0)
1398     output[o++] = alphabet[divide_by (buf, sizeof alphabet)];
1399   if (! o)
1400     output[o++] = '0';
1401   output[o] = '\0';
1402 }
1403