111894Speter/* Check in revisions of RCS files from working files. */ 211894Speter 311894Speter/* Copyright 1982, 1988, 1989 Walter Tichy 411894Speter Copyright 1990, 1991, 1992, 1993, 1994, 1995 Paul Eggert 59Sjkh Distributed under license by the Free Software Foundation, Inc. 69Sjkh 79SjkhThis file is part of RCS. 89Sjkh 99SjkhRCS is free software; you can redistribute it and/or modify 109Sjkhit under the terms of the GNU General Public License as published by 119Sjkhthe Free Software Foundation; either version 2, or (at your option) 129Sjkhany later version. 139Sjkh 149SjkhRCS is distributed in the hope that it will be useful, 159Sjkhbut WITHOUT ANY WARRANTY; without even the implied warranty of 169SjkhMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 179SjkhGNU General Public License for more details. 189Sjkh 199SjkhYou should have received a copy of the GNU General Public License 2011894Speteralong with RCS; see the file COPYING. 2111894SpeterIf not, write to the Free Software Foundation, 2211894Speter59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 239Sjkh 249SjkhReport problems and direct all questions to: 259Sjkh 269Sjkh rcs-bugs@cs.purdue.edu 279Sjkh 289Sjkh*/ 299Sjkh 309Sjkh/* 3111894Speter * Revision 5.30 1995/06/16 06:19:24 eggert 3211894Speter * Update FSF address. 338858Srgrimes * 3411894Speter * Revision 5.29 1995/06/01 16:23:43 eggert 3511894Speter * (main): Add -kb. 3611894Speter * Use `cmpdate', not `cmpnum', to compare dates. 3711894Speter * This is for MKS RCS's incompatible 20th-century date format. 3811894Speter * Don't worry about errno after ftruncate fails. 3911894Speter * Fix input file rewinding bug when large_memory && !maps_memory 4011894Speter * and checking in a branch tip. 4111894Speter * 4211894Speter * (fixwork): Fall back on chmod if fchmod fails, since it might be ENOSYS. 4311894Speter * 4411894Speter * Revision 5.28 1994/03/20 04:52:58 eggert 4511894Speter * Do not generate a corrupted RCS file if the user modifies the working file 4611894Speter * while `ci' is running. 4711894Speter * Do not remove the lock when `ci -l' reverts. 4811894Speter * Move buffer-flushes out of critical sections, since they aren't critical. 4911894Speter * Use ORCSerror to clean up after a fatal error. 5011894Speter * Specify subprocess input via file descriptor, not file name. 5111894Speter * 5211894Speter * Revision 5.27 1993/11/09 17:40:15 eggert 5311894Speter * -V now prints version on stdout and exits. Don't print usage twice. 5411894Speter * 5511894Speter * Revision 5.26 1993/11/03 17:42:27 eggert 5611894Speter * Add -z. Don't subtract from RCS file timestamp even if -T. 5711894Speter * Scan for and use Name keyword if -k. 5811894Speter * Don't discard ignored phrases. Improve quality of diagnostics. 5911894Speter * 6011894Speter * Revision 5.25 1992/07/28 16:12:44 eggert 6111894Speter * Add -i, -j, -V. Check that working and RCS files are distinct. 6211894Speter * 6311894Speter * Revision 5.24 1992/02/17 23:02:06 eggert 6411894Speter * `-rREV' now just specifies a revision REV; only bare `-r' reverts to default. 6511894Speter * Add -T. 6611894Speter * 6711894Speter * Revision 5.23 1992/01/27 16:42:51 eggert 6811894Speter * Always unlock branchpoint if caller has a lock. 6911894Speter * Add support for bad_chmod_close, bad_creat0. lint -> RCS_lint 7011894Speter * 7111894Speter * Revision 5.22 1992/01/06 02:42:34 eggert 7211894Speter * Invoke utime() before chmod() to keep some buggy systems happy. 7311894Speter * 749Sjkh * Revision 5.21 1991/11/20 17:58:07 eggert 759Sjkh * Don't read the delta tree from a nonexistent RCS file. 769Sjkh * 779Sjkh * Revision 5.20 1991/10/07 17:32:46 eggert 789Sjkh * Fix log bugs. Remove lint. 799Sjkh * 809Sjkh * Revision 5.19 1991/09/26 23:10:30 eggert 819Sjkh * Plug file descriptor leak. 829Sjkh * 839Sjkh * Revision 5.18 1991/09/18 07:29:10 eggert 849Sjkh * Work around a common ftruncate() bug. 859Sjkh * 869Sjkh * Revision 5.17 1991/09/10 22:15:46 eggert 879Sjkh * Fix test for redirected stdin. 889Sjkh * 899Sjkh * Revision 5.16 1991/08/19 23:17:54 eggert 909Sjkh * When there are no changes, revert to previous revision instead of aborting. 919Sjkh * Add piece tables, -M, -r$. Tune. 929Sjkh * 939Sjkh * Revision 5.15 1991/04/21 11:58:14 eggert 949Sjkh * Ensure that working file is newer than RCS file after ci -[lu]. 959Sjkh * Add -x, RCSINIT, MS-DOS support. 969Sjkh * 979Sjkh * Revision 5.14 1991/02/28 19:18:47 eggert 989Sjkh * Don't let a setuid ci create a new RCS file; rcs -i -a must be run first. 999Sjkh * Fix ci -ko -l mode bug. Open work file at most once. 1009Sjkh * 1019Sjkh * Revision 5.13 1991/02/25 07:12:33 eggert 1029Sjkh * getdate -> getcurdate (SVR4 name clash) 1039Sjkh * 1049Sjkh * Revision 5.12 1990/12/31 01:00:12 eggert 1059Sjkh * Don't use uninitialized storage when handling -{N,n}. 1069Sjkh * 1079Sjkh * Revision 5.11 1990/12/04 05:18:36 eggert 1089Sjkh * Use -I for prompts and -q for diagnostics. 1099Sjkh * 1109Sjkh * Revision 5.10 1990/11/05 20:30:10 eggert 1119Sjkh * Don't remove working file when aborting due to no changes. 1129Sjkh * 1139Sjkh * Revision 5.9 1990/11/01 05:03:23 eggert 1149Sjkh * Add -I and new -t behavior. Permit arbitrary data in logs. 1159Sjkh * 1169Sjkh * Revision 5.8 1990/10/04 06:30:09 eggert 1179Sjkh * Accumulate exit status across files. 1189Sjkh * 1199Sjkh * Revision 5.7 1990/09/25 20:11:46 hammer 1209Sjkh * fixed another small typo 1219Sjkh * 1229Sjkh * Revision 5.6 1990/09/24 21:48:50 hammer 1239Sjkh * added cleanups from Paul Eggert. 1249Sjkh * 1259Sjkh * Revision 5.5 1990/09/21 06:16:38 hammer 1269Sjkh * made it handle multiple -{N,n}'s. Also, made it treat re-directed stdin 1279Sjkh * the same as the terminal 1289Sjkh * 1299Sjkh * Revision 5.4 1990/09/20 02:38:51 eggert 1309Sjkh * ci -k now checks dates more thoroughly. 1319Sjkh * 1329Sjkh * Revision 5.3 1990/09/11 02:41:07 eggert 1339Sjkh * Fix revision bug with `ci -k file1 file2'. 1349Sjkh * 1359Sjkh * Revision 5.2 1990/09/04 08:02:10 eggert 1369Sjkh * Permit adjacent revisions with identical time stamps (possible on fast hosts). 1379Sjkh * Improve incomplete line handling. Standardize yes-or-no procedure. 1389Sjkh * 1399Sjkh * Revision 5.1 1990/08/29 07:13:44 eggert 1409Sjkh * Expand locker value like co. Clean old log messages too. 1419Sjkh * 1429Sjkh * Revision 5.0 1990/08/22 08:10:00 eggert 1439Sjkh * Don't require a final newline. 1449Sjkh * Make lock and temp files faster and safer. 1459Sjkh * Remove compile-time limits; use malloc instead. 1469Sjkh * Permit dates past 1999/12/31. Switch to GMT. 1479Sjkh * Add setuid support. Don't pass +args to diff. Check diff's output. 1489Sjkh * Ansify and Posixate. Add -k, -V. Remove snooping. Tune. 1499Sjkh * Check diff's output. 1509Sjkh * 1519Sjkh * Revision 4.9 89/05/01 15:10:54 narten 1529Sjkh * changed copyright header to reflect current distribution rules 1538858Srgrimes * 1549Sjkh * Revision 4.8 88/11/08 13:38:23 narten 1559Sjkh * changes from root@seismo.CSS.GOV (Super User) 1569Sjkh * -d with no arguments uses the mod time of the file it is checking in 1578858Srgrimes * 1589Sjkh * Revision 4.7 88/08/09 19:12:07 eggert 1599Sjkh * Make sure workfile is a regular file; use its mode if RCSfile doesn't have one. 1609Sjkh * Use execv(), not system(); allow cc -R; remove lint. 1619Sjkh * isatty(fileno(stdin)) -> ttystdin() 1628858Srgrimes * 1639Sjkh * Revision 4.6 87/12/18 11:34:41 narten 1649Sjkh * lint cleanups (from Guy Harris) 1658858Srgrimes * 1669Sjkh * Revision 4.5 87/10/18 10:18:48 narten 1679Sjkh * Updating version numbers. Changes relative to revision 1.1 are actually 1689Sjkh * relative to 4.3 1698858Srgrimes * 1709Sjkh * Revision 1.3 87/09/24 13:57:19 narten 1718858Srgrimes * Sources now pass through lint (if you ignore printf/sprintf/fprintf 1729Sjkh * warnings) 1738858Srgrimes * 1749Sjkh * Revision 1.2 87/03/27 14:21:33 jenkins 1759Sjkh * Port to suns 1768858Srgrimes * 1779Sjkh * Revision 4.3 83/12/15 12:28:54 wft 1789Sjkh * ci -u and ci -l now set mode of working file properly. 1798858Srgrimes * 1809Sjkh * Revision 4.2 83/12/05 13:40:54 wft 1819Sjkh * Merged with 3.9.1.1: added calls to clearerr(stdin). 1829Sjkh * made rewriteflag external. 1838858Srgrimes * 1849Sjkh * Revision 4.1 83/05/10 17:03:06 wft 1859Sjkh * Added option -d and -w, and updated assingment of date, etc. to new delta. 1869Sjkh * Added handling of default branches. 1879Sjkh * Option -k generates std. log message; fixed undef. pointer in reading of log. 1889Sjkh * Replaced getlock() with findlock(), link--unlink with rename(), 1899Sjkh * getpwuid() with getcaller(). 1909Sjkh * Moved all revision number generation to new routine addelta(). 1919Sjkh * Removed calls to stat(); now done by pairfilenames(). 1929Sjkh * Changed most calls to catchints() with restoreints(). 1939Sjkh * Directed all interactive messages to stderr. 1948858Srgrimes * 1959Sjkh * Revision 3.9.1.1 83/10/19 04:21:03 lepreau 1969Sjkh * Added clearerr(stdin) to getlogmsg() for re-reading stdin. 1978858Srgrimes * 1989Sjkh * Revision 3.9 83/02/15 15:25:44 wft 1999Sjkh * 4.2 prerelease 2008858Srgrimes * 2019Sjkh * Revision 3.9 83/02/15 15:25:44 wft 2029Sjkh * Added call to fastcopy() to copy remainder of RCS file. 2039Sjkh * 2049Sjkh * Revision 3.8 83/01/14 15:34:05 wft 2059Sjkh * Added ignoring of interrupts while new RCS file is renamed; 2069Sjkh * Avoids deletion of RCS files by interrupts. 2079Sjkh * 2089Sjkh * Revision 3.7 82/12/10 16:09:20 wft 2099Sjkh * Corrected checking of return code from diff. 2109Sjkh * 2119Sjkh * Revision 3.6 82/12/08 21:34:49 wft 2129Sjkh * Using DATEFORM to prepare date of checked-in revision; 2139Sjkh * Fixed return from addbranch(). 2149Sjkh * 2159Sjkh * Revision 3.5 82/12/04 18:32:42 wft 2169Sjkh * Replaced getdelta() with gettree(), SNOOPDIR with SNOOPFILE. Updated 2179Sjkh * field lockedby in removelock(), moved getlogmsg() before calling diff. 2189Sjkh * 2199Sjkh * Revision 3.4 82/12/02 13:27:13 wft 2209Sjkh * added option -k. 2219Sjkh * 2229Sjkh * Revision 3.3 82/11/28 20:53:31 wft 2239Sjkh * Added mustcheckin() to check for redundant checkins. 2249Sjkh * Added xpandfile() to do keyword expansion for -u and -l; 2259Sjkh * -m appends linefeed to log message if necessary. 2269Sjkh * getlogmsg() suppresses prompt if stdin is not a terminal. 2279Sjkh * Replaced keeplock with lockflag, fclose() with ffclose(), 2289Sjkh * %02d with %.2d, getlogin() with getpwuid(). 2299Sjkh * 2309Sjkh * Revision 3.2 82/10/18 20:57:23 wft 2319Sjkh * An RCS file inherits its mode during the first ci from the working file, 2329Sjkh * otherwise it stays the same, except that write permission is removed. 2339Sjkh * Fixed ci -l, added ci -u (both do an implicit co after the ci). 2349Sjkh * Fixed call to getlogin(), added call to getfullRCSname(), added check 2359Sjkh * for write error. 2369Sjkh * Changed conflicting identifiers. 2379Sjkh * 2389Sjkh * Revision 3.1 82/10/13 16:04:59 wft 2399Sjkh * fixed type of variables receiving from getc() (char -> int). 2409Sjkh * added include file dbm.h for getting BYTESIZ. This is used 2419Sjkh * to check the return code from diff portably. 2429Sjkh */ 2439Sjkh 2449Sjkh#include "rcsbase.h" 2459Sjkh 2469Sjkhstruct Symrev { 2479Sjkh char const *ssymbol; 2489Sjkh int override; 2499Sjkh struct Symrev * nextsym; 2509Sjkh}; 2519Sjkh 2529Sjkhstatic char const *getcurdate P((void)); 25311894Speterstatic int addbranch P((struct hshentry*,struct buf*,int)); 2549Sjkhstatic int addelta P((void)); 2559Sjkhstatic int addsyms P((char const*)); 25611894Speterstatic int fixwork P((mode_t,time_t)); 2579Sjkhstatic int removelock P((struct hshentry*)); 25811894Speterstatic int xpandfile P((RILE*,struct hshentry const*,char const**,int)); 2599Sjkhstatic struct cbuf getlogmsg P((void)); 2609Sjkhstatic void cleanup P((void)); 2619Sjkhstatic void incnum P((char const*,struct buf*)); 26211894Speterstatic void addassoclst P((int,char const*)); 2639Sjkh 2649Sjkhstatic FILE *exfile; 2659Sjkhstatic RILE *workptr; /* working file pointer */ 2669Sjkhstatic struct buf newdelnum; /* new revision number */ 2679Sjkhstatic struct cbuf msg; 2689Sjkhstatic int exitstatus; 2699Sjkhstatic int forceciflag; /* forces check in */ 2709Sjkhstatic int keepflag, keepworkingfile, rcsinitflag; 2719Sjkhstatic struct hshentries *gendeltas; /* deltas to be generated */ 2729Sjkhstatic struct hshentry *targetdelta; /* old delta to be generated */ 2739Sjkhstatic struct hshentry newdelta; /* new delta to be inserted */ 2749Sjkhstatic struct stat workstat; 27511894Speterstatic struct Symrev *assoclst, **nextassoc; 2769Sjkh 27750472SpetermainProg(ciId, "ci", "$FreeBSD$") 2789Sjkh{ 2799Sjkh static char const cmdusage[] = 28011894Speter "\nci usage: ci -{fIklMqru}[rev] -d[date] -mmsg -{nN}name -sstate -ttext -T -Vn -wwho -xsuff -zzone file ..."; 2819Sjkh static char const default_state[] = DEFAULTSTATE; 2829Sjkh 2839Sjkh char altdate[datesize]; 2849Sjkh char olddate[datesize]; 28511894Speter char newdatebuf[datesize + zonelenmax]; 28611894Speter char targetdatebuf[datesize + zonelenmax]; 2879Sjkh char *a, **newargv, *textfile; 2889Sjkh char const *author, *krev, *rev, *state; 28911894Speter char const *diffname, *expname; 29011894Speter char const *newworkname; 29111894Speter int initflag, mustread; 29211894Speter int lockflag, lockthis, mtimeflag, removedlock, Ttimeflag; 2939Sjkh int r; 29411894Speter int changedRCS, changework, dolog, newhead; 2959Sjkh int usestatdate; /* Use mod time of file for -d. */ 2969Sjkh mode_t newworkmode; /* mode for working file */ 29711894Speter time_t mtime, wtime; 2989Sjkh struct hshentry *workdelta; 2998858Srgrimes 3009Sjkh setrid(); 3019Sjkh 30211894Speter author = rev = state = textfile = 0; 30311894Speter initflag = lockflag = mustread = false; 3049Sjkh mtimeflag = false; 30511894Speter Ttimeflag = false; 3069Sjkh altdate[0]= '\0'; /* empty alternate date for -d */ 3079Sjkh usestatdate=false; 3089Sjkh suffixes = X_DEFAULT; 30911894Speter nextassoc = &assoclst; 3109Sjkh 3119Sjkh argc = getRCSINIT(argc, argv, &newargv); 3129Sjkh argv = newargv; 3139Sjkh while (a = *++argv, 0<--argc && *a++=='-') { 3149Sjkh switch (*a++) { 3159Sjkh 3169Sjkh case 'r': 31711894Speter if (*a) 31811894Speter goto revno; 3199Sjkh keepworkingfile = lockflag = false; 32011894Speter break; 32111894Speter 32211894Speter case 'l': 32311894Speter keepworkingfile = lockflag = true; 3249Sjkh revno: 3259Sjkh if (*a) { 3269Sjkh if (rev) warn("redefinition of revision number"); 3279Sjkh rev = a; 3289Sjkh } 3299Sjkh break; 3309Sjkh 3319Sjkh case 'u': 3329Sjkh keepworkingfile=true; lockflag=false; 3339Sjkh goto revno; 3349Sjkh 33511894Speter case 'i': 33611894Speter initflag = true; 33711894Speter goto revno; 33811894Speter 33911894Speter case 'j': 34011894Speter mustread = true; 34111894Speter goto revno; 34211894Speter 3439Sjkh case 'I': 3449Sjkh interactiveflag = true; 3459Sjkh goto revno; 3469Sjkh 3479Sjkh case 'q': 3489Sjkh quietflag=true; 3499Sjkh goto revno; 3509Sjkh 3519Sjkh case 'f': 3529Sjkh forceciflag=true; 3539Sjkh goto revno; 3549Sjkh 3559Sjkh case 'k': 3569Sjkh keepflag=true; 3579Sjkh goto revno; 3589Sjkh 3599Sjkh case 'm': 3609Sjkh if (msg.size) redefined('m'); 3619Sjkh msg = cleanlogmsg(a, strlen(a)); 3629Sjkh if (!msg.size) 36311894Speter error("missing message for -m option"); 3649Sjkh break; 3659Sjkh 3669Sjkh case 'n': 3679Sjkh if (!*a) { 3689Sjkh error("missing symbolic name after -n"); 3699Sjkh break; 3709Sjkh } 37111894Speter checkssym(a); 3729Sjkh addassoclst(false, a); 3739Sjkh break; 3748858Srgrimes 3759Sjkh case 'N': 3769Sjkh if (!*a) { 3779Sjkh error("missing symbolic name after -N"); 3789Sjkh break; 3799Sjkh } 38011894Speter checkssym(a); 3819Sjkh addassoclst(true, a); 3829Sjkh break; 3839Sjkh 3849Sjkh case 's': 3859Sjkh if (*a) { 3869Sjkh if (state) redefined('s'); 3879Sjkh checksid(a); 3889Sjkh state = a; 3899Sjkh } else 39011894Speter error("missing state for -s option"); 3919Sjkh break; 3929Sjkh 3939Sjkh case 't': 3949Sjkh if (*a) { 3959Sjkh if (textfile) redefined('t'); 3969Sjkh textfile = a; 3979Sjkh } 3989Sjkh break; 3999Sjkh 4009Sjkh case 'd': 4019Sjkh if (altdate[0] || usestatdate) 4029Sjkh redefined('d'); 40311894Speter altdate[0] = '\0'; 4049Sjkh if (!(usestatdate = !*a)) 4059Sjkh str2date(a, altdate); 4069Sjkh break; 4079Sjkh 4089Sjkh case 'M': 4099Sjkh mtimeflag = true; 4109Sjkh goto revno; 4119Sjkh 4129Sjkh case 'w': 4139Sjkh if (*a) { 4149Sjkh if (author) redefined('w'); 4159Sjkh checksid(a); 4169Sjkh author = a; 4179Sjkh } else 41811894Speter error("missing author for -w option"); 4199Sjkh break; 4209Sjkh 4219Sjkh case 'x': 4229Sjkh suffixes = a; 4239Sjkh break; 4249Sjkh 4259Sjkh case 'V': 4269Sjkh setRCSversion(*argv); 4279Sjkh break; 4289Sjkh 42911894Speter case 'z': 43011894Speter zone_set(a); 43111894Speter break; 4329Sjkh 43311894Speter case 'T': 43411894Speter if (!*a) { 43511894Speter Ttimeflag = true; 43611894Speter break; 43711894Speter } 43811894Speter /* fall into */ 4399Sjkh default: 44011894Speter error("unknown option: %s%s", *argv, cmdusage); 4419Sjkh }; 4429Sjkh } /* end processing of options */ 4439Sjkh 44411894Speter /* Handle all pathnames. */ 44511894Speter if (nerror) cleanup(); 44611894Speter else if (argc < 1) faterror("no input file%s", cmdusage); 44711894Speter else for (; 0 < argc; cleanup(), ++argv, --argc) { 44811894Speter targetdelta = 0; 4499Sjkh ffree(); 4509Sjkh 45111894Speter switch (pairnames(argc, argv, rcswriteopen, mustread, false)) { 4529Sjkh 4539Sjkh case -1: /* New RCS file */ 4549Sjkh# if has_setuid && has_getuid 4559Sjkh if (euid() != ruid()) { 45611894Speter workerror("setuid initial checkin prohibited; use `rcs -i -a' first"); 4579Sjkh continue; 4589Sjkh } 4599Sjkh# endif 4609Sjkh rcsinitflag = true; 4619Sjkh break; 4629Sjkh 4639Sjkh case 0: /* Error */ 4649Sjkh continue; 4659Sjkh 4669Sjkh case 1: /* Normal checkin with prev . RCS file */ 46711894Speter if (initflag) { 46811894Speter rcserror("already exists"); 46911894Speter continue; 47011894Speter } 4719Sjkh rcsinitflag = !Head; 4729Sjkh } 4739Sjkh 47411894Speter /* 47511894Speter * RCSname contains the name of the RCS file, and 47611894Speter * workname contains the name of the working file. 4779Sjkh * If the RCS file exists, finptr contains the file descriptor for the 47811894Speter * RCS file, and RCSstat is set. The admin node is initialized. 4799Sjkh */ 4809Sjkh 48111894Speter diagnose("%s <-- %s\n", RCSname, workname); 4829Sjkh 48311894Speter if (!(workptr = Iopen(workname, FOPEN_R_WORK, &workstat))) { 48411894Speter eerror(workname); 4859Sjkh continue; 4869Sjkh } 4879Sjkh 48811894Speter if (finptr) { 48911894Speter if (same_file(RCSstat, workstat, 0)) { 49011894Speter rcserror("RCS file is the same as working file %s.", 49111894Speter workname 49211894Speter ); 49311894Speter continue; 49411894Speter } 49511894Speter if (!checkaccesslist()) 49611894Speter continue; 49711894Speter } 49811894Speter 4999Sjkh krev = rev; 5009Sjkh if (keepflag) { 5019Sjkh /* get keyword values from working file */ 5029Sjkh if (!getoldkeys(workptr)) continue; 5039Sjkh if (!rev && !*(krev = prevrev.string)) { 50411894Speter workerror("can't find a revision number"); 5059Sjkh continue; 5069Sjkh } 5079Sjkh if (!*prevdate.string && *altdate=='\0' && usestatdate==false) 50811894Speter workwarn("can't find a date"); 5099Sjkh if (!*prevauthor.string && !author) 51011894Speter workwarn("can't find an author"); 5119Sjkh if (!*prevstate.string && !state) 51211894Speter workwarn("can't find a state"); 5139Sjkh } /* end processing keepflag */ 5149Sjkh 5159Sjkh /* Read the delta tree. */ 5169Sjkh if (finptr) 5179Sjkh gettree(); 5189Sjkh 5199Sjkh /* expand symbolic revision number */ 5209Sjkh if (!fexpandsym(krev, &newdelnum, workptr)) 5219Sjkh continue; 5229Sjkh 5239Sjkh /* splice new delta into tree */ 5249Sjkh if ((removedlock = addelta()) < 0) 5259Sjkh continue; 5269Sjkh 5279Sjkh newdelta.num = newdelnum.string; 52811894Speter newdelta.branches = 0; 52911894Speter newdelta.lockedby = 0; /* This might be changed by addlock(). */ 5309Sjkh newdelta.selector = true; 53111894Speter newdelta.name = 0; 53211894Speter clear_buf(&newdelta.ig); 53311894Speter clear_buf(&newdelta.igtext); 5349Sjkh /* set author */ 53511894Speter if (author) 5369Sjkh newdelta.author=author; /* set author given by -w */ 5379Sjkh else if (keepflag && *prevauthor.string) 5389Sjkh newdelta.author=prevauthor.string; /* preserve old author if possible*/ 5399Sjkh else newdelta.author=getcaller();/* otherwise use caller's id */ 5409Sjkh newdelta.state = default_state; 54111894Speter if (state) 5429Sjkh newdelta.state=state; /* set state given by -s */ 5439Sjkh else if (keepflag && *prevstate.string) 5449Sjkh newdelta.state=prevstate.string; /* preserve old state if possible */ 5459Sjkh if (usestatdate) { 5469Sjkh time2date(workstat.st_mtime, altdate); 5479Sjkh } 5489Sjkh if (*altdate!='\0') 5499Sjkh newdelta.date=altdate; /* set date given by -d */ 5509Sjkh else if (keepflag && *prevdate.string) { 5519Sjkh /* Preserve old date if possible. */ 5529Sjkh str2date(prevdate.string, olddate); 5539Sjkh newdelta.date = olddate; 5549Sjkh } else 5559Sjkh newdelta.date = getcurdate(); /* use current date */ 5569Sjkh /* now check validity of date -- needed because of -d and -k */ 55711894Speter if (targetdelta && 55811894Speter cmpdate(newdelta.date,targetdelta->date) < 0) { 55911894Speter rcserror("Date %s precedes %s in revision %s.", 5609Sjkh date2str(newdelta.date, newdatebuf), 5619Sjkh date2str(targetdelta->date, targetdatebuf), 5629Sjkh targetdelta->num 5639Sjkh ); 5649Sjkh continue; 5659Sjkh } 5669Sjkh 5679Sjkh 56811894Speter if (lockflag && addlock(&newdelta, true) < 0) continue; 56911894Speter 57011894Speter if (keepflag && *prevname.string) 57111894Speter if (addsymbol(newdelta.num, prevname.string, false) < 0) 57211894Speter continue; 5739Sjkh if (!addsyms(newdelta.num)) 5749Sjkh continue; 5759Sjkh 5768858Srgrimes 57711894Speter putadmin(); 5789Sjkh puttree(Head,frewrite); 5799Sjkh putdesc(false,textfile); 5809Sjkh 58111894Speter changework = Expand < MIN_UNCHANGED_EXPAND; 58211894Speter dolog = true; 5839Sjkh lockthis = lockflag; 5849Sjkh workdelta = &newdelta; 5859Sjkh 5869Sjkh /* build rest of file */ 5879Sjkh if (rcsinitflag) { 58811894Speter diagnose("initial revision: %s\n", newdelta.num); 5899Sjkh /* get logmessage */ 5909Sjkh newdelta.log=getlogmsg(); 59111894Speter putdftext(&newdelta, workptr, frewrite, false); 5929Sjkh RCSstat.st_mode = workstat.st_mode; 59311894Speter RCSstat.st_nlink = 0; 5949Sjkh changedRCS = true; 5959Sjkh } else { 59611894Speter diffname = maketemp(0); 5979Sjkh newhead = Head == &newdelta; 5989Sjkh if (!newhead) 5999Sjkh foutptr = frewrite; 60011894Speter expname = buildrevision( 6019Sjkh gendeltas, targetdelta, (FILE*)0, false 6029Sjkh ); 6039Sjkh if ( 6049Sjkh !forceciflag && 60511894Speter strcmp(newdelta.state, targetdelta->state) == 0 && 6069Sjkh (changework = rcsfcmp( 60711894Speter workptr, &workstat, expname, targetdelta 6089Sjkh )) <= 0 6099Sjkh ) { 6109Sjkh diagnose("file is unchanged; reverting to previous revision %s\n", 6119Sjkh targetdelta->num 6129Sjkh ); 6139Sjkh if (removedlock < lockflag) { 6149Sjkh diagnose("previous revision was not locked; ignoring -l option\n"); 6159Sjkh lockthis = 0; 6169Sjkh } 61711894Speter dolog = false; 61811894Speter if (! (changedRCS = lockflag<removedlock || assoclst)) 6199Sjkh workdelta = targetdelta; 6209Sjkh else { 6219Sjkh /* 6229Sjkh * We have started to build the wrong new RCS file. 6239Sjkh * Start over from the beginning. 6249Sjkh */ 6259Sjkh long hwm = ftell(frewrite); 6269Sjkh int bad_truncate; 62711894Speter Orewind(frewrite); 62811894Speter 62911894Speter /* 63011894Speter * Work around a common ftruncate() bug: 63111894Speter * NFS won't let you truncate a file that you 63211894Speter * currently lack permissions for, even if you 63311894Speter * had permissions when you opened it. 63411894Speter * Also, Posix 1003.1b-1993 sec 5.6.7.2 p 128 l 1022 63511894Speter * says ftruncate might fail because it's not supported. 63611894Speter */ 6379Sjkh# if !has_ftruncate 63811894Speter# undef ftruncate 63911894Speter# define ftruncate(fd,length) (-1) 6409Sjkh# endif 64111894Speter bad_truncate = ftruncate(fileno(frewrite), (off_t)0); 64211894Speter 6439Sjkh Irewind(finptr); 6449Sjkh Lexinit(); 6459Sjkh getadmin(); 6469Sjkh gettree(); 6479Sjkh if (!(workdelta = genrevs( 6489Sjkh targetdelta->num, (char*)0, (char*)0, (char*)0, 6499Sjkh &gendeltas 6509Sjkh ))) 6519Sjkh continue; 6529Sjkh workdelta->log = targetdelta->log; 6539Sjkh if (newdelta.state != default_state) 6549Sjkh workdelta->state = newdelta.state; 65511894Speter if (lockthis<removedlock && removelock(workdelta)<0) 6569Sjkh continue; 6579Sjkh if (!addsyms(workdelta->num)) 6589Sjkh continue; 65911894Speter if (dorewrite(true, true) != 0) 6609Sjkh continue; 6619Sjkh fastcopy(finptr, frewrite); 6629Sjkh if (bad_truncate) 6639Sjkh while (ftell(frewrite) < hwm) 6649Sjkh /* White out any earlier mistake with '\n's. */ 6659Sjkh /* This is unlikely. */ 6669Sjkh afputc('\n', frewrite); 6679Sjkh } 6689Sjkh } else { 66911894Speter int wfd = Ifileno(workptr); 67011894Speter struct stat checkworkstat; 67111894Speter char const *diffv[6 + !!OPEN_O_BINARY], **diffp; 67211894Speter# if large_memory && !maps_memory 67311894Speter FILE *wfile = workptr->stream; 67411894Speter long wfile_off; 67511894Speter# endif 67611894Speter# if !has_fflush_input && !(large_memory && maps_memory) 67711894Speter off_t wfd_off; 67811894Speter# endif 67911894Speter 6809Sjkh diagnose("new revision: %s; previous revision: %s\n", 68111894Speter newdelta.num, targetdelta->num 6829Sjkh ); 6839Sjkh newdelta.log = getlogmsg(); 68411894Speter# if !large_memory 68511894Speter Irewind(workptr); 68611894Speter# if has_fflush_input 68711894Speter if (fflush(workptr) != 0) 68811894Speter Ierror(); 68911894Speter# endif 69011894Speter# else 69111894Speter# if !maps_memory 69211894Speter if ( 69311894Speter (wfile_off = ftell(wfile)) == -1 69411894Speter || fseek(wfile, 0L, SEEK_SET) != 0 69511894Speter# if has_fflush_input 69611894Speter || fflush(wfile) != 0 69711894Speter# endif 69811894Speter ) 69911894Speter Ierror(); 70011894Speter# endif 70111894Speter# endif 70211894Speter# if !has_fflush_input && !(large_memory && maps_memory) 70311894Speter wfd_off = lseek(wfd, (off_t)0, SEEK_CUR); 70411894Speter if (wfd_off == -1 70511894Speter || (wfd_off != 0 70611894Speter && lseek(wfd, (off_t)0, SEEK_SET) != 0)) 70711894Speter Ierror(); 70811894Speter# endif 70911894Speter diffp = diffv; 71011894Speter *++diffp = DIFF; 71111894Speter *++diffp = DIFFFLAGS; 71211894Speter# if OPEN_O_BINARY 71311894Speter if (Expand == BINARY_EXPAND) 71411894Speter *++diffp = "--binary"; 71511894Speter# endif 71611894Speter *++diffp = newhead ? "-" : expname; 71711894Speter *++diffp = newhead ? expname : "-"; 71811894Speter *++diffp = 0; 71911894Speter switch (runv(wfd, diffname, diffv)) { 7209Sjkh case DIFF_FAILURE: case DIFF_SUCCESS: break; 72111894Speter default: rcsfaterror("diff failed"); 7229Sjkh } 72311894Speter# if !has_fflush_input && !(large_memory && maps_memory) 72411894Speter if (lseek(wfd, wfd_off, SEEK_CUR) == -1) 72511894Speter Ierror(); 72611894Speter# endif 72711894Speter# if large_memory && !maps_memory 72811894Speter if (fseek(wfile, wfile_off, SEEK_SET) != 0) 72911894Speter Ierror(); 73011894Speter# endif 7319Sjkh if (newhead) { 7329Sjkh Irewind(workptr); 73311894Speter putdftext(&newdelta, workptr, frewrite, false); 73411894Speter if (!putdtext(targetdelta,diffname,frewrite,true)) continue; 7359Sjkh } else 73611894Speter if (!putdtext(&newdelta,diffname,frewrite,true)) continue; 73711894Speter 73811894Speter /* 73911894Speter * Check whether the working file changed during checkin, 74011894Speter * to avoid producing an inconsistent RCS file. 74111894Speter */ 74211894Speter if ( 74311894Speter fstat(wfd, &checkworkstat) != 0 74411894Speter || workstat.st_mtime != checkworkstat.st_mtime 74511894Speter || workstat.st_size != checkworkstat.st_size 74611894Speter ) { 74711894Speter workerror("file changed during checkin"); 74811894Speter continue; 74911894Speter } 75011894Speter 7519Sjkh changedRCS = true; 7529Sjkh } 7539Sjkh } 75411894Speter 75511894Speter /* Deduce time_t of new revision if it is needed later. */ 75611894Speter wtime = (time_t)-1; 75711894Speter if (mtimeflag | Ttimeflag) 75811894Speter wtime = date2time(workdelta->date); 75911894Speter 76011894Speter if (donerewrite(changedRCS, 76111894Speter !Ttimeflag ? (time_t)-1 76211894Speter : finptr && wtime < RCSstat.st_mtime ? RCSstat.st_mtime 76311894Speter : wtime 76411894Speter ) != 0) 7659Sjkh continue; 7669Sjkh 7679Sjkh if (!keepworkingfile) { 7689Sjkh Izclose(&workptr); 76911894Speter r = un_link(workname); /* Get rid of old file */ 7709Sjkh } else { 7719Sjkh newworkmode = WORKMODE(RCSstat.st_mode, 7729Sjkh ! (Expand==VAL_EXPAND || lockthis < StrictLocks) 7739Sjkh ); 77411894Speter mtime = mtimeflag ? wtime : (time_t)-1; 7759Sjkh 7769Sjkh /* Expand if it might change or if we can't fix mode, time. */ 7779Sjkh if (changework || (r=fixwork(newworkmode,mtime)) != 0) { 7789Sjkh Irewind(workptr); 7799Sjkh /* Expand keywords in file. */ 7809Sjkh locker_expansion = lockthis; 78111894Speter workdelta->name = 78211894Speter namedrev( 78311894Speter assoclst ? assoclst->ssymbol 78411894Speter : keepflag && *prevname.string ? prevname.string 78511894Speter : rev, 78611894Speter workdelta 78711894Speter ); 7889Sjkh switch (xpandfile( 78911894Speter workptr, workdelta, &newworkname, dolog 7909Sjkh )) { 7919Sjkh default: 7929Sjkh continue; 7939Sjkh 7949Sjkh case 0: 7959Sjkh /* 7969Sjkh * No expansion occurred; try to reuse working file 7979Sjkh * unless we already tried and failed. 7989Sjkh */ 7999Sjkh if (changework) 8009Sjkh if ((r=fixwork(newworkmode,mtime)) == 0) 8019Sjkh break; 8029Sjkh /* fall into */ 8039Sjkh case 1: 80411894Speter Izclose(&workptr); 80511894Speter aflush(exfile); 80611894Speter ignoreints(); 80711894Speter r = chnamemod(&exfile, newworkname, 80811894Speter workname, 1, newworkmode, mtime 80911894Speter ); 81011894Speter keepdirtemp(newworkname); 81111894Speter restoreints(); 8129Sjkh } 8139Sjkh } 8149Sjkh } 8159Sjkh if (r != 0) { 81611894Speter eerror(workname); 8179Sjkh continue; 8189Sjkh } 8199Sjkh diagnose("done\n"); 8209Sjkh 82111894Speter } 8229Sjkh 8239Sjkh tempunlink(); 8249Sjkh exitmain(exitstatus); 8259Sjkh} /* end of main (ci) */ 8269Sjkh 8279Sjkh static void 8289Sjkhcleanup() 8299Sjkh{ 8309Sjkh if (nerror) exitstatus = EXIT_FAILURE; 8319Sjkh Izclose(&finptr); 8329Sjkh Izclose(&workptr); 8339Sjkh Ozclose(&exfile); 8349Sjkh Ozclose(&fcopy); 83511894Speter ORCSclose(); 8369Sjkh dirtempunlink(); 8379Sjkh} 8389Sjkh 83911894Speter#if RCS_lint 8409Sjkh# define exiterr ciExit 8419Sjkh#endif 84211894Speter void 8439Sjkhexiterr() 8449Sjkh{ 84511894Speter ORCSerror(); 8469Sjkh dirtempunlink(); 8479Sjkh tempunlink(); 8489Sjkh _exit(EXIT_FAILURE); 8499Sjkh} 8509Sjkh 8519Sjkh/*****************************************************************/ 8529Sjkh/* the rest are auxiliary routines */ 8539Sjkh 8549Sjkh 8559Sjkh static int 8569Sjkhaddelta() 8579Sjkh/* Function: Appends a delta to the delta tree, whose number is 8589Sjkh * given by newdelnum. Updates Head, newdelnum, newdelnumlength, 8599Sjkh * and the links in newdelta. 8609Sjkh * Return -1 on error, 1 if a lock is removed, 0 otherwise. 8619Sjkh */ 8629Sjkh{ 8639Sjkh register char *tp; 86411894Speter register int i; 8659Sjkh int removedlock; 86611894Speter int newdnumlength; /* actual length of new rev. num. */ 8679Sjkh 8689Sjkh newdnumlength = countnumflds(newdelnum.string); 8699Sjkh 8709Sjkh if (rcsinitflag) { 8719Sjkh /* this covers non-existing RCS file and a file initialized with rcs -i */ 87211894Speter if (newdnumlength==0 && Dbranch) { 8739Sjkh bufscpy(&newdelnum, Dbranch); 8749Sjkh newdnumlength = countnumflds(Dbranch); 8759Sjkh } 8769Sjkh if (newdnumlength==0) bufscpy(&newdelnum, "1.1"); 8779Sjkh else if (newdnumlength==1) bufscat(&newdelnum, ".1"); 8789Sjkh else if (newdnumlength>2) { 87911894Speter rcserror("Branch point doesn't exist for revision %s.", 88011894Speter newdelnum.string 88111894Speter ); 8829Sjkh return -1; 8839Sjkh } /* newdnumlength == 2 is OK; */ 8849Sjkh Head = &newdelta; 88511894Speter newdelta.next = 0; 8869Sjkh return 0; 8879Sjkh } 8889Sjkh if (newdnumlength==0) { 8899Sjkh /* derive new revision number from locks */ 8909Sjkh switch (findlock(true, &targetdelta)) { 8919Sjkh 8929Sjkh default: 8939Sjkh /* found two or more old locks */ 8949Sjkh return -1; 8959Sjkh 8969Sjkh case 1: 8979Sjkh /* found an old lock */ 8989Sjkh /* check whether locked revision exists */ 8999Sjkh if (!genrevs(targetdelta->num,(char*)0,(char*)0,(char*)0,&gendeltas)) 9009Sjkh return -1; 9019Sjkh if (targetdelta==Head) { 9029Sjkh /* make new head */ 9039Sjkh newdelta.next=Head; 9049Sjkh Head= &newdelta; 9059Sjkh } else if (!targetdelta->next && countnumflds(targetdelta->num)>2) { 9069Sjkh /* new tip revision on side branch */ 9079Sjkh targetdelta->next= &newdelta; 90811894Speter newdelta.next = 0; 9099Sjkh } else { 9109Sjkh /* middle revision; start a new branch */ 9119Sjkh bufscpy(&newdelnum, ""); 91211894Speter return addbranch(targetdelta, &newdelnum, 1); 9139Sjkh } 9149Sjkh incnum(targetdelta->num, &newdelnum); 9159Sjkh return 1; /* successful use of existing lock */ 9169Sjkh 9179Sjkh case 0: 9189Sjkh /* no existing lock; try Dbranch */ 9199Sjkh /* update newdelnum */ 9209Sjkh if (StrictLocks || !myself(RCSstat.st_uid)) { 92111894Speter rcserror("no lock set by %s", getcaller()); 9229Sjkh return -1; 9239Sjkh } 9249Sjkh if (Dbranch) { 9259Sjkh bufscpy(&newdelnum, Dbranch); 9269Sjkh } else { 9279Sjkh incnum(Head->num, &newdelnum); 9289Sjkh } 9299Sjkh newdnumlength = countnumflds(newdelnum.string); 9309Sjkh /* now fall into next statement */ 9319Sjkh } 9329Sjkh } 9339Sjkh if (newdnumlength<=2) { 9349Sjkh /* add new head per given number */ 9359Sjkh if(newdnumlength==1) { 9369Sjkh /* make a two-field number out of it*/ 9379Sjkh if (cmpnumfld(newdelnum.string,Head->num,1)==0) 9389Sjkh incnum(Head->num, &newdelnum); 9399Sjkh else 9409Sjkh bufscat(&newdelnum, ".1"); 9419Sjkh } 9429Sjkh if (cmpnum(newdelnum.string,Head->num) <= 0) { 94311894Speter rcserror("revision %s too low; must be higher than %s", 94411894Speter newdelnum.string, Head->num 94511894Speter ); 9469Sjkh return -1; 9479Sjkh } 9489Sjkh targetdelta = Head; 9499Sjkh if (0 <= (removedlock = removelock(Head))) { 9509Sjkh if (!genrevs(Head->num,(char*)0,(char*)0,(char*)0,&gendeltas)) 9519Sjkh return -1; 9529Sjkh newdelta.next = Head; 9539Sjkh Head = &newdelta; 9549Sjkh } 9559Sjkh return removedlock; 9569Sjkh } else { 9579Sjkh /* put new revision on side branch */ 9589Sjkh /*first, get branch point */ 9599Sjkh tp = newdelnum.string; 96011894Speter for (i = newdnumlength - ((newdnumlength&1) ^ 1); --i; ) 9619Sjkh while (*tp++ != '.') 96211894Speter continue; 9639Sjkh *--tp = 0; /* Kill final dot to get old delta temporarily. */ 96411894Speter if (!(targetdelta=genrevs(newdelnum.string,(char*)0,(char*)0,(char*)0,&gendeltas))) 9659Sjkh return -1; 9669Sjkh if (cmpnum(targetdelta->num, newdelnum.string) != 0) { 96711894Speter rcserror("can't find branch point %s", newdelnum.string); 9689Sjkh return -1; 9699Sjkh } 9709Sjkh *tp = '.'; /* Restore final dot. */ 97111894Speter return addbranch(targetdelta, &newdelnum, 0); 9729Sjkh } 9739Sjkh} 9749Sjkh 9759Sjkh 9769Sjkh 9779Sjkh static int 97811894Speteraddbranch(branchpoint, num, removedlock) 9799Sjkh struct hshentry *branchpoint; 9809Sjkh struct buf *num; 98111894Speter int removedlock; 9829Sjkh/* adds a new branch and branch delta at branchpoint. 9839Sjkh * If num is the null string, appends the new branch, incrementing 9849Sjkh * the highest branch number (initially 1), and setting the level number to 1. 9859Sjkh * the new delta and branchhead are in globals newdelta and newbranch, resp. 9869Sjkh * the new number is placed into num. 9879Sjkh * Return -1 on error, 1 if a lock is removed, 0 otherwise. 98811894Speter * If REMOVEDLOCK is 1, a lock was already removed. 9899Sjkh */ 9909Sjkh{ 9919Sjkh struct branchhead *bhead, **btrail; 9929Sjkh struct buf branchnum; 99311894Speter int result; 99411894Speter int field, numlength; 9959Sjkh static struct branchhead newbranch; /* new branch to be inserted */ 9969Sjkh 9979Sjkh numlength = countnumflds(num->string); 9989Sjkh 99911894Speter if (!branchpoint->branches) { 10009Sjkh /* start first branch */ 10019Sjkh branchpoint->branches = &newbranch; 10029Sjkh if (numlength==0) { 10039Sjkh bufscpy(num, branchpoint->num); 10049Sjkh bufscat(num, ".1.1"); 10059Sjkh } else if (numlength&1) 10069Sjkh bufscat(num, ".1"); 100711894Speter newbranch.nextbranch = 0; 10089Sjkh 10099Sjkh } else if (numlength==0) { 10109Sjkh /* append new branch to the end */ 10119Sjkh bhead=branchpoint->branches; 10129Sjkh while (bhead->nextbranch) bhead=bhead->nextbranch; 10139Sjkh bhead->nextbranch = &newbranch; 10149Sjkh bufautobegin(&branchnum); 10159Sjkh getbranchno(bhead->hsh->num, &branchnum); 10169Sjkh incnum(branchnum.string, num); 10179Sjkh bufautoend(&branchnum); 10189Sjkh bufscat(num, ".1"); 101911894Speter newbranch.nextbranch = 0; 10209Sjkh } else { 10219Sjkh /* place the branch properly */ 102211894Speter field = numlength - ((numlength&1) ^ 1); 10239Sjkh /* field of branch number */ 10249Sjkh btrail = &branchpoint->branches; 10259Sjkh while (0 < (result=cmpnumfld(num->string,(*btrail)->hsh->num,field))) { 10269Sjkh btrail = &(*btrail)->nextbranch; 10279Sjkh if (!*btrail) { 10289Sjkh result = -1; 10299Sjkh break; 10309Sjkh } 10319Sjkh } 10329Sjkh if (result < 0) { 10339Sjkh /* insert/append new branchhead */ 10349Sjkh newbranch.nextbranch = *btrail; 10359Sjkh *btrail = &newbranch; 10369Sjkh if (numlength&1) bufscat(num, ".1"); 10379Sjkh } else { 10389Sjkh /* branch exists; append to end */ 10399Sjkh bufautobegin(&branchnum); 10409Sjkh getbranchno(num->string, &branchnum); 104111894Speter targetdelta = genrevs( 104211894Speter branchnum.string, (char*)0, (char*)0, (char*)0, 104311894Speter &gendeltas 104411894Speter ); 10459Sjkh bufautoend(&branchnum); 10469Sjkh if (!targetdelta) 10479Sjkh return -1; 10489Sjkh if (cmpnum(num->string,targetdelta->num) <= 0) { 104911894Speter rcserror("revision %s too low; must be higher than %s", 105011894Speter num->string, targetdelta->num 105111894Speter ); 10529Sjkh return -1; 10539Sjkh } 105411894Speter if (!removedlock 105511894Speter && 0 <= (removedlock = removelock(targetdelta)) 105611894Speter ) { 10579Sjkh if (numlength&1) 10589Sjkh incnum(targetdelta->num,num); 10599Sjkh targetdelta->next = &newdelta; 10609Sjkh newdelta.next = 0; 10619Sjkh } 10629Sjkh return removedlock; 10639Sjkh /* Don't do anything to newbranch. */ 10649Sjkh } 10659Sjkh } 10669Sjkh newbranch.hsh = &newdelta; 106711894Speter newdelta.next = 0; 106811894Speter if (branchpoint->lockedby) 106911894Speter if (strcmp(branchpoint->lockedby, getcaller()) == 0) 107011894Speter return removelock(branchpoint); /* This returns 1. */ 107111894Speter return removedlock; 10729Sjkh} 10739Sjkh 10749Sjkh static int 10759Sjkhaddsyms(num) 10769Sjkh char const *num; 10779Sjkh{ 10789Sjkh register struct Symrev *p; 10799Sjkh 10809Sjkh for (p = assoclst; p; p = p->nextsym) 108111894Speter if (addsymbol(num, p->ssymbol, p->override) < 0) 10829Sjkh return false; 10839Sjkh return true; 10849Sjkh} 10859Sjkh 10869Sjkh 10879Sjkh static void 10889Sjkhincnum(onum,nnum) 10899Sjkh char const *onum; 10909Sjkh struct buf *nnum; 10919Sjkh/* Increment the last field of revision number onum by one and 10929Sjkh * place the result into nnum. 10939Sjkh */ 10949Sjkh{ 10959Sjkh register char *tp, *np; 10969Sjkh register size_t l; 10979Sjkh 10989Sjkh l = strlen(onum); 10999Sjkh bufalloc(nnum, l+2); 11009Sjkh np = tp = nnum->string; 11019Sjkh VOID strcpy(np, onum); 11029Sjkh for (tp = np + l; np != tp; ) 11039Sjkh if (isdigit(*--tp)) { 11049Sjkh if (*tp != '9') { 11059Sjkh ++*tp; 11069Sjkh return; 11079Sjkh } 11089Sjkh *tp = '0'; 11099Sjkh } else { 11109Sjkh tp++; 11119Sjkh break; 11129Sjkh } 11139Sjkh /* We changed 999 to 000; now change it to 1000. */ 11149Sjkh *tp = '1'; 11159Sjkh tp = np + l; 11169Sjkh *tp++ = '0'; 11179Sjkh *tp = 0; 11189Sjkh} 11199Sjkh 11209Sjkh 11219Sjkh 11229Sjkh static int 11239Sjkhremovelock(delta) 11249Sjkhstruct hshentry * delta; 11259Sjkh/* function: Finds the lock held by caller on delta, 11269Sjkh * removes it, and returns nonzero if successful. 11279Sjkh * Print an error message and return -1 if there is no such lock. 11289Sjkh * An exception is if !StrictLocks, and caller is the owner of 11299Sjkh * the RCS file. If caller does not have a lock in this case, 11309Sjkh * return 0; return 1 if a lock is actually removed. 11319Sjkh */ 11329Sjkh{ 113311894Speter register struct rcslock *next, **trail; 11349Sjkh char const *num; 11359Sjkh 11369Sjkh num=delta->num; 11379Sjkh for (trail = &Locks; (next = *trail); trail = &next->nextlock) 11389Sjkh if (next->delta == delta) 11399Sjkh if (strcmp(getcaller(), next->login) == 0) { 11409Sjkh /* We found a lock on delta by caller; delete it. */ 11419Sjkh *trail = next->nextlock; 11429Sjkh delta->lockedby = 0; 11439Sjkh return 1; 11449Sjkh } else { 114511894Speter rcserror("revision %s locked by %s", num, next->login); 11469Sjkh return -1; 11479Sjkh } 11489Sjkh if (!StrictLocks && myself(RCSstat.st_uid)) 11499Sjkh return 0; 115011894Speter rcserror("no lock set by %s for revision %s", getcaller(), num); 11519Sjkh return -1; 11529Sjkh} 11539Sjkh 11549Sjkh 11559Sjkh 11569Sjkh static char const * 11579Sjkhgetcurdate() 11589Sjkh/* Return a pointer to the current date. */ 11599Sjkh{ 11609Sjkh static char buffer[datesize]; /* date buffer */ 11619Sjkh 116211894Speter if (!buffer[0]) 116311894Speter time2date(now(), buffer); 11649Sjkh return buffer; 11659Sjkh} 11669Sjkh 11679Sjkh static int 11689Sjkh#if has_prototypes 116911894Speterfixwork(mode_t newworkmode, time_t mtime) 11709Sjkh /* The `#if has_prototypes' is needed because mode_t might promote to int. */ 11719Sjkh#else 11729Sjkh fixwork(newworkmode, mtime) 11739Sjkh mode_t newworkmode; 117411894Speter time_t mtime; 11759Sjkh#endif 11769Sjkh{ 11779Sjkh return 11789Sjkh 1 < workstat.st_nlink 117911894Speter || (newworkmode&S_IWUSR && !myself(workstat.st_uid)) 118011894Speter || setmtime(workname, mtime) != 0 11819Sjkh ? -1 118211894Speter : workstat.st_mode == newworkmode ? 0 118311894Speter#if has_fchmod 118411894Speter : fchmod(Ifileno(workptr), newworkmode) == 0 ? 0 118511894Speter#endif 118611894Speter#if bad_chmod_close 118711894Speter : -1 118811894Speter#else 118911894Speter : chmod(workname, newworkmode) 119011894Speter#endif 119111894Speter ; 11929Sjkh} 11939Sjkh 11949Sjkh static int 119511894Speterxpandfile(unexfile, delta, exname, dolog) 11969Sjkh RILE *unexfile; 11979Sjkh struct hshentry const *delta; 119811894Speter char const **exname; 119911894Speter int dolog; 12009Sjkh/* 12019Sjkh * Read unexfile and copy it to a 120211894Speter * file, performing keyword substitution with data from delta. 12039Sjkh * Return -1 if unsuccessful, 1 if expansion occurred, 0 otherwise. 12049Sjkh * If successful, stores the stream descriptor into *EXFILEP 120511894Speter * and its name into *EXNAME. 12069Sjkh */ 12079Sjkh{ 120811894Speter char const *targetname; 12099Sjkh int e, r; 12109Sjkh 121111894Speter targetname = makedirtemp(1); 121211894Speter if (!(exfile = fopenSafer(targetname, FOPEN_W_WORK))) { 121311894Speter eerror(targetname); 121411894Speter workerror("can't build working file"); 12159Sjkh return -1; 12169Sjkh } 12179Sjkh r = 0; 121811894Speter if (MIN_UNEXPAND <= Expand) 12199Sjkh fastcopy(unexfile,exfile); 12209Sjkh else { 12219Sjkh for (;;) { 122211894Speter e = expandline( 122311894Speter unexfile, exfile, delta, false, (FILE*)0, dolog 122411894Speter ); 12259Sjkh if (e < 0) 12269Sjkh break; 12279Sjkh r |= e; 12289Sjkh if (e <= 1) 12299Sjkh break; 12309Sjkh } 12319Sjkh } 123211894Speter *exname = targetname; 12339Sjkh return r & 1; 12349Sjkh} 12359Sjkh 12369Sjkh 12379Sjkh 12389Sjkh 12399Sjkh/* --------------------- G E T L O G M S G --------------------------------*/ 12409Sjkh 12419Sjkh 12429Sjkh static struct cbuf 12439Sjkhgetlogmsg() 12449Sjkh/* Obtain and yield a log message. 12459Sjkh * If a log message is given with -m, yield that message. 12469Sjkh * If this is the initial revision, yield a standard log message. 12479Sjkh * Otherwise, reads a character string from the terminal. 12489Sjkh * Stops after reading EOF or a single '.' on a 12499Sjkh * line. getlogmsg prompts the first time it is called for the 12509Sjkh * log message; during all later calls it asks whether the previous 12519Sjkh * log message can be reused. 12529Sjkh */ 12539Sjkh{ 12549Sjkh static char const 12559Sjkh emptych[] = EMPTYLOG, 12569Sjkh initialch[] = "Initial revision"; 12579Sjkh static struct cbuf const 12589Sjkh emptylog = { emptych, sizeof(emptych)-sizeof(char) }, 12599Sjkh initiallog = { initialch, sizeof(initialch)-sizeof(char) }; 12609Sjkh static struct buf logbuf; 12619Sjkh static struct cbuf logmsg; 12629Sjkh 12639Sjkh register char *tp; 12649Sjkh register size_t i; 12659Sjkh char const *caller; 12669Sjkh 12679Sjkh if (msg.size) return msg; 12689Sjkh 12699Sjkh if (keepflag) { 12709Sjkh /* generate std. log message */ 12719Sjkh caller = getcaller(); 12729Sjkh i = sizeof(ciklog)+strlen(caller)+3; 127311894Speter bufalloc(&logbuf, i + datesize + zonelenmax); 12749Sjkh tp = logbuf.string; 12759Sjkh VOID sprintf(tp, "%s%s at ", ciklog, caller); 12769Sjkh VOID date2str(getcurdate(), tp+i); 12779Sjkh logmsg.string = tp; 12789Sjkh logmsg.size = strlen(tp); 12799Sjkh return logmsg; 12809Sjkh } 12819Sjkh 12829Sjkh if (!targetdelta && ( 12839Sjkh cmpnum(newdelnum.string,"1.1")==0 || 12849Sjkh cmpnum(newdelnum.string,"1.0")==0 12859Sjkh )) 12869Sjkh return initiallog; 12879Sjkh 12889Sjkh if (logmsg.size) { 12899Sjkh /*previous log available*/ 12909Sjkh if (yesorno(true, "reuse log message of previous file? [yn](y): ")) 12919Sjkh return logmsg; 12929Sjkh } 12939Sjkh 12949Sjkh /* now read string from stdin */ 12959Sjkh logmsg = getsstdin("m", "log message", "", &logbuf); 12969Sjkh 12979Sjkh /* now check whether the log message is not empty */ 12989Sjkh if (logmsg.size) 12999Sjkh return logmsg; 13009Sjkh return emptylog; 13019Sjkh} 13029Sjkh 13039Sjkh/* Make a linked list of Symbolic names */ 13049Sjkh 13059Sjkh static void 13069Sjkhaddassoclst(flag, sp) 130711894Speter int flag; 130811894Speter char const *sp; 13099Sjkh{ 13109Sjkh struct Symrev *pt; 13118858Srgrimes 13129Sjkh pt = talloc(struct Symrev); 13139Sjkh pt->ssymbol = sp; 13149Sjkh pt->override = flag; 131511894Speter pt->nextsym = 0; 131611894Speter *nextassoc = pt; 131711894Speter nextassoc = &pt->nextsym; 13189Sjkh} 1319