|
|
1.1 root 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: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.