1: /* 2: * RCS checkout operation 3: */ 4: static char rcsid[]= 5: "$Header: /usr/wft/RCS/SRC/RCS/co.c,v 3.7 83/02/15 15:27:07 wft Exp $ Purdue CS"; 6: /***************************************************************************** 7: * check out revisions from RCS files 8: ***************************************************************************** 9: * 10: * Copyright (C) 1982 by Walter F. Tichy 11: * Purdue University 12: * Computer Science Department 13: * West Lafayette, IN 47907 14: * 15: * All rights reserved. No part of this software may be sold or distributed 16: * in any form or by any means without the prior written permission of the 17: * author. 18: * Report problems and direct all inquiries to Tichy@purdue (ARPA net). 19: */ 20: 21: 22: /* $Log: co.c,v $ 23: * Revision 3.7 83/02/15 15:27:07 wft 24: * Added call to fastcopy() to copy remainder of RCS file. 25: * 26: * Revision 3.6 83/01/15 14:37:50 wft 27: * Added ignoring of interrupts while RCS file is renamed; this avoids 28: * deletion of RCS files during the unlink/link window. 29: * 30: * Revision 3.5 82/12/08 21:40:11 wft 31: * changed processing of -d to use DATEFORM; removed actual from 32: * call to preparejoin; re-fixed printing of done at the end. 33: * 34: * Revision 3.4 82/12/04 18:40:00 wft 35: * Replaced getdelta() with gettree(), SNOOPDIR with SNOOPFILE. 36: * Fixed printing of "done". 37: * 38: * Revision 3.3 82/11/28 22:23:11 wft 39: * Replaced getlogin() with getpwuid(), flcose() with ffclose(), 40: * %02d with %.2d, mode generation for working file with WORKMODE. 41: * Fixed nil printing. Fixed -j combined with -l and -p, and exit 42: * for non-existing revisions in preparejoin(). 43: * 44: * Revision 3.2 82/10/18 20:47:21 wft 45: * Mode of working file is now maintained even for co -l, but write permission 46: * is removed. 47: * The working file inherits its mode from the RCS file, plus write permission 48: * for the owner. The write permission is not given if locking is strict and 49: * co does not lock. 50: * An existing working file without write permission is deleted automatically. 51: * Otherwise, co asks (empty answer: abort co). 52: * Call to getfullRCSname() added, check for write error added, call 53: * for getlogin() fixed. 54: * 55: * Revision 3.1 82/10/13 16:01:30 wft 56: * fixed type of variables receiving from getc() (char -> int). 57: * removed unused variables. 58: */ 59: 60: 61: 62: 63: #include <pwd.h> 64: #include "rcsbase.h" 65: #include "time.h" 66: #include <sys/types.h> 67: #include <sys/stat.h> 68: 69: static char rcsbaseid[] = RCSBASE; 70: 71: extern FILE * fopen(); 72: extern int rename(); 73: extern struct passwd *getpwuid(); 74: extern char * malloc(); 75: extern struct hshentry * genrevs(); /*generate delta numbers */ 76: extern int nextc; /*next input character */ 77: extern int nerror; /*counter for errors */ 78: extern char * Kdesc; /*keyword for description */ 79: extern char * maketempfile(); /*temporary file name */ 80: extern char * buildrevision(); /*constructs desired revision */ 81: extern int buildjoin(); /*join several revisions */ 82: extern char * mktempfile(); /*temporary file name generator */ 83: extern struct lock * addlock(); /*add a new lock */ 84: extern long maketime(); /*convert parsed time to unix time. */ 85: extern struct tm * localtime(); /*convert unixtime into a tm-structure */ 86: extern int StrictLocks; 87: extern FILE * finptr; /* RCS input file */ 88: extern FILE * frewrite; /* new RCS file */ 89: 90: char * RCSfilename, * workfilename; 91: char * newRCSfilename, * neworkfilename; 92: int rewriteflag; /* indicates whether input should be echoed to frewrite */ 93: 94: char * date, * rev, * state, * author, * join; 95: char finaldate[datelength]; 96: 97: int lockflag, tostdout; 98: char * caller; /* caller's login; */ 99: extern quietflag; 100: 101: char numericrev[revlength]; /* holds expanded revision number */ 102: struct hshentry * gendeltas[hshsize]; /* stores deltas to be generated */ 103: struct hshentry * targetdelta; /* final delta to be generated */ 104: 105: char * joinlist[joinlength]; /* pointers to revisions to be joined */ 106: int lastjoin; /* index of last element in joinlist */ 107: 108: main (argc, argv) 109: int argc; 110: char * argv[]; 111: { 112: register c; 113: char * cmdusage; 114: struct stat RCSstat; 115: struct tm parseddate, *ftm; 116: char * rawdate; 117: long unixtime; 118: 119: catchints(); 120: cmdid = "co"; 121: cmdusage = "command format:\nco -l[rev] -p[rev] -q[rev] -r[rev] -ddate -sstate -w[login] -jjoinlist file ..."; 122: date = rev = state = author = join = nil; 123: lockflag = tostdout = quietflag = false; 124: caller=getpwuid(getuid())->pw_name; 125: 126: while (--argc,++argv, argc>=1 && ((*argv)[0] == '-')) { 127: switch ((*argv)[1]) { 128: 129: case 'l': 130: lockflag=true; 131: case 'r': 132: revno: if ((*argv)[2]!='\0') { 133: if (rev!=nil) warn("Redefinition of revision number"); 134: rev = (*argv)+2; 135: } 136: break; 137: 138: case 'p': 139: tostdout=true; 140: goto revno; 141: 142: case 'q': 143: quietflag=true; 144: goto revno; 145: 146: case 'd': 147: if ((*argv)[2]!='\0') { 148: if (date!=nil) warn("Redefinition of -d option"); 149: rawdate=(*argv)+2; 150: } 151: /* process date/time */ 152: if (partime(rawdate,&parseddate)==0) 153: faterror("Can't parse date/time: %s",rawdate); 154: if ((unixtime=maketime(&parseddate))== 0L) 155: faterror("Inconsistent date/time: %s",rawdate); 156: ftm=localtime(&unixtime); 157: sprintf(finaldate,DATEFORM, 158: ftm->tm_year,ftm->tm_mon+1,ftm->tm_mday,ftm->tm_hour,ftm->tm_min,ftm->tm_sec); 159: date=finaldate; 160: break; 161: 162: case 'j': 163: if ((*argv)[2]!='\0'){ 164: if (join!=nil)warn("Redefinition of -j option"); 165: join = (*argv)+2; 166: } 167: break; 168: 169: case 's': 170: if ((*argv)[2]!='\0'){ 171: if (state!=nil)warn("Redefinition of -s option"); 172: state = (*argv)+2; 173: } 174: break; 175: 176: case 'w': 177: if (author!=nil)warn("Redefinition of -w option"); 178: if ((*argv)[2]!='\0') 179: author = (*argv)+2; 180: else author = caller; 181: break; 182: 183: default: 184: faterror("unknown option: %s\n%s", *argv,cmdusage); 185: 186: }; 187: } /* end of option processing */ 188: 189: if (argc<1) faterror("No input file\n%s",cmdusage); 190: 191: /* now handle all filenames */ 192: do { 193: rewriteflag=false; 194: finptr=frewrite=NULL; 195: neworkfilename=nil; 196: 197: if (!pairfilenames(argc,argv,true,tostdout)) continue; 198: 199: /* now RCSfilename contains the name of the RCS file, and finptr 200: * the file descriptor. If tostdout is false, workfilename contains 201: * the name of the working file, otherwise undefined (not nil!). 202: */ 203: diagnose("%s --> %s", RCSfilename,tostdout?"stdout":workfilename); 204: 205: fstat(fileno(finptr),&RCSstat); /* get file status, esp. the mode */ 206: 207: if (!tostdout && !trydiraccess(workfilename)) continue; /* give up */ 208: if (lockflag && !checkaccesslist(caller)) continue; /* give up */ 209: if (!trysema(RCSfilename,lockflag)) continue; /* give up */ 210: 211: 212: gettree(); /* reads in the delta tree */ 213: 214: if (Head==nil) { 215: /* no revisions; create empty file */ 216: diagnose("no revisions present; generating empty revision 0.0"); 217: if (!tostdout) 218: if (!creatempty(workfilename)) continue; 219: else putchar('\0'); /* end of file */ 220: /* Can't reserve a delta, so don't call addlock */ 221: } else { 222: /* expand symbolic revision number */ 223: if (!expandsym(rev,numericrev)) 224: continue; 225: /* get numbers of deltas to be generated */ 226: if (!(targetdelta=genrevs(numericrev,date,author,state,gendeltas))) 227: continue; 228: /* check reservations */ 229: if (lockflag && !addlock(targetdelta,caller)) 230: continue; 231: 232: if (join && !preparejoin()) continue; 233: 234: diagnose("revision %s %s",targetdelta->num, 235: lockflag?"(locked)":""); 236: 237: /* remove old working file if necessary */ 238: if (!tostdout) 239: if (!rmoldfile(workfilename)) continue; 240: 241: /* prepare for rewriting the RCS file */ 242: if (lockflag) { 243: newRCSfilename=mktempfile(RCSfilename,NEWRCSFILE); 244: if ((frewrite=fopen(newRCSfilename, "w"))==NULL) { 245: error("Can't open file %s",newRCSfilename); 246: continue; 247: } 248: putadmin(frewrite); 249: puttree(Head,frewrite); 250: fprintf(frewrite, "\n\n%s%c",Kdesc,nextc); 251: rewriteflag=true; 252: } 253: 254: /* skip description */ 255: getdesc(false); /* don't echo*/ 256: 257: if (!(neworkfilename=buildrevision(gendeltas,targetdelta, 258: tostdout?(join!=nil?"/tmp/":nil):workfilename,true))) 259: continue; 260: 261: if (lockflag&&nerror==0) { 262: /* rewrite the rest of the RCSfile */ 263: fastcopy(finptr,frewrite); 264: ffclose(frewrite); frewrite=NULL; 265: ignoreints(); 266: if (rename(newRCSfilename,RCSfilename)<0) { 267: error("Can't rewrite %s; saved in: %s", 268: RCSfilename, newRCSfilename); 269: newRCSfilename[0]='\0'; /* avoid deletion*/ 270: catchints(); 271: break; 272: } 273: newRCSfilename[0]='\0'; /* avoid re-deletion by cleanup()*/ 274: if (chmod(RCSfilename,RCSstat.st_mode & ~0222)<0) 275: warn("Can't preserve mode of %s",RCSfilename); 276: catchints(); 277: } 278: 279: # ifdef SNOOPFILE 280: logcommand("co",targetdelta,gendeltas,caller); 281: # endif 282: 283: if (join) { 284: rmsema(); /* kill semaphore file so other co's can proceed */ 285: if (!buildjoin(neworkfilename,tostdout)) continue; 286: } 287: if (!tostdout) { 288: if (link(neworkfilename,workfilename) <0) { 289: error("Can't create %s; see %s",workfilename,neworkfilename); 290: neworkfilename[0]= '\0'; /*avoid deletion*/ 291: continue; 292: } 293: } 294: } 295: if (!tostdout) 296: if (chmod(workfilename, WORKMODE(RCSstat.st_mode))<0) 297: warn("Can't adjust mode of %s",workfilename); 298: 299: if (!tostdout) diagnose("done"); 300: } while (cleanup(), 301: ++argv, --argc >=1); 302: 303: exit(nerror!=0); 304: 305: } /* end of main (co) */ 306: 307: 308: /***************************************************************** 309: * The following routines are auxiliary routines 310: *****************************************************************/ 311: 312: int rmoldfile(ofile) 313: char * ofile; 314: /* Function: unlinks ofile, if it exists, under the following conditions: 315: * If the file is read-only, file is unlinked. 316: * Otherwise (file writable): 317: * if !quietmode asks the user whether to really delete it (default: fail); 318: * otherwise failure. 319: * Returns false on failure to unlink, true otherwise. 320: */ 321: { 322: int response, c; /* holds user response to queries */ 323: struct stat buf; 324: 325: if (stat (ofile, &buf) < 0) /* File doesn't exist */ 326: return (true); /* No problem */ 327: 328: if (buf.st_mode & 0222) { /* File is writable */ 329: if (!quietflag) { 330: fprintf(stderr,"writable %s exists; overwrite? [ny](n): ",ofile); 331: /* must be stderr in case of IO redirect */ 332: c=response=getchar(); 333: while (!(c==EOF || c=='\n')) c=getchar(); /*skip rest*/ 334: if (c == EOF) 335: clearerr(stdin); 336: if (!(response=='y'||response=='Y')) { 337: warn("checkout aborted."); 338: return false; 339: } 340: } else { 341: error("writable %s exists; checkout aborted.",ofile); 342: return false; 343: } 344: } 345: /* now unlink: either not writable, or permission given */ 346: if (unlink(ofile) != 0) { /* Remove failed */ 347: error("Can't unlink %s",ofile); 348: return false; 349: } 350: return true; 351: } 352: 353: 354: creatempty(file) 355: char * file; 356: /* Function: creates an empty file named file. 357: * Removes an existing file with the same name with rmoldfile(). 358: */ 359: { 360: int fdesc; /* file descriptor */ 361: 362: if (!rmoldfile(file)) return false; 363: fdesc=creat(file,0666); 364: if (fdesc < 0) { 365: faterror("Cannot create %s",file); 366: return false; 367: } else { 368: close(fdesc); /* empty file */ 369: return true; 370: } 371: } 372: 373: 374: 375: /***************************************************************** 376: * The rest of the routines are for handling joins 377: *****************************************************************/ 378: 379: char * getrev(sp, tp, buffsize) 380: register char * sp, *tp; int buffsize; 381: /* Function: copies a symbolic revision number from sp to tp, 382: * appends a '\0', and returns a pointer to the character following 383: * the revision number; returns nil if the revision number is more than 384: * buffsize characters long. 385: * The revision number is terminated by space, tab, comma, colon, 386: * semicolon, newline, or '\0'. 387: * used for parsing the -j option. 388: */ 389: { 390: register char c; 391: register int length; 392: 393: length = 0; 394: while (((c= *sp)!=' ')&&(c!='\t')&&(c!='\n')&&(c!=':')&&(c!=',') 395: &&(c!=';')&&(c!='\0')) { 396: if (length>=buffsize) return false; 397: *tp++= *sp++; 398: length++; 399: } 400: *tp= '\0'; 401: return sp; 402: } 403: 404: 405: 406: int preparejoin() 407: /* Function: Parses a join list pointed to by join and places pointers to the 408: * revision numbers into joinlist. 409: */ 410: { 411: struct hshentry * (* joindeltas)[]; 412: struct hshentry * tmpdelta; 413: register char * j; 414: char symbolrev[revlength],numrev[revlength]; 415: 416: joindeltas = (struct hshentry * (*)[])malloc(hshsize*sizeof(struct hshentry *)); 417: j=join; 418: lastjoin= -1; 419: for (;;) { 420: while ((*j==' ')||(*j=='\t')||(*j==',')) j++; 421: if (*j=='\0') break; 422: if (lastjoin>=joinlength-2) { 423: error("too many joins"); 424: return(false); 425: } 426: if(!(j=getrev(j,symbolrev,revlength))) return false; 427: if (!expandsym(symbolrev,numrev)) return false; 428: tmpdelta=genrevs(numrev,nil,nil,nil,joindeltas); 429: if (tmpdelta==nil) 430: return false; 431: else joinlist[++lastjoin]=tmpdelta->num; 432: while ((*j==' ') || (*j=='\t')) j++; 433: if (*j == ':') { 434: j++; 435: while((*j==' ') || (*j=='\t')) j++; 436: if (*j!='\0') { 437: if(!(j=getrev(j,symbolrev,revlength))) return false; 438: if (!expandsym(symbolrev,numrev)) return false; 439: tmpdelta=genrevs(numrev,nil,nil,nil,joindeltas); 440: if (tmpdelta==nil) 441: return false; 442: else joinlist[++lastjoin]=tmpdelta->num; 443: } else { 444: error("join pair incomplete"); 445: return false; 446: } 447: } else { 448: if (lastjoin==0) { /* first pair */ 449: /* common ancestor missing */ 450: joinlist[1]=joinlist[0]; 451: lastjoin=1; 452: /*derive common ancestor*/ 453: joinlist[0]=malloc(revlength); 454: if (!getancestor(targetdelta->num,joinlist[1],joinlist[0])) 455: return false; 456: } else { 457: error("join pair incomplete"); 458: return false; 459: } 460: } 461: } 462: if (lastjoin<1) { 463: error("empty join"); 464: return false; 465: } else return true; 466: } 467: 468: 469: 470: buildjoin(initialfile, tostdout) 471: char * initialfile; int tostdout; 472: /* Function: merge pairs of elements in joinlist into initialfile 473: * If tostdout==true, copy result to stdout. 474: * All unlinking of initialfile, rev2, and rev3 should be done by cleanup(). 475: */ 476: { char command[NCPPN+80]; 477: char subs[revlength]; 478: char * rev2, * rev3; 479: int i; 480: 481: rev2=mktempfile("/tmp/",JOINFIL2); 482: rev3=mktempfile("/tmp/",JOINFIL3); 483: 484: i=0; 485: while (i<lastjoin) { 486: /*prepare marker for merge*/ 487: if (i==0) 488: strcpy(subs,targetdelta->num); 489: else sprintf(subs, "merge%d",i/2); 490: diagnose("revision %s",joinlist[i]); 491: sprintf(command,"%s/co -p%s -q %s > %s\n",TARGETDIR,joinlist[i],RCSfilename,rev2); 492: if (system(command)) { 493: nerror++;return false; 494: } 495: diagnose("revision %s",joinlist[i+1]); 496: sprintf(command,"%s/co -p%s -q %s > %s\n",TARGETDIR,joinlist[i+1],RCSfilename,rev3); 497: if (system(command)) { 498: nerror++; return false; 499: } 500: diagnose("merging..."); 501: sprintf(command,"%s %s%s %s %s %s %s\n", MERGE, 502: ((i+2)>=lastjoin && tostdout)?"-p ":"", 503: initialfile,rev2,rev3,subs,joinlist[i+1]); 504: if (system(command)) { 505: nerror++; return false; 506: } 507: i=i+2; 508: } 509: return true; 510: }