[comp.os.minix] Repost of SVC, the Shell Version Control system for Minix

housel@en.ecn.purdue.edu (Peter S. Housel) (01/17/89)

	For those who missed it last time around, or who rejected it
because it required too many fixes to the stdio libraries, here is SVC
again. It has gotten me through 1.1->1.2->1.3*->1.3c conversions
during the past year without any problems whatsoever.

-Peter S. Housel-	housel@en.ecn.purdue.edu	...!pur-ee!housel

echo 'x - Makefile'
sed 's/^X//' <<'**-Makefile-EOF-**' >Makefile
X#
X# makefile for SVC
X#
X
XCFLAGS = -F -T/usr/tmp
X
Xall:	ci co
X
Xci:	ci.c
X	cc $(CFLAGS) -o ci ci.c
X	chmem =8000 ci
X
Xco:	co.c
X	cc $(CFLAGS) -o co co.c
X	chmem =8000 co
X
Xsvclog:	svclog.sh
X	cp svclog.sh svclog
X	chmod 755 svclog
**-Makefile-EOF-**
echo 'x - README'
sed 's/^X//' <<'**-README-EOF-**' >README
XThis file explains the purposes of the files in the SVC package.
X
X	SVC is the Shell Version Control system, and is inspired by Walter
XTichy's Revision Control System, or RCS. The intention of SVC is to implement
Xthe subset of RCS that is commonly used for everyday maintainance. (That is,
Xmost of the part I know how to use.)
X
X	SVC requires Minix 1.3d or Minix 1.2 with the 1.3 patches for
Xgetc()/fgetc(), fseek(), scanf(), and ctime(). I suspect some of these
Xbugs have not been fixed in the ST versions of these files, but I may
Xbe wrong.
X
X	To use SVC on a non-Minix system, you will need to find a copy
Xof "fix", or of Larry Wall's "patch" program. To use patch, you need to
Xcompile ci.c with "-DPATCH".
X
XMAN PAGES
X
XCommand:	ci - check in an SVC revision
XSyntax:		ci [-l] [-u] file
XFlags:		-l	After checking in, check back out again and lock
X		-u	After checking in, do not delete the file
XExamples:	ci -u Makefile		check Makefile back in
X		ci -l newfile		create SVC file for newfile, relock
X
X   Ci checks the specified file into its SVC archive. If an SVC archive file
Xdid not exist, one is created with the name "file,S". (If a directory named
X"SVC" is present in the same directory as "file", the archive will be placed
Xthere instead.) 
X   A log message will be prompted for, and read from standard input; this
Xmay be several lines, terminated with "." on a line by itself or EOT. After
Xthe file has been checked in, the default action is to remove the source
Xfile. If "-u" is specified, the file will not be unlinked, but all write
Xpermissions will be removed. If "-l" is specified, a lock is placed on
Xthe SVC archive, and the file will remain writable by the owner. SVC archive
Xfiles are always read-only.
X   Version numbers start at "1" and are incremented by one for each revision
Xchecked in.
X
X
XCommand:	co - check out an SVC revision
XSyntax:		co [-l] [-r rev] file
XFlags:		-l	Place a lock on the SVC archive after checkout
X		-r rev	Check out revision "rev" instead most recent revision
XExamples:	co -l Makefile		check out and lock Makefile
X		co -r 4 foo		check out version 4 of foo
X
X   Co checks the specified file out of its SVC archive. If the archive
X".../file,S" does not exist, it is searched for in ".../SVC/file,S".
X   The specified revision will be checked out and placed in "file"; by
Xdefault it will be non-writable. If "-l" is specified, a lock will be
Xplaced on the archive and the file will be writable.
X
X
XCommand:	svclog - print log of SVC revisions
XSyntax:		svclog file
XFlags:		none
XExamples:	svclog foo		print out revision history for foo
X		svclog Makefile,S	print out revision history for Makefile
X
X   Svclog prints out the revision history of the specified file. This
Xinformation includes the revision numbers, dates, and log entries for each
Xof the revisions, and indicates whether or not a lock on this file exists.
XFor simplicity, the output format is rather raw.
X
X
XINSTALLATION
X
X	Installation of SVC should be rather straightforward. The "ci" and
X"co" binaries should be compiled using the supplied makefile, and the "ci",
X"co", and "svclog" files should be put in your favorite default path
Xdirectory (such as /usr/bin). It will also be helpful to increase the memory
Xallocation for /usr/bin/diff and /usr/bin/fix (using chmem) to as high as
Xpossible.
X
XBACKGROUND
X
X	SVC stands for Shell Version Control, and is so named for two
Xreasons.  The first is that it was originally implemented using Bourne
Xshell scripts.  (These are included as "ci.sh" and "co.sh"; they serve
Xno useful purpose in the current version but are presented for your
Xamusement and/or enlightenment).  This implementation gave pretty good
Xperformance on an 8-MIP machine running BSD 4.3-Tahoe, but on my AT
Xcompatible it was just too slow.  Also, the scripts make heavy use of
X"sed," which didn't quite work on Minix at the time.  For this reason
X"ci" and "co" were rewritten in C.
X
X	The other reason for the name is that the SVC archives are in the
Xform of shell scripts, in the manner of shar-files. The original reason for
Xthis was so that the archives could literally extract themselves - by
Xexecuting them with "sh". This format is reasonably easy to figure out by
Xreading it, and not too difficult for the program to understand. In a pinch,
Xyou can do without "co".
X
X	However, there is a problem with this approach. Shell input using
X"<<" (a here document in Minix sh source file terminology) is processed
Xby the shell, including the expansion of environment variables and such.
XThis means that files which contain shell-variable syntax (i.e. makefiles
Xand other shell scripts) may be garbled on extraction. This was another
Xreason why the programs were translated into C. (Here documents are also
Xvery slow...)
X
X	ci.c and co.c were designed to be somewhat bullet-proof, and have
Xworked very well in the time I have been using them.
X
XSUGGESTED USE
X
X	The major use for SVC on my system is keeping track of system
Xsources. There are "SVC" subdirectories all throughout /usr/src, and
Xwhenever a fix is posted to the net I can check out a file, apply the
Xpatches, and check it back in. The advantage of this is that if I want
Xto refer to an earlier version of the file, or want to apply patches to a
Xfile I have changed since the last official version, an older revision can
Xbe checked out (going back to Minix 1.1 in most cases).
X
X	I suggest experimenting with the system to get a feel for how it
Xworks if you are not already familiar with RCS. Do not use it on any
Xfiles for which you do not have a backup copy until you are confident
Xthat you are doing things correctly.
X
X-Peter S. Housel- 	housel@ecn.purdue.edu	...!pur-ee!housel
**-README-EOF-**
echo 'x - ci.c'
sed 's/^X//' <<'**-ci.c-EOF-**' >ci.c
X/* file: ci.c
X** author: Peter S. Housel 12/17/87
X*/
X
X#include <stdio.h>
X#include <string.h>
X#include <sys/stat.h>
X#include <pwd.h>
X#include <signal.h>
X
X#define SUFFIX		",S"		/* svc indicator */
X#define SVCDIR		"SVC"		/* svc postfix indicator */
X
X#define LINELEN		256		/* maximum line length */
X
X#ifndef PATCH
X#define FIX		"fix $1 Fix.$1 > New.$1; mv New.$1 $1\n"
X#else
X#define FIX		"patch -n -s $1 < Fix.$1; rm -f $1.orig\n"
X#endif !PATCH
X
X#ifdef MAXPATHLEN
X#define PATHLEN MAXPATHLEN
X#else
X#define PATHLEN 128			/* buffer length for filenames */
X#endif
X
Xint unlocked = 0;			/* leave unlocked after checkin */
Xint relock = 0;				/* lock next revision after checkin */
Xchar file[PATHLEN];			/* file to be checked in */
Xchar svc[PATHLEN];			/* filename for svc file */
Xchar newsvc[PATHLEN];			/* new copy of SVC file */
Xchar line[LINELEN];			/* temporary line buffer */
Xchar *p;				/* scratch character pointer */
X
XFILE *svcfp;				/* svc file */
XFILE *origfp, *newfp;			/* "orig" and "new" temp files */
XFILE *srcfp;				/* source file */
Xint rev;				/* new revision number */
Xint status;				/* wait() buffer */
Xstruct stat stb1, stb2;			/* stat buffers for size compare */
Xchar original[] = "/tmp/cioXXXXXX";	/* previous revision */
Xchar diffout[] =  "/tmp/cidXXXXXX";	/* diffs */
X
Xextern FILE *fopen();
Xextern char *mktemp(), *fgets(), *rindex(), *index();
Xextern char *ctime();
Xextern struct passwd *getpwuid();
Xextern long ftell();
X
Xchar *whoami();
Xint onintr();
X
Xmain(argc, argv)
Xint argc; char **argv;
X{
X#ifdef perprintf
X char errbuf[BUFSIZ];
X setbuf(stderr, errbuf);
X perprintf(stderr);
X#endif
X
X while(++argv, --argc)
X      {
X       if('-' == (*argv)[0])
X         {
X	  if('u' == (*argv)[1])
X	     ++unlocked;
X	  else if('l' == (*argv)[1])
X	     ++relock;
X	  else
X	    {fprintf(stderr, "ci: illegal option -%c\n", (*argv)[1]);
X	     exit(1);
X	    }
X	 }
X       else
X	  break;
X      }
X
X if(1 != argc)
X   {
X    fprintf(stderr, "ci: bad number of files arguments\n");
X    exit(1);
X   }
X
X fname(*argv, file);
X svcname(file, svc);
X
X fprintf(stderr, "%s -> %s\n", file, svc);
X
X signal(SIGHUP, onintr);
X signal(SIGINT, onintr);
X signal(SIGTERM, onintr);
X 
X#ifndef BSD
X if(NULL == (p = rindex(file, '/')))
X    p = file;
X else
X    ++p;
X
X if(strlen(p) > 13)
X   {
X    fprintf(stderr, "ci: filename %s is too long\n");
X    exit(1);
X   }
X#endif !BSD
X
X strcpy(newsvc, svc);
X *(rindex(newsvc, ',')) = ';';		/* temporary file will be "file;S" */
X
X if(NULL == (newfp = fopen(newsvc, "w")))
X   {
X    perror("ci: can't create SVC temporary");
X    exit(1);
X   }
X
X (void) mktemp(original);
X (void) mktemp(diffout);
X
X if(NULL != (svcfp = fopen(svc, "r")))  /* does svc-file exist? */
X   {
X    fgets(line, LINELEN, svcfp);
X    if(1 != sscanf(line, "# %d", &rev))
X      {
X       fprintf(stderr, "ci: %s: illegal SVC file header\n", svc);
X       exit(1);
X      }
X    ++rev;
X
X    if(!lockcheck(svcfp, rev))
X      {
X       fprintf(stderr, "Revision %d not locked\n", rev);
X       clean();
X       exit(1);
X      }    
X
X    if(NULL == (origfp = fopen(original, "w")))
X      {
X       fprintf(stderr, "ci: can't create %s", original);
X       perror("");
X      }
X    fgets(line, LINELEN, svcfp);   /* skip "cat <<***MAIN-eof***" line */
X
X    while(NULL != fgets(line, LINELEN, svcfp)
X	  && strcmp(line, "***MAIN-eof***\n"))
X         {
X	  fputs(line, origfp);
X	  if(ferror(origfp))
X	    {
X	     perror("ci: origfile");
X	     exit(1);
X	    }
X	 }
X    fclose(origfp);
X    
X    rundiff();
X
X    if(0 != stat(original, &stb1) || 0 != stat(diffout, &stb2))
X      {
X       perror("ci: can't stat original or diffout");
X       clean();
X       exit(1);
X      }
X   }
X else
X   { /* no - create one */
X    rev = 1;
X   }
X
X fprintf(newfp, "# %d\n", rev);
X fprintf(newfp, "cat <<***MAIN-eof*** >$1\n");
X if(NULL == (srcfp = fopen(file, "r")))
X   {
X    perror("ci: can't read source file");
X    clean();
X    exit(1);
X   }
X while(NULL != fgets(line, LINELEN, srcfp))
X       fputs(line, newfp);
X fclose(srcfp);
X fputs("***MAIN-eof***\n", newfp);
X 
X if(rev > 1)
X   {
X    fprintf(newfp, "if test $2 -ge %d ; then rm -f Fix.$1 ; exit 0 ; fi ; cat <<***%d-eof*** >Fix.$1\n", rev, rev);
X    p = (stb1.st_size <= stb2.st_size) ? original : diffout;
X    if(NULL == (origfp = fopen(p, "r")))
X      {
X       perror("can't open diff output file");
X       clean();
X       exit(1);
X      }
X    while(NULL != fgets(line, LINELEN, origfp))
X          fputs(line, newfp);
X    fclose(origfp);
X    fprintf(newfp, "***%d-eof***\n", rev);
X    fputs((original == p) ? "mv Fix.$1 $1\n" : FIX, newfp);
X    logmsg(newfp);
X    while(NULL != fgets(line, LINELEN, svcfp) && strncmp(line, "#***SVCLOCK***", 14)) 
X	  fputs(line, newfp);
X   }
X else
X   {
X    logmsg(newfp);
X    fputs("rm -f Fix.$1\n", newfp);
X   }
X 
X if(relock)
X   {fprintf(stderr, "(relocking into revision %d)\n", rev+1);
X    fprintf(newfp, "#***SVCLOCK*** %s %d\n", whoami(), rev+1);
X   }
X
X signal(SIGHUP, SIG_IGN);	/* disable during critical section */
X signal(SIGINT, SIG_IGN);
X
X if(ferror(newfp) || fclose(newfp) || ((rev > 1) && unlink(svc))
X				   || link(newsvc, svc))
X   {
X    fprintf(stderr, "SVC file write/link error - Checkin aborted\n");
X    clean();
X    exit(1);
X   }
X else
X    fprintf(stderr, "Checkin complete.\n");
X
X if(stat(svc, &stb1) < 0 || chmod(svc, stb1.st_mode & 0555) < 0)
X    perror("ci: can't chmod SVC file");
X
X if(unlocked)
X   {if(stat(file, &stb1) < 0 || chmod(file, stb1.st_mode & 0555) < 0)
X       perror("ci: can't chmod source file");
X   }
X else if(relock)
X   {if(stat(file, &stb1) < 0 || chmod(file, stb1.st_mode | 0200) < 0)
X       perror("ci: can't chmod source file");
X   }
X else
X    unlink(file);
X
X clean();
X exit(0);
X}
X
Xrundiff()
X{		/* do "diff file original > diffout" */
X int fd;				/* redirected output file */
X
X switch(fork())
X       {
X	case -1:perror("ci: fork");	/* error */
X		clean();
X		exit(1);
X
X	case 0:				/* child */
X		if((fd = creat(diffout, 0600)) < 0 || -1 == dup2(fd, 1))
X		  {
X		   perror("ci: diffout");
X		   clean();
X		   exit(1);
X		  }
X		close(fd);
X		execl("/usr/bin/diff", "diff",  file, original, 0);
X		perror("ci: exec diff failed");
X		exit(1);
X
X	default:break;			/* parent */
X       }
X wait(&status);
X if(0 != status && 1<<8 != status)
X   {
X    fprintf(stderr, "ci: bad return status (0x%x) from diff\n", status);
X    clean();
X    exit(1);
X   }
X}
X
Xlogmsg(fp)
XFILE *fp;
X{
X long now;
X
X time(&now);
X fprintf(stderr, "Enter log message for revision %d (end with ^D or '.'):\n", rev);
X fprintf(fp, "#***SVC*** revision %d %s %s", rev, file, ctime(&now));
X while(NULL != gets(line) && strcmp(line, "."))
X       fprintf(fp, "#***SVC*** %s\n", line);
X}
X
Xfname(src, dst)
Xchar *src, *dst;
X{
X char *p;
X strcpy(dst, src);
X p = &dst[strlen(src) - strlen(SUFFIX)];
X if(!strcmp(p, SUFFIX))
X    *p = '\0';
X}
X
Xsvcname(src, dst)
Xchar *src, *dst;
X{
X extern char *rindex();
X char *p;
X 
X strcpy(dst, src);
X strcat(dst, SUFFIX);
X
X if(0 != access(dst, 4))
X   {
X    char dirname[PATHLEN];
X    if(NULL != (p = rindex(src, '/')))
X       strncpy(dirname, src, p - src + 1);
X    else
X       dirname[0] = '\0';
X    strcat(dirname, SVCDIR);
X
X    if(0 == access(dirname, 1))
X      {
X       strcpy(dst, dirname);
X       if(NULL == p)
X	 {strcat(dst, "/");
X          strcat(dst, src);
X         }
X       else
X          strcat(dst, p);
X       strcat(dst, SUFFIX);
X      }
X   } 
X}
X
Xlockcheck(fp, rev)
XFILE *fp;
Xint rev;
X{
X char lock[40], check[40];
X long pos;
X int ret;
X
X sprintf(lock, "#***SVCLOCK*** %s %d\n", whoami(), rev);
X  
X pos = ftell(fp);
X fseek(fp, -((long)strlen(lock)), 2);
X fgets(check, 40, fp);
X ret = (0 == strcmp(lock, check));
X fseek(fp, pos, 0);
X
X return ret;
X}
X
Xonintr()
X{
X fprintf(stderr, "Interrupt - Aborting checkin, cleaning up\n");
X clean();
X exit(1);
X}
X
Xclean()
X{
X if(strlen(original))		/* if only more programs made this check! */
X    unlink(original);
X if(strlen(diffout))
X    unlink(diffout);
X if(strlen(newsvc))
X    unlink(newsvc);
X}
X
Xchar *whoami()
X{
X struct passwd *pw;
X
X if(NULL != (pw = getpwuid(getuid())))
X    return pw->pw_name;
X else
X    return "nobody";
X}
**-ci.c-EOF-**
echo 'x - ci.sh'
sed 's/^X//' <<'**-ci.sh-EOF-**' >ci.sh
X#!/bin/sh
X# SVC - the Shell Version Control system
X# author: Peter S. Housel 10/24/87
X#
Xremove="rm -f"
Xlock="false"
X#
Xwhile :; do
X   case $1 in
X-u ) remove="chmod a-w"
X     shift;;
X-l ) remove="chmod u+w"
X     lock=":"
X     shift;;
X*  ) break
X   esac
Xdone
X#
Xfile=`basename $1 ,S`
Xsvc=$file,S
Xif test \( ! -r $svc \) -a -d "SVC" ; then svc=SVC/$svc ; fi
Xecho '***MAIN-eof***' >/tmp/cir$$
Xif test -r $svc; then
X  rev=`sed -n '1s/#.* //p' $svc`; rev=`expr $rev + 1`
X  if test "#***SVCLOCK*** $USER $rev" != "`tail -1 $svc`" ; then
X    echo "Revision $rev not locked by $USER"
X    exit 1
X  fi
X  sed -e '3,/^\*\*\*MAIN-eof\*\*\*/!d' \
X      -e '/^\*\*\*MAIN-eof\*\*\*/d' $svc >/tmp/cio$$
X  sed -e '/^\*\*\*MAIN-eof\*\*\*/,$!d' \
X      -e '/^#\*\*\*SVCLOCK\*\*\*/d' \
X      -e '/^\*\*\*MAIN-eof\*\*\*/d' $svc >/tmp/cid$$
X  echo 'if test $2 -ge $rev ; then rm -f /tmp/$$ ; exit 0 ; fi ; cat <<***$rev-eof*** >/tmp/$$' >>/tmp/cir$$
X  diff $file /tmp/cio$$ >>/tmp/cir$$
X  echo "***$rev-eof***" >>/tmp/cir$$
X  echo 'fix $1 /tmp/$$ > New.$1; mv New.$1 $1' >>/tmp/cir$$
Xelse
X  rev=1
X  echo 'rm -f /tmp/$$' > /tmp/cid$$
Xfi
Xecho "Enter log message for revision $rev (end with ^D or '.'):"
Xecho "#***SVC*** revision $rev $file "`date` $USER >>/tmp/cir$$
Xwhile read logline; do
X  if test x"$logline" = x. ; then break; fi
X  echo "#***SVC*** $logline" >>/tmp/cir$$
Xdone
Xecho "# " $rev >$svc+
Xecho 'cat <<***MAIN-eof*** >$1' >> $svc+
Xif cat $file /tmp/cir$$ /tmp/cid$$ >> $svc+ ; then
X  rm -f /tmp/ci?$$
X  echo "Checkin complete."
X  mv $svc+ $svc
X  if $lock; then
X    rev=`expr $rev + 1`
X    echo "#***SVCLOCK*** $USER $rev" >>$svc
X    echo "(Locking into revision $rev.)"
X  fi
X  chmod a-w $svc
X  $remove $file
Xelse
X  echo "Checkout ABORTED!!!"
X  exit 1
Xfi
**-ci.sh-EOF-**
echo 'x - co.c'
sed 's/^X//' <<'**-co.c-EOF-**' >co.c
X/* file: co.c
X** author: Peter S. Housel 12/24/87
X*/
X
X#include <stdio.h>
X#include <string.h>
X#include <sys/stat.h>
X#include <pwd.h>
X
X#define SUFFIX		",S"		/* svc indicator */
X#define SVCDIR		"SVC"		/* svc postfix indicator */
X
X#define LINELEN		256		/* maximum line length */
X
X#ifdef MAXPATHLEN
X#define PATHLEN MAXPATHLEN
X#else
X#define PATHLEN 128			/* buffer length for filenames */
X#endif
X
Xchar file[PATHLEN];			/* file to be checked in */
Xchar svc[PATHLEN];			/* filename for svc file */
Xchar newsvc[PATHLEN];			/* new copy of SVC file */
Xchar line[LINELEN];			/* temporary line buffer */
Xchar *p;				/* scratch character pointer */
X
XFILE *svcfp;				/* svc file */
Xint rev;				/* old revision number */
Xint lastrev, lockrev;			/* latest file revision, lock into */
Xint status;				/* wait() buffer */
Xint lock;				/* lock the SVC file */
Xstruct stat stb;			/* stat() buffer */
Xchar *base;				/* basename of file */
X
Xchar difftemp[PATHLEN];			/* extract() fix/patch input */
X
Xextern FILE *fopen();
Xextern char *mktemp(), *fgets(), *rindex(), *index();
Xextern struct passwd *getpwuid();
X
Xchar *whoami(), *basename();
X
Xmain(argc, argv)
Xint argc; char **argv;
X{
X#ifdef perprintf
X char errbuf[BUFSIZ];
X setbuf(stderr, errbuf);
X perprintf(stderr);
X#endif
X
X while(++argv, --argc)
X      {
X       if('-' == (*argv)[0])
X         {
X	  if('r' == (*argv)[1])
X	    {--argc;
X	     rev = atoi(*++argv);
X	     if(rev < 1)
X	       {fprintf(stderr, "Illegal revision number\n");
X		exit(1);
X	       }
X	    }
X	  else if('l' == (*argv)[1])
X	     ++lock;
X	  else
X	    {fprintf(stderr, "co: illegal option -%c\n", (*argv)[1]);
X	     exit(1);
X	    }
X	 }
X       else
X	  break;
X      }
X
X if(1 != argc)
X   {
X    fprintf(stderr, "co: bad number of files arguments\n");
X    exit(1);
X   }
X
X fname(*argv, file);
X svcname(file, svc);
X 
X fprintf(stderr, "%s -> %s\n", svc, base = basename(file));
X
X if(NULL == (svcfp = fopen(svc, "r")))
X   {
X    perror("co: can't read SVC file");
X    exit(1);
X   }
X
X if(1 != fscanf(svcfp, "# %d", &lastrev) || lastrev < 1)
X   {
X    fprintf(stderr, "co: illegal SVC file format\n");
X    exit(1);
X   }
X
X fclose(svcfp);
X
X if(stat(base, &stb) >= 0 && (stb.st_mode & 0222))
X   {
X    fprintf(stderr, "Writable %s exists - overwrite (n/y)? ", base);
X    if(!getyn())
X      {
X       fprintf(stderr, "Checkout aborted\n");
X       exit(1);
X      }
X   }
X
X if(strlen(base))
X    unlink(base);
X
X if(0 == rev)
X    rev = lastrev;
X
X fprintf(stderr, "Checking out revision %d", rev);
X
X extract(svc, base, rev);
X
X if(lock)
X   {
X    lockrev = lastrev + 1;
X    fprintf(stderr, "; Locking into revision %d\n", lockrev);
X    if(stat(svc, &stb) < 0 || chmod(svc, stb.st_mode | 0200) < 0)
X       perror("co: can't chmod SVC file");
X    
X    if(stat(base, &stb) < 0 || chmod(base, stb.st_mode | 0200) < 0)
X       perror("co: can't chmod source file");
X
X    if(NULL == (svcfp = fopen(svc, "a"))
X       || (fprintf(svcfp, "#***SVCLOCK*** %s %d\n", whoami(), lockrev), ferror(svcfp)))
X      {
X       fprintf(stderr, "co: can't lock %s\n", svc);
X       exit(1);
X      }
X    if(stat(svc, &stb) < 0 || chmod(svc, stb.st_mode & 0555))
X       perror("co: can't chmod SVC file");
X   }
X else
X   {
X    putchar('\n');
X    if(stat(base, &stb) < 0 || chmod(base, stb.st_mode & 0555))
X       perror("co: can't chmod source file");
X   }
X
X exit(0);
X}
X
X
Xfname(src, dst)
Xchar *src, *dst;
X{
X char *p;
X strcpy(dst, src);
X p = &dst[strlen(src) - strlen(SUFFIX)];
X if(!strcmp(p, SUFFIX))
X    *p = '\0';
X}
X
Xsvcname(src, dst)
Xchar *src, *dst;
X{
X extern char *rindex();
X char *p;
X 
X strcpy(dst, src);
X strcat(dst, SUFFIX);
X
X if(0 != access(dst, 4))
X   {
X    char dirname[PATHLEN];
X    if(NULL != (p = rindex(src, '/')))
X       strncpy(dirname, src, p - src + 1);
X    else
X       dirname[0] = '\0';
X    strcat(dirname, SVCDIR);
X
X    if(0 == access(dirname, 1))
X      {
X       strcpy(dst, dirname);
X       if(NULL == p)
X	 {strcat(dst, "/");
X          strcat(dst, src);
X         }
X       else
X          strcat(dst, p);
X       strcat(dst, SUFFIX);
X      }
X   } 
X}
X
Xextract(script, out, rev)
Xchar *script, *out; int rev;
X{
X FILE *outfp;
X int testrev;
X char buf[80];
X
X sprintf(difftemp, "Fix.%s", out);
X
X svcfp = fopen(script, "r");
X fgets(line, LINELEN, svcfp);		/* skip '# rev' line */
X fgets(line, LINELEN, svcfp);		/* skip 'cat <***MAIN-eof***' line */
X
X if(NULL == (outfp = fopen(out, "w")))
X   {
X    perror("co: can't create output file");
X    return;
X   }
X
X while(NULL != fgets(line, LINELEN, svcfp) && strcmp(line, "***MAIN-eof***\n"))
X       fputs(line, outfp);
X
X fclose(outfp);
X
X while(NULL != fgets(line, LINELEN, svcfp))
X      {
X       if(!strncmp(line, "if ", 3))
X         {
X	  sscanf(line, "if test $2 -ge %d", &testrev);
X	  if(rev >= testrev)
X	    {
X	     unlink(difftemp);
X	     return;
X	    }
X          if(NULL ==  (outfp = fopen(difftemp, "w")))
X	    {
X	     perror("co: can't create output file");
X	     return;
X	    }
X	  sprintf(buf, "***%d-eof***\n", testrev);
X	  while(NULL != fgets(line, LINELEN, svcfp) && strcmp(line, buf))
X 		fputs(line, outfp);
X	  fclose(outfp);
X	 }
X       else if(!strncmp(line, "mv ", 3))
X	 {
X	  sprintf(buf, "mv Fix.%s %s", out, out);
X	  system(buf);
X	 }
X       else if(!strncmp(line, "fix ", 4))
X	 {
X	  sprintf(buf, "fix %s Fix.%s > New.%s; mv New.%s %s", out, out, out, out, out);
X	  system(buf);
X	 }
X       else if(!strncmp(line, "patch ", 6))
X	 {
X	  sprintf(buf, "patch -n -s %s < Fix.%s; rm -f %s.orig", out, out, out);
X	  system(buf);
X	 }
X       else
X	 {	/* ignore */
X	 }
X      }
X
X unlink(difftemp);
X return;	
X}
X
Xchar *basename(name)
Xchar *name;
X{
X char *p;
X
X if(NULL == (p = rindex(name, '/')))
X    return name;
X else
X    return p + 1;
X}
X
Xchar *whoami()
X{
X struct passwd *pw;
X
X if(NULL != (pw = getpwuid(getuid())))
X    return pw->pw_name;
X else
X    return "nobody";
X}
X
Xint getyn()
X{
X char ans[10];
X
X return (NULL != fgets(ans, 10, stdin)) && ('y' == ans[0] || 'Y' == ans[0]);
X}
**-co.c-EOF-**
echo 'x - co.sh'
sed 's/^X//' <<'**-co.sh-EOF-**' >co.sh
X#!/bin/sh
X# SVC - the Shell Version Control system
X# author: Peter S. Housel 10/24/87
X#
Xlock="false"
X#
Xwhile :; do
X   case $1 in
X-l ) lock=":"
X     shift;;
X-r ) rev=$2;
X     shift;
X     shift;;
X*  ) break
X   esac
Xdone
X#
Xfile=`basename $1 ,S`
Xsvc=$file,S
Xif test \( ! -r $svc \) -a -d "SVC" ; then svc=SVC/$svc ; fi
Xif test ! -r $svc ; then
X  echo "can't find $file,S or RCS/$file,S"
X  exit 1
Xfi
Xlastrev=`sed -n '1s/#.* //p' $svc`
Xif test -w $file; then
X  echo -n "Writable $file exists; Continue (n/y)? " ; read yn
X  if test x$yn != xy ; then echo "Checkout aborted" ; exit 1 ; fi
Xelif test -f $file; then
X  chmod u+w $file
Xfi
Xecho -n "Checking out revision " ${rev=$lastrev}
Xsh $svc $file $rev
Xif $lock; then
X  lockrev=`expr $lastrev + 1`
X  echo "; Locking into revision $lockrev"
X  chmod u+w $file
X  chmod u+w $svc; echo "#***SVCLOCK*** $USER $lockrev" >>$svc; chmod u-w $svc
Xelse
X  echo
X  chmod a-w $file
Xfi
Xexit 0
X
**-co.sh-EOF-**
echo 'x - svclog.sh'
sed 's/^X//' <<'**-svclog.sh-EOF-**' >svclog.sh
X#!/bin/sh
X#
Xsvc=`basename $1 ,S`,S
Xif test \( ! -r $svc \) -a -d "SVC" ; then svc=SVC/$svc ; fi
Xgrep '^#\*\*\*SVC' $svc
X
**-svclog.sh-EOF-**