v23i075: Frontend to RCS ci/co

Rich Salz rsalz at bbn.com
Thu Dec 6 04:21:24 AEST 1990


Submitted-by: Jason Winters <grinch!jason>
Posting-number: Volume 23, Issue 75
Archive-name: cio

[  Save this; I hope to be posting RCS shortly...  --r$  ]

#! /bin/sh
# This is a shell archive.  Remove anything before this line, then feed it
# into a shell via "sh file" or similar.  To overwrite existing files,
# type "sh file -c".
# The tool that generated this appeared in the comp.sources.unix newsgroup;
# send mail to comp-sources-unix at uunet.uu.net if you want that tool.
# Contents:  Makefile cio.c cio.man
# Wrapped by rsalz at litchi.bbn.com on Wed Dec  5 12:19:58 1990
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
echo If this archive is complete, you will see the following message:
echo '          "shar: End of archive."'
if test -f 'Makefile' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'Makefile'\"
else
  echo shar: Extracting \"'Makefile'\" \(315 characters\)
  sed "s/^X//" >'Makefile' <<'END_OF_FILE'
X
X#
X# makefile for cio.c
X#
X
XCFLAGS= -O
XLDFLAGS= 
XLIBS= 
X
XCFILES= cio.c
XOBJS= cio.o
X
Xdefault: cio cii coo
X
Xcii: cio
X	ln cio cii
X
Xcoo: cio
X	ln cio coo
X
Xcio:	$(OBJS)
X	cc $(LDFLAGS) -o cio $(OBJS) $(LIBS)
X
Xinstall: cio
X	cp cio $(HOME)/.bin/coo
X	ln $(HOME)/.bin/coo $(HOME)/.bin/cii
X
Xshar:
X	doshar cio.man makefile cio.c
END_OF_FILE
  if test 315 -ne `wc -c <'Makefile'`; then
    echo shar: \"'Makefile'\" unpacked with wrong size!
  fi
  # end of 'Makefile'
fi
if test -f 'cio.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'cio.c'\"
else
  echo shar: Extracting \"'cio.c'\" \(46876 characters\)
  sed "s/^X//" >'cio.c' <<'END_OF_FILE'
X/*----------------------------------------------------------------------------
X/ cio.c - This is a C program to replace the over-grown shell script
X/ that started as something small...
X/
X/ Authors:	Jason P. Winters and Craig J. Kim
X/
X/  We hereby declare this software to be Public Domain.  That means we don't
X/  care what you do with it.  We _would_ prefer that you at least leave our
X/  names in it, as befits Authors, but we can't force you.  Of course, since
X/  it's public domain, all risks/gains using it are your problems.  You don't
X/  have any reason to call us up and complain about it deleting 30megs of
X/  your source code without telling you. ( Software should at least *tell* you
X/  when it does something like that, right? )  :)
X/
X/ Start Date:	November 23, 1988
X/ Revisions:	29-Nov-88 jpw - initial coding completed.
X/		29-Nov-88 cjk - rewrite of front-end logic
X/		30-Nov-88 jpw - Added signals, cleanup of temp files.
X/		30-Nov-88 cjk - added -N option
X/		01-Dec-88 cjk - added usage
X/		01-Dec-88 jpw - added SUID controls for secure files.
X/		02-Dec-88 jpw - added -A option
X/		05-Dec-88 cjk - added '~' commands for message entering
X/		05-Dec-88 cjk - fixed cp to check file status before copying
X/		06-Dec-88 cjk - use separate process for user file input
X/		06-Dec-88 jpw - source now passes System V.2 lint
X/		07-Dec-88 cjk - added environment variable check routine
X/		20-Dec-88 cjk - chmod files in source dir to 0640
X/		21-Dec-88 cjk - put RCS header if not exists
X/		08-Mar-89 cjk - SCCS version
X/		02-Oct-89 jpw - Fixed parsing for header type to insert
X/		                Fixed small bug in directory creation routine
X/		10-May-90 jpw - Changed st += sprintf() code to allow for
X/		                broken sprint() calls.  Failed on Sun
X/		                machines.
X/
X/ Known bugs:
X/       On some systems, the Control-D as end of input in the log entry
X/       routines will cause stdin to be closed, which means the next call
X/       to get an entry (such as a Title file) will fail.  A call to clearerr()
X/       has been added to fix this, but it has not been tested.
X/
X/ To be done:
X/	1. When -U is used, the destination directory should be created if
X/	   it does not exist already.  Also, the file mode should be changed to
X/	   0640 so that overwriting is not possible by anyone other than
X/	   $RCSOWN for security.
X/	2. Full "interactive" mode support
X/   3. Actually use the SCCS version. (Ugh!)
X/---------------------------------------------------------------------------*/
X
X/*#define DEBUG		/* wanna know what's goin' on? */
X/*#define INTERACTIVE	/* enable -I option (incomplete) */
X/*#define void	int	/* if system can't handle void types. */
X#define V_RCS		/* RCS version */
X/*#define V_SCCS		/* SCCS version */
X
X/*--------------------------------------------------- includes -------------*/
X#include <stdio.h>
X#include <string.h>
X#include <ctype.h>
X#include <sys/types.h>
X#include <sys/stat.h>
X#include <errno.h>
X#include <signal.h>
X
X/*--------------------------------------------------- externals ------------*/
Xextern int errno;              /* error code set by system library routines */
X
Xextern FILE *fopen();               /* open a stream (should be in stdio.h) */
Xextern FILE *popen();                 /* open a pipe (should be in stdio.h) */
Xextern char *getenv();                      /* read an environment variable */
Xextern char *tmpnam();                      /* create a temporary file name */
Xextern char *mktemp();                  /* create temp file with a template */
Xextern char *malloc();                        /* allocate a chunk of memory */
Xextern char *getcwd();                             /* get current directory */
Xextern int  unlink();                             /* unlink (delete) a file */
Xextern void exit();                            /* define these for LINT!!!! */
Xextern void perror();                          /* define these for LINT!!!! */
Xextern void free();                            /* define these for LINT!!!! */
X
X/*--------------------------------------------------- defines --------------*/
X#ifndef TRUE
X# define TRUE	1
X# define FALSE	0
X#endif
X
X#define TYPE_LOG	0		/* log message */
X#define TYPE_TITLE	1		/* title message */
X#define MAX_LOG		1020		/* max # of characters for a log */
X
X/*--------------------------------------------------- forwards -------------*/
Xvoid usage(/*_ void _*/);                    /* print program usage message */
Xint  getfinput(/*_ char *name, int type _*/);             /* get title file */
Xint  child_getfinput(/*_ char *name, int type _*/);      /* actual get file */
Xvoid doincmds(/*_ char *argv[], int argc, int in _*/); /* perform user wish */
Xvoid do_ciodir(/*_ char *filenm _*/);    /* do user wish in recursive if -R */
Xint  cio(/*_ char *filenme _*/);                          /* the work horse */
Xint  addrcsext(/*_ char *fname _*/);         /* add RCS file name extension */
Xint  rmrcsext(/*_ char *fname _*/);       /* remove RCS file name extension */
Xvoid inshdr(/*_ char *fname _*/);                      /* insert RCS header */
Xint  makedir(/*_ char *path _*/);    /* make a directory and all its parent */
Xchar *strstr(/*_ char *s1, char *s2 _*/);  /* find a string within a string */
Xchar *strlwr(/*_ char *s _*/);               /* convert upper case to lower */
Xint  asciifile(/*_ char *filename _*/);                  /* check file type */
Xint  rcsfile(/*_ char *filename _*/);             /* check file is RCS file */
Xint  strrd(/*_ char *buf, int max_size,  FILE *fle _*/);/* read from a file */
Xvoid getdir(/*_ void _*/);        /* obtain necessary directory information */
Xint  fix_envstr(/*_ char *cs _*/);  /* remove leading/trailing blanks, etc. */
Xvoid getrcsdir(/*_ char *dir _*/);          /* build rcs dir out of working */
Xvoid getworkdir(/*_ char *dir _*/);         /* build working dir out of rcs */
Xvoid getsrcdir(/*_ char *dir _*/);       /* build source dir out of working */
Xchar *justname(/*_ char *fpath _*/);        /* return just filename portion */
Xchar *memalloc(/*_ int size _*/);  /* allocate memory and check for success */
Xvoid sigcleanup(/*_ void _*/);                 /* cleanup in case interrupt */
Xvoid get_final_id(/*_ void _*/);           /* get user id of RCS file owner */
Xint  nextent(/*_ FILE *fp _*/);    /* read next entry from /etc/passwd file */
X
X/*--------------------------------------------------- globals --------------*/
X#ifdef V_RCS
Xchar *ci_cmd = "ci";                /* command to use to check in a module. */
Xchar *co_cmd = "co";               /* command to use to check out a module. */
X#else
Xchar *ici_cmd = "admin";      /* command to check in a module for 1st time. */
Xchar *ci_cmd = "delta";                    /* command to check in a module. */
Xchar *co_cmd = "get";                     /* command to check out a module. */
X#endif
Xchar *currentdir;                                     /* current directory. */
Xchar *homedir;                                    /* user's home directory. */
Xchar *rcswrk;                                                  /* $RCSWORK. */
Xchar *rcsown;                                                   /* $RCSOWN. */
Xchar *rcsdir;                                                   /* $RCSDIR. */
Xchar *srcdir;                                                   /* $RCSSRC. */
Xchar *headdir;                                                 /* $RCSHEAD. */
Xchar *path;                                                       /* $PATH. */
Xchar *pwdfile = "/etc/passwd";                      /* default passwd file. */
Xchar cioopt[100];                      /* large buffer for command options. */
Xchar d_ent[90];                             /* small buffer for file reads. */
Xchar final[400];                    /* final dir to pass on as destination. */
X#ifdef V_SCCS
Xchar finalfile[400];                  /* final file without SCCS extension. */
X#endif
Xchar logstr[MAX_LOG + 4];                            /* log string to pass. */
Xchar title[100];                                     /* log string to pass. */
Xchar cmdbuf[2400];                                     /* do a single file. */
Xchar editfile[400];                  /* temp edit file, makes cleanup easy. */
Xchar pwdname[20];                                    /* Name found in file. */
Xchar ftypestr[82];                               /* command file(1) output. */
Xchar cii = FALSE;                       /* if set, we are in check in mode. */
Xchar recurse = FALSE;               /* if set, do recursive check in/out's. */
Xchar usertitle = FALSE;                       /* user specified title file. */
Xchar interactive = FALSE;                /* user friendly interactive mode. */
Xchar allfiles = FALSE;                    /* set it TRUE to copy all files. */
Xchar insertheader = FALSE;     /* insert RCS header at the top of the file. */
X#ifdef DEBUG
Xchar noexec = TRUE;                     /* set it FALSE for execution mode. */
Xchar verbose = TRUE;                                    /* be a chatterbox. */
X#else
Xchar noexec = FALSE;                  /* set it TRUE for no execution mode. */
Xchar verbose = FALSE;                                  /* not a chatterbox. */
X#endif
Xchar updsrcdir = FALSE;           /* update master source directory on cii. */
X#ifdef V_SCCS
Xchar do_admin = FALSE;                    /* set it TRUE if first check in. */
X#endif
Xchar *prognam;                                     /* name of this program. */
Xstruct stat filestat;                                  /* file status info. */
Xint s_currentdir;                           /* length of current directory. */
Xint s_homedir;                          /* length of user's home directory. */
Xint s_rcswrk;                                        /* length of $RCSWORK. */
Xint s_rcsdir;                                         /* length of $RCSDIR. */
Xint s_srcdir;                                         /* length of $RCSSRC. */
Xint s_path;                                             /* length of $PATH. */
Xint s_rcsown;                                         /* length of $RCSOWN. */
Xint s_headdir;                                       /* length of $RCSHEAD. */
Xint user_id;                   /* Effective user id to use if ROOT process. */
Xint real_user_id;                      /* Save buffer for original user_id. */
Xint do_unlink;  /* add -l option if not set to avoid unlink of source file. */
X
X/*--------------------------------------------------- main() ----------------
X/ where we begin!
X/---------------------------------------------------------------------------*/
Xmain(argc, argv)
Xint argc;
Xchar *argv[];
X{
X	register int in;
X	register char *cp;		/* used in string updates. */
X
X	prognam = justname(argv[0]);	/* program name */
X	getdir();			/* go get the enviroment pointers. */
X
X	/*
X	 * figure what the user wants us to be by reading program name
X	 */
X	if (!strcmp("ciitest", prognam) || !strcmp("cootest", prognam))
X	{
X		getrcsdir(final, currentdir);	/* setup variables. */
X		/*
X		 * print out our variables here.
X		*/
X#ifdef V_RCS
X		(void) printf("Homedir:%s:  rcsdir:%s:  rcswrk:%s:  rcssrc:%s:\n",
X#else
X		(void) printf("HOME:%s:  SCCSDIR:%s:  SCCSWRK:%s:  SCCSSRC:%s:\n",
X#endif
X				homedir ? homedir : "",
X				rcsdir ? rcsdir : "",
X				rcswrk ? rcswrk : "",
X				srcdir ? srcdir : "");
X		(void) printf("Final dir:%s:\n", final);
X		return(0);
X	}
X	if (!strcmp("cii", prognam))	/* this is input.. */
X		cii = TRUE;		/* show we are inputs. */
X	else if (strcmp("coo", prognam))/* it's not this either.. */
X	{
X		(void) printf("Just what did you think this program was, anyway??\n");
X		return(-1);
X	}
X
X	if (!(user_id = geteuid())) /* we are a root process.. */
X	{
X		(void) umask(027); /* set general mask to private modes. */
X		real_user_id = getuid();
X		if (cii)	/* only set this if we are in checkin mode. */
X			get_final_id(); /* go get the final user ID for file creation. */
X		else
X			user_id = real_user_id;	/* otherwise, use owners real one. */
X		(void) setuid(user_id); /* and, fix up the user id now. */
X#ifdef DEBUG
X		(void) printf("Using Uid:%d:\n", user_id);
X#endif
X	}
X	else
X		real_user_id = user_id;	/* else they should match. */
X
X	/* prepare for disasters */
X	(void) signal(SIGINT, (int (*)()) sigcleanup);
X	(void) signal(SIGQUIT, (int (*)()) sigcleanup);
X
X	cp = cioopt;
X	title[0] = '\0';  /* make sure no titles are required. */
X	for (in = 1; argv[in][0] == '-'; in++) /* while chars here.. */
X	{
X		switch ((int) argv[in][1])	/* what option? */
X		{
X		case '?':		/* print program usage */
X		case '-':
X			usage();
X			return(0);
X		case 'A':		/* copy all files */
X			allfiles = !allfiles;
X			break;
X#ifdef INTERACTIVE
X		case 'I':		/* interactive mode */
X			interactive = !interactive;
X			break;
X#endif
X		case 'H':		/* RCS/SCCS header */
X			insertheader = !insertheader;
X			break;
X		case 'N':		/* do not execute */
X			noexec = !noexec;
X			break;
X		case 'R':		/* Recursion flag. */
X			recurse = !recurse;
X			break;
X		case 'T':		/* get Title flag. */
X			if(cii)	/* get a title file and name. */
X				(void) getfinput(title, TYPE_TITLE);
X			break;
X		case 'U':		/* update working directory */
X			updsrcdir = !updsrcdir;
X			break;
X		case 'V':		/* verbose */
X			verbose = !verbose;
X			break;
X#ifdef V_RCS
X		case 'm':		/* they gave us one */
X#else
X		case 'y':		/* they gave us one */
X#endif
X			(void) strcpy(logstr, &argv[in][1]); /* copy across. */
X			break;
X		case 't':		/* user gave us one */
X			(void) strcpy(title, &argv[in][1]);
X			usertitle = TRUE;
X			break;
X		default:		/* must be a ci or co command */
X			(void) sprintf(cp, " %s", argv[in]); /* append */
X			cp += strlen(cp); /* skip to end of string. */
X			break;
X		}
X	}
X#ifdef INTERACTIVE
X	if (noexec && interactive)	/* resolve conflict of interest */
X		interactive = FALSE;
X#endif
X	doincmds(argv, argc, in);	/* do the check in command. */
X	if (title[0] && !usertitle)	/* if file is here. */
X		(void) unlink(title);	/* zap it! */
X	return(0);			/* we did it good! */
X}
X
X/*--------------------------------------------------- usage() ---------------
X/ print program usage information
X/---------------------------------------------------------------------------*/
Xvoid
Xusage()
X{
X	char *inout = cii ? "in" : "out";
X
X#ifdef INTERACTIVE
X	(void) fprintf(stderr, "Usage: %s [-A] %s[-I] [-N] [-R] %s",
X#else
X	(void) fprintf(stderr, "Usage: %s [-A] %s[-N] [-R] %s",
X#endif
X			prognam, cii ? "[-H] " : "",
X				 cii ? "[-T] [-U] " : "");
X	(void) fprintf(stderr, "[-V] [%s options] [[filename]...]\n",
X			cii ? ci_cmd : co_cmd);
X	(void) fprintf(stderr, "     -A : check %s all files\n", inout);
X#ifdef INTERACTIVE
X	(void) fprintf(stderr, "     -I : interactive mode\n");
X#endif
X	if (cii)
X#ifdef V_RCS
X		(void) fprintf(stderr, "     -H : attach RCS header\n");
X#else
X		(void) fprintf(stderr, "     -H : attach SCCS header\n");
X#endif
X	(void) fprintf(stderr, "     -N : no execute mode\n");
X	(void) fprintf(stderr, "     -R : recursive check %s\n", inout);
X	if (cii)
X	{
X		(void) fprintf(stderr, "     -T : create title file\n");
X		(void) fprintf(stderr, "     -U : update source directory\n");
X	}
X	(void) fprintf(stderr, "     -V : verbose mode\n");
X}
X
X/*--------------------------------------------------- sigcleanup() ----------
X/ cleanup before exiting. 
X/---------------------------------------------------------------------------*/
Xvoid
Xsigcleanup()
X{
X	(void) printf("\n[%s: Interrupted]\n", prognam);
X	if (title[0] && !usertitle)	/* remove title file, if here. */
X		(void) unlink(title);
X	if (editfile[0])		/* if a temp file might be here. */
X		(void) unlink(editfile);	/* attempt to remove it.*/
X	exit(0);
X}
X
X/*--------------------------------------------------- doincmds() ------------
X/ actual do routine
X/---------------------------------------------------------------------------*/
Xvoid
Xdoincmds(argv, argc, in)
Xchar *argv[];
Xint argc, in;
X{
X	register char *cp;
X	char entry[400];
X
X	if (cii)
X	{
X		if (argc == in)		/* no arguments given */
X		{
X			do_ciodir(currentdir);
X			return /* void */;
X		}
X		for ( ; in < argc; in++)
X		{
X			(void) sprintf(entry, "%s/%s", currentdir, argv[in]);
X#ifdef DEBUG
X			(void) printf("Processing %s\n", entry);
X#endif
X			if (stat(entry, &filestat))
X			{
X#ifdef DEBUG
X				(void) printf("Unable to stat(2) %s\n", entry);
X#endif
X				continue;
X			}
X			do_unlink = (filestat.st_uid == real_user_id);
X			if ((filestat.st_mode & S_IFMT) == S_IFDIR)
X				do_ciodir(entry);
X			else
X				(void) cio(entry);
X		}
X	}
X	else /* coo */
X	{
X		char *ep;
X
X		getrcsdir(entry, currentdir);
X		if (argc == in)
X		{
X			do_ciodir(entry);
X			return /* void */;
X		}
X		for (ep = entry; *ep; ep++)
X			;
X		for ( ; in < argc; in++)
X		{
X			(void) sprintf(ep, "/%s", argv[in]);
X#ifdef DEBUG
X			(void) printf("Processing %s\n", entry);
X#endif
X			if (stat(entry, &filestat))
X			{
X				if (!addrcsext(entry))
X					continue;
X				if (stat(entry, &filestat))
X					continue;
X			}
X			do_unlink = (filestat.st_uid == real_user_id);
X			if ((filestat.st_mode & S_IFMT) == S_IFDIR)
X				do_ciodir(entry);
X			else
X				(void) cio(entry);
X		}
X			
X	}
X	return /* void */;
X}
X
X/*--------------------------------------------------- do_ciodir() -----------
X/ actual do routine - Warning: RECURSIVE
X/---------------------------------------------------------------------------*/
Xvoid
Xdo_ciodir(dir)
Xchar *dir;
X{
X	FILE *pp;
X	char *cmd, *entry;
X
X	(void) sprintf(cmd = memalloc(strlen(dir) + 4), "ls %s", dir);
X	pp = popen(cmd, "r");
X	free(cmd);
X	if (!pp)
X		return /* void */;
X	entry = memalloc(strlen(dir) + 30);
X	while (strrd(d_ent, 80, pp) != -1)
X	{
X		(void) sprintf(entry, "%s/%s", dir, d_ent);
X		if (stat(entry, &filestat))
X		{
X#ifdef DEBUG
X			(void) printf("Unable to stat(2) %s\n", entry);
X#endif
X			continue;
X		}
X		do_unlink = (filestat.st_uid == real_user_id);
X		if ((filestat.st_mode & S_IFMT) == S_IFDIR)
X		{
X			if (recurse)
X				do_ciodir(entry);
X			else
X				continue;
X		}
X		else
X			(void) cio(entry);
X	}
X	free(entry);
X	(void) pclose(pp);
X	return /* void */;
X}
X
X/*--------------------------------------------------- cio() -----------------
X/ do this to file, if we can.
X/---------------------------------------------------------------------------*/
Xint
Xcio(filename)
Xchar *filename;
X{
X	register char *cp, *st;		/* some char pointers */
X	int do_copy = FALSE;		/* do cp(1) */
X	char titlest[100];		/* for file names. */
X
X	if (cii)
X	{
X		if (!asciifile(filename))	/* if not ascii file */
X		{
X			if (!allfiles)
X				return(FALSE);	/* don't if not forced copy */
X			do_copy = TRUE;
X		}
X		else if (insertheader)		/* inserting default header */
X		{
X			inshdr(filename);
X		}
X	}
X	else if (!rcsfile(filename))		/* not an RCS file */
X	{
X		if (!allfiles)
X			return(FALSE);
X		do_copy = TRUE;			/* simply cp(1) it */
X	}
X
X	if (cii && !do_copy && !logstr[0]) /* we don't have a log entry yet. */
X	{
X		if (noexec)
X		{
X			(void) printf("Logfile entry bypassed.\n");
X			(void) strcpy(logstr, " ");	/* fake it */
X		}
X		else if (!getfinput(titlest, TYPE_LOG)) /* if we entered anything */
X		{
X			FILE *fp;
X			char buf[100];
X			register char *bp;
X			register int numchars = 0;
X
X			if (fp = fopen(titlest, "r"))
X			{
X				st = logstr;
X#ifdef V_RCS
X				(void) sprintf(st, " -m\"");
X#else
X				(void) sprintf(st, " -y\"");
X#endif
X				st += strlen(st); /* skip to end of string. */
X				while (strrd(buf, 80, fp) != -1)
X				{	/*
X					 * escape quotes (") characters
X					 */
X					bp = buf;
X					while (*bp)
X					{
X						if (*bp == '"')
X							if (numchars < MAX_LOG)
X							{
X								numchars++;
X								*st++ = '\\'; /*escape*/
X							}
X						if (numchars < MAX_LOG)
X						{
X							numchars++;
X							*st++ = *bp++; /* append */
X						}
X					}
X					*st++ = '\n'; /* add end of line now. */
X				}
X				if (*(st - 1) == '\n')
X					*(st - 1) = '"';
X				else
X					*st++ = '"';
X				*st = '\0';
X				(void) fclose(fp);
X				if (numchars >= MAX_LOG)
X					(void) printf("Log entry truncated.\n");
X			}
X			(void) unlink(titlest); /* cleanup. */
X		}
X	}
X
X	titlest[0] = '\0'; /* cleanup string reference. */
X	if (cii)
X		getrcsdir(final, filename);
X	else
X		getworkdir(final, filename);
X
X	if (st = strrchr(final, '/'))	/* if this has a sub dir. */
X	{
X		*st = '\0';	/* terminate it at the directoy level. */
X		if (access(final, 0))	/* it's not here... */
X			if (!makedir(final))	/* so try and make it. */
X			{
X				(void) printf("Could not create directory :%s:\n", final);
X				return(FALSE);
X			}
X		*st = '/';	/* restore the rest of the file name. */
X	}
X
X	if (cii)
X	{
X		if (!do_copy)	/* not in binary mode. */
X		{	/*
X			 * make the new file name and check if it's here
X			 */
X#ifdef V_SCCS
X			(void) strcpy(finalfile, final);
X#endif
X			(void) addrcsext(final);
X			if (!noexec && access(final, 0))
X			{
X				/*
X				 * if not, need to get a title message for it
X				 */
X				if (!title[0]) /* no title file yet. */
X					(void) getfinput(title, TYPE_TITLE); /* get one */
X				if (title[0])  /* if we've one, build command */
X					(void) sprintf(titlest, " -t%s ", title);
X#ifdef V_SCCS
X				do_admin = TRUE;	/* 1st check-in */
X#endif
X			}
X#ifdef V_SCCS
X			else
X				do_admin = FALSE;
X#endif
X		}
X		else if (noexec)
X			(void) strcpy(titlest, " -tfile_name ");
X	}
X	else	/* check out mode. */
X	{	/*
X		 * find a comma to make the new name.
X		 * if found one, strip it to make a new name.
X		 */
X		(void) rmrcsext(final);
X	}
X
X	/*
X	 * Build command string
X	 */
X	if (do_copy)	/* we want to just copy the file now. */
X	{
X		if (!interactive && !noexec && !access(final, 0))
X		{
X			(void) printf("%s already exists.  Overwrite? (yes) ", final);
X			(void) strrd(cmdbuf, 20, stdin);
X			(void) strlwr(cmdbuf);
X			if (strncmp(cmdbuf, "yes", strlen(cmdbuf)))
X				return(TRUE);
X		}
X		(void) sprintf(cmdbuf, "cp %s %s", filename, final); /* copy command */
X	}
X	else if (cii)
X	{
X#ifdef V_RCS
X		(void) sprintf(cmdbuf, "%s%s%s%s%s %s %s",
X			ci_cmd, cioopt, titlest, logstr,
X			do_unlink ? "" : " -l", filename, final);
X#else
X		st = cmdbuf;
X		(void) sprintf(st, "cp %s %s; ", filename, finalfile);
X		st += strlen(st); /* skip to end of string. */
X		if (cp = strrchr(final, '/'))
X		{
X			*cp = '\0';
X			(void) sprintf(st, "cd %s; ", final);
X			st += strlen(st); /* skip to end of string. */
X			*cp = '/';
X		}
X		if (do_admin)
X		{
X			(void) sprintf(st, "%s -i%s%s%s%s %s",
X				ici_cmd, justname(finalfile),
X				cioopt, titlest, logstr, justname(final));
X			st += strlen(st); /* skip to end of string. */
X		}
X		else
X		{
X			(void) sprintf(st, "%s%s%s %s",
X				ci_cmd, cioopt, logstr, justname(final));
X			st += strlen(st); /* skip to end of string. */
X		}
X		if (do_unlink)
X		{
X			(void) sprintf(st, "; rm %s", filename);
X			st += strlen(st); /* skip to end of string. */
X		}
X#endif
X	}
X	else	/* coo */
X	{
X#ifdef V_RCS
X		(void) sprintf(cmdbuf, "%s%s %s %s",
X				co_cmd, cioopt, filename, final);
X#else
X		st = cmdbuf;
X		(void) sprintf(st, "rm -f %s; ", final);
X		st += strlen(st); /* skip to end of string. */
X		if (cp = strrchr(filename, '/'))
X		{
X			*cp = '\0';
X			(void) sprintf(st, "cd %s; ", filename);
X			st += strlen(st); /* skip to end of string. */
X			*cp = '/';
X		}
X		addrcsext(filename);
X		(void) sprintf(st, "%s%s %s", co_cmd, cioopt,
X			justname(filename));
X		st += strlen(st); /* skip to end of string. */
X		if (cp = strrchr(final, '/'))
X		{
X			cp++;
X			(void) sprintf(st, "; mv %s %s", cp, final);
X			st += strlen(st); /* skip to end of string. */
X		}
X#endif
X	}
X
X#ifdef INTERACTIVE
X	if (interactive)
X	{
X		char ans[20];
X		int done = FALSE;
X
X		do
X		{
X			(void) printf("%s\nExecute? (yes) ", cmdbuf);
X			(void) strrd(ans, 20, stdin);
X			st = ans;
X			while (isspace(*st))
X				st++;
X			if (!*st)
X				(void) strcpy(st, "yes");
X			(void) strlwr(st);
X			if (!strncmp(st, "yes", strlen(st))) {
X				done = TRUE;
X				(void) system(cmdbuf);	/* do it. */
X			}
X			else if (*st == '?')
X			{
X				(void) printf("\n");
X				(void) printf("yes     Do it\n");
X				(void) printf("no      Don't do it\n");
X				(void) printf("view    View current file\n");
X				(void) printf("?       Print this message\n");
X				(void) printf("\n");
X			}
X			else if (!strncmp(st, "view", strlen(st)))
X			{
X				(void) printf("Not implemented yet!\n\n");
X			}
X			else /* take it as "no" */
X				done = TRUE;
X		} while (!done);
X	}
X	else
X	{
X#endif /* INTERACTIVE */
X		if (verbose)
X			(void) printf("%s command :%s:\n", prognam, cmdbuf);
X		if (!noexec)
X		{
X			if (do_copy && !verbose)
X				(void) printf("%s\n", cmdbuf);
X			(void) system(cmdbuf);	/* do it. */
X		}
X		else if (!verbose)	/* if don't want to exec, don't */
X			(void) printf("%s\n", cmdbuf);
X#ifdef INTERACTIVE
X	}
X#endif /* INTERACTIVE */
X	if (updsrcdir)			/* update source directory */
X	{
X		getsrcdir(final, filename);
X		(void) sprintf(cmdbuf, "cp %s %s", filename, final);
X		if (noexec)		/* don't actual do it */
X			(void) printf("%s\n", cmdbuf);
X		else
X		{
X			if (verbose)	/* speak, yo wise one! */
X				(void) printf("%s command :%s:\n", prognam, cmdbuf);
X			if (!strcmp(filename, final))
X				(void) printf("Source and destination identical.  Not updated.\n");
X			else
X			{
X				(void) chmod(final, 0640);
X				(void) system(cmdbuf);	/* do it! */
X			}
X		}
X	}
X	return(TRUE);
X}
X
X/*--------------------------------------------------- addrcsext() -----------
X/ add RCS file extension ",v" if there isn't one already
X/---------------------------------------------------------------------------*/
Xint
Xaddrcsext(fname)
Xchar *fname;
X{
X	register char *cp;
X
X#ifdef V_RCS
X	for (cp = fname; *cp; cp++)
X		;
X	if (*--cp == 'v' && *(cp - 1) == ',')
X		return(0);		/* already there */
X
X	*++cp = ',';			/* add ",v" */
X	*++cp = 'v';
X	*++cp = '\0';
X#else
X	char t_name[20];
X
X	cp = justname(fname);
X	if (*cp == 's' && *(cp + 1) == '.')
X		return(0);
X	(void) strcpy(t_name, cp);
X	*cp++ = 's';			/* add "s." in front of file name */
X	*cp++ = '.';
X	(void) strcpy(cp, t_name);
X#endif
X	return(1);
X}
X
X/*--------------------------------------------------- rmrcsext() ------------
X/ remove RCS extension if there is one; returns 1 if remove, else 0
X/---------------------------------------------------------------------------*/
Xint
Xrmrcsext(fname)
Xchar *fname;
X{
X	register char *cp;
X
X#ifdef V_RCS
X	for (cp = fname; *cp; cp++)
X		;
X	if (*--cp == 'v' && *--cp == ',')
X	{
X		*cp = '\0';
X		return(1);
X	}
X#else
X	cp = justname(fname);
X	if (*cp == 's' && *(cp + 1) == '.')
X	{
X		(void) strcpy(cp, cp + 2);
X		return(1);
X	}
X#endif
X	return(0);
X}
X
X/*--------------------------------------------------- inshdr() --------------
X/ insert RCS header if none exists already
X/---------------------------------------------------------------------------*/
Xvoid
Xinshdr(t_name)
Xchar *t_name;
X{
X#	define FTYPE_C		0	/* C program text */
X#	define FTYPE_S		1	/* assembly program text */
X#	define FTYPE_SH		2	/* shell script */
X#	define FTYPE_ROFF	3	/* nroff, tbl, eqn, etc */
X#	define FTYPE_F		4	/* Fortran program text */
X#	define FTYPE_DEFAULT	5	/* don't know */
X#	define FTYPE_MK		6	/* makefile script */
X#	define FTYPE_H		7	/* C header file text */
X
X	static struct _ftype {
X		char *keyword;		/* phrase may exist in file(1) output */
X		char *header;		/* header template file name. */
X	} ftype[] = {
X#ifdef V_RCS
X		{ "c program",	".rcshead.c"	},	/* FTYPE_C */
X		{ "assembler",	".rcshead.s"	},	/* FTYPE_S */
X		{ "command",	".rcshead.sh"	},	/* FTYPE_SH */
X		{ "roff, tbl",	".rcshead.roff"	},	/* FTYPE_ROFF */
X		{ "fortran",	".rcshead.f"	},	/* FTYPE_F */
X		{ 0,		".rcshead"	},	/* FTYPE_DEFAULT */
X		{ 0,		".rcshead.mk"	},	/* FTYPE_MK */
X		{ 0,		".rcshead.h"	} };	/* FTYPE_H */
X#else
X		{ "c program",	".sccshead.c"	},	/* FTYPE_C */
X		{ "assembler",	".sccshead.s"	},	/* FTYPE_S */
X		{ "command",	".sccshead.sh"	},	/* FTYPE_SH */
X		{ "roff, tbl",	".sccshead.roff"},	/* FTYPE_ROFF */
X		{ "fortran",	".sccshead.f"	},	/* FTYPE_F */
X		{ 0,		".sccshead"	},	/* FTYPE_DEFAULT */
X		{ 0,		".sccshead.mk"	},	/* FTYPE_MK */
X		{ 0,		".sccshead.h"	} };	/* FTYPE_H */
X#endif /* V_RCS */
X	static struct _fext {
X		char *name;
X		int type;
X	} fext[] = {
X		{ ".c",		FTYPE_C		},
X		{ ".h",		FTYPE_H		},
X		{ ".s",		FTYPE_S		},
X		{ ".f",		FTYPE_F		},
X		{ ".man",	FTYPE_ROFF	},
X		{ ".mk",	FTYPE_MK	},
X		{ ".1",		FTYPE_ROFF	},
X		{ ".2",		FTYPE_ROFF	},
X		{ ".3",		FTYPE_ROFF	},
X		{ ".4",		FTYPE_ROFF	},
X		{ ".5",		FTYPE_ROFF	},
X		{ ".6",		FTYPE_ROFF	},
X		{ ".7",		FTYPE_ROFF	},
X		{ ".8",		FTYPE_ROFF	},
X		{ ".9",		FTYPE_ROFF	},
X		{ 0,		0		} };
X	FILE *ifp, *ofp;
X	char buf[4096], headfile[128], tempfile[20], fname[40];
X	register int i, c, ft = FTYPE_DEFAULT, err=0;
X	register char *ext;
X
X	strcpy(fname, justname(t_name)); /* copy over only name. */
X
X	if (!(ifp = fopen(t_name, "r")))	/* quickly check for RCS header */
X		return;
X					/* we are looking for "$Header" */
X					/* within first 50 lines of the file */
X	for (i = 0; strrd(buf, 128, ifp) > 0 && i < 50; i++)
X#ifdef V_RCS
X		if (strstr(buf, "$Header"))
X#else
X		if (strstr(buf, "#ident"))
X#endif
X		{
X			(void) fclose(ifp);
X			if (verbose)
X#ifdef V_RCS
X				(void) printf("%s already has a RCS header.\n",
X#else
X				(void) printf("%s already has a SCCS header.\n",
X#endif /* V_RCS */
X						t_name);
X			return;
X		}
X
X	(void) fclose(ifp);
X					/* examine file(1) output */
X	for (i = 0; ftype[i].keyword; i++)
X		if (strstr(ftypestr, ftype[i].keyword))
X		{
X			ft = i;
X			break;		/* found one */
X		}
X
X	if (!ftype[i].keyword)		/* file(1) didn't help */
X	{				/* examine file extension */
X		if (ext = strrchr(fname, '.'))
X		{
X			for (i = 0; fext[i].name; i++)
X				if (!strcmp(ext, fext[i].name))
X					ft = fext[i].type;
X		}
X		else
X		{			/* check if makefile script */
X			(void) strcpy(buf, fname);
X			(void) strlwr(buf);
X			if (!strcmp(buf, "makefile") || !strcmp(buf, "Makefile"))
X				ft = FTYPE_MK; /* If either of two fixed names.. */
X		}
X	}
X	else if (ft == FTYPE_C)		/* see if source or header */
X	{
X		if ((ext = strrchr(fname, '.')) && !strcmp(ext, ".h"))
X			ft = FTYPE_H;
X	}
X	if (verbose)			/* is this necessary */
X		(void) printf("%s is type [%d]\n", fname, ft);
X	if (noexec)			/* no execution mode */
X		return;
X
X	(void) sprintf(headfile, "%s%s", headdir, ftype[ft].header);
X	if (!(ifp = fopen(headfile, "r")))
X	{
X		(void) printf("Unable to open header template file [%s]\n",
X				headfile);
X		return;
X	}
X	/* build a tmp file in the same directory as old file. */
X	strcpy(tempfile, t_name);
X	ext = justname(tempfile); /* find end of path. */
X	*ext = '\0'; /* and terminate path there. */
X	(void) strcat(tempfile, "ciotmp._XXXXXX"); /* add tmp name */
X	(void) mktemp(tempfile);		/* generate temp file name */
X	if (!(ofp = fopen(tempfile, "w")))	/* open temp file */
X	{
X		(void) printf("Unable to open temporary file [%s]\n", tempfile);
X		(void) fclose(ifp);
X		return;
X	}
X	while ((c = fgetc(ifp)) != EOF)		/* copy header first */
X		(void) fputc(c, ofp);
X	(void) fclose(ifp);
X	if (!(ifp = fopen(t_name, "r")))		/* open check-in file */
X	{
X		(void) printf("Unable to open [%s] for read\n", t_name);
X		(void) fclose(ofp);
X		(void) unlink(tempfile);
X		return;
X	}
X	while ((c = fgetc(ifp)) != EOF)		/* append to temp file */
X		if(fputc(c, ofp) == EOF)
X			err=1; /* couldn't write error. */
X	(void) fclose(ifp);			/* done */
X	(void) fclose(ofp);
X
X/* ok.  It's hard to make sure that everthing has gone well; if we unlink
X   the src file and can't link the temp file, we could lose everthing.
X   So, if copy fails, leave temp file alone, as it may be the only copy
X   we have left!  If the unlinking the original fails, we can remove the
X   copy, as we don't need it.
X*/
X	if(!err && !unlink(t_name))	/* 'mv tempfile fname' */
X	{
X		if(!link(tempfile, t_name)) /* 'cp tempfile t_name' */
X			(void) unlink(tempfile);		/* 'rm tempfile' */
X		else
X			(void) printf("Link of %s and %s failed after removing %s.\n%s not removed.\n",
X				tempfile, t_name, t_name, tempfile);
X	}
X	else
X	{
X		(void) unlink(tempfile);		/* 'rm tempfile' */
X		(void) printf("Could not insert header into %s.  Copy failed.\n", t_name);
X	}
X}
X
X/*--------------------------------------------------- makedir() -------------
X/ make a directory path, with recursion.
X/ returns TRUE if successful, FALSE otherwise.
X/
X/ This really needs to be re-written.  It works, but that's all I can really
X/ say for it...  Hey, *I* don't have mkdir() calls!
X/---------------------------------------------------------------------------*/
Xint
Xmakedir(newpath)
Xchar *newpath;		/* path name to make. */
X{
X	register char *st, *cp;
X
X	if(!*newpath) return(FALSE); /* skip last directory attempt. */
X	cp = memalloc(strlen(newpath) + 24);
X	(void) sprintf(cp, "/bin/mkdir %s 2>/dev/null", newpath);
X	if(verbose)
X		(void) printf("calling mkdir: %s\n", cp);
X	if (noexec)
X	{
X		(void) printf("%s\n", cp);
X		free(cp);
X		return(TRUE);
X	}
X	if (system(cp))			/* it failed.. */
X	{
X		(void) strcpy(cp, newpath);	/* get current one. */
X		st = strrchr(cp, '/'); /* remove one more layer.. */
X		*st = '\0';		/* terminate here. */
X		if (makedir(cp) == FALSE)	/* try and build next level back. */
X		{
X			free(cp);
X			return(FALSE);
X		} /* ok, so.. it passed on back. Try this again. */
X		else if(makedir(newpath) == FALSE)
X		{
X			free(cp);
X			return(FALSE);
X		}
X	}
X	free(cp);
X	return(TRUE);
X}
X
X/*--------------------------------------------------- strstr() --------------
X/ find a substring within a string
X/---------------------------------------------------------------------------*/
Xchar *
Xstrstr(s1, s2)
Xregister char *s1, *s2;
X{
X	register int l;
X
X	if (l = strlen(s2))
X		for ( ; s1 = strchr(s1, s2[0]); s1++)
X			if (memcmp(s1, s2, l) == 0)
X				break;
X	return(s1);
X}
X
X/*--------------------------------------------------- strlwr() ---------------
X/ strlwr.c - convert passed string to its equivalent lowercases
X/----------------------------------------------------------------------------*/
Xchar *
Xstrlwr(s)
Xregister char *s;
X{
X	char *op;
X
X	for (op = s; *s; s++)
X		if (isupper(*s))
X			*s = _tolower(*s);
X	return(op);
X}
X
X/*--------------------------------------------------- asciifile() -----------
X/ check if passed file is an ascii file using file(1) command
X/---------------------------------------------------------------------------*/
Xint
Xasciifile(fn)
Xchar *fn;
X{
X	char cmdstr[256];
X	register FILE *fp;
X
X	(void) sprintf(cmdstr, "file %s", fn);
X	if (!(fp = popen(cmdstr, "r")))
X		return(FALSE);
X	(void) strrd(ftypestr, 80, fp); /* get a line. */
X	(void) pclose(fp);	/* and done. */
X#ifdef DEBUG
X	(void) printf("%s\n", cmdstr);
X#endif
X	if (strstr(ftypestr, "text"))
X		return(TRUE);
X	return(FALSE);
X}
X
X/*--------------------------------------------------- rcsfile() -------------
X/ check if passed file is an RCS file using file(1) command
X/---------------------------------------------------------------------------*/
Xint
Xrcsfile(fn)
Xchar *fn;
X{
X	char cmdstr[256];
X	register FILE *fp;
X
X	(void) sprintf(cmdstr, "file %s", fn);
X	if (!(fp = popen(cmdstr, "r")))
X		return(FALSE);
X	(void) strrd(ftypestr, 80, fp); /* get a line. */
X	(void) pclose(fp);	/* and done. */
X#ifdef DEBUG
X	(void) printf("%s\n", cmdstr);
X#endif
X#ifdef V_RCS
X	if (strstr(ftypestr, "text"))
X#else
X	if (strstr(ftypestr, "sccs"))
X#endif
X		return(TRUE);
X	return(FALSE);
X}
X
X/*--------------------------------------------------- strrd() ----------------
X/ read from given file pointer until a line separator or end-of-file or
X/ (len) characters excluding the terminator.
X/---------------------------------------------------------------------------*/
Xint
Xstrrd(buf, len, fle)
Xchar *buf;
Xint len;
XFILE *fle;
X{
X	int c0, i0 = 0;
X
X	while (((c0 = getc(fle)) != EOF) && c0 && c0 != '\n')
X		if(i0 < len)  /* if room in buffer..*/
X			buf[i0++] = (char) c0;  /* save it. */
X	buf[i0] = 0;
X	if (i0 == 0 && c0 == EOF)
X		return(-1);
X	return(i0);
X}
X
X/*--------------------------------------------------- getdir() --------------	
X/ get and readin variables for later.
X/---------------------------------------------------------------------------*/
Xvoid
Xgetdir()
X{
X	register char *cp;
X
X	if(cp = getenv("HOME"))		/* get user's home dir. */
X	{
X		(void) strcpy(homedir = memalloc(strlen(cp) + 2), cp);
X		if (!(s_homedir = fix_envstr(homedir)))
X		{
X			free(homedir);
X			homedir = (char *) 0;
X		}
X	}
X	else				/* this should NEVER happen */
X	{
X		(void) fprintf(stderr, "No home directory???\n");
X		exit(-1);
X	}
X
X#ifdef V_RCS
X	if(cp = getenv("RCSDIR"))	/* RCS directory */
X#else
X	if(cp = getenv("SCCSDIR"))	/* SCCS directory */
X#endif
X		(void) strcpy(rcsdir = memalloc(strlen(cp) + 2), cp);
X	else				/* RCS is $HOME/RCS */
X		(void) sprintf(rcsdir = memalloc(s_homedir + 6),
X#ifdef V_RCS
X				"%s/RCS", homedir);
X#else
X				"%s/SCCS", homedir);
X#endif
X	if (!(s_rcsdir = fix_envstr(rcsdir)))
X	{
X		free(rcsdir);
X		rcsdir = (char *) 0;
X	}
X
X#ifdef V_RCS
X	if(cp = getenv("RCSWORK"))	/* user's working directory */
X#else
X	if(cp = getenv("SCCSWORK"))	/* user's working directory */
X#endif
X	{
X		(void) strcpy(rcswrk = memalloc(strlen(cp) + 2), cp);
X		if (!(s_rcswrk = fix_envstr(rcswrk)))
X		{
X			free(rcswrk);
X			rcswrk = (char *) 0;
X		}
X	}
X
X#ifdef V_RCS
X	if (cp = getenv("RCSSRC"))	/* master source directory */
X#else
X	if (cp = getenv("SCCSSRC"))	/* master source directory */
X#endif
X	{
X		(void) strcpy(srcdir = memalloc(strlen(cp) + 2), cp);
X		if (!(s_srcdir = fix_envstr(srcdir)))
X		{
X			free(srcdir);
X			srcdir = (char *) 0;
X		}
X	}
X
X#ifdef V_RCS
X	if (cp = getenv("RCSHEAD"))	/* RCS header file directory */
X#else
X	if (cp = getenv("SCCSHEAD"))	/* SCCS header file directory */
X#endif
X	{
X		(void) strcpy(headdir = memalloc(strlen(cp) + 2), cp);
X		if (!(s_headdir = fix_envstr(headdir)))
X		{
X			free(headdir);
X			headdir = homedir;
X		}
X	}
X	else
X		headdir = homedir;
X
X#ifdef V_RCS
X	if (cp = getenv("RCSOWN"))	/* the owner of RCS files */
X#else
X	if (cp = getenv("SCCSOWN"))	/* the owner of SCCS files */
X#endif
X	{
X		s_rcsown = strlen(cp);
X		(void) strcpy(rcsown = memalloc(s_rcsown + 1), cp);
X	}
X
X	if(cp = getenv("PATH"))		/* current path, ie. $PATH */
X	{
X		s_path = strlen(cp);
X		(void) strcpy(path = memalloc(s_path + 1), cp);
X	}
X
X	if((currentdir = getcwd((char *)NULL, 200)) == NULL)
X	{
X		(void) fprintf(stderr, "Cannot get working dir.\n");
X		exit(-1);
X	}
X	s_currentdir = strlen(currentdir);
X}
X
X/*--------------------------------------------------- fix_envstr() ----------
X/ fix environment variable to avoid problems later.
X/ 1. strip leading/trailing white spaces
X/ 2. strip duplicate slashes
X/ 3. add trailing slash
X/ 4. return string length
X/---------------------------------------------------------------------------*/
Xint
Xfix_envstr(cs)
Xchar *cs;
X{
X	register char *cp, *dp;
X	register int was_slash = FALSE;
X
X	cp = dp = cs;
X	while (isspace(*cp))		/* remove leading white spaces */
X		cp++;
X
X	if (!*cp)			/* string was a full of blanks */
X		return(0);
X
X	while (*cp)
X	{
X		if (*cp == '/')
X		{
X			if (was_slash)
X			{
X				cp++;	/* strip duplicate slashes */
X				continue;
X			}
X			else
X				was_slash = TRUE;
X		}
X		else
X			was_slash = FALSE;
X		*dp++ = *cp++;
X	}
X
X	do				/* remove trailing while spaces */
X	{
X		dp--;
X	} while (isspace(*dp));
X
X	if (*dp != '/')			/* add trailing slash */
X		*++dp = '/';
X	*++dp = '\0';			/* null terminate */
X	return(strlen(cs));
X}
X
X/*--------------------------------------------------- getrcsdir() -----------
X/ get $RCSDIR + tail directory
X/---------------------------------------------------------------------------*/
Xvoid
Xgetrcsdir(tdir, sdir)
Xchar *tdir, *sdir;
X{
X	register char *cp = sdir;
X
X	if(rcswrk && !strncmp(rcswrk, cp, s_rcswrk))
X		cp += s_rcswrk;
X	if(homedir && !strncmp(homedir, cp, s_homedir))
X		cp += s_homedir;
X	/*
X	 * build the final directory name
X	 */
X	(void) sprintf(tdir, "%s%s", rcsdir, cp);
X}
X
X/*--------------------------------------------------- getworkdir() ----------
X/ get $RCSWORK or $HOME + tail
X/---------------------------------------------------------------------------*/
Xvoid
Xgetworkdir(tdir, sdir)
Xchar *tdir, *sdir;
X{
X	register char *cp = sdir;
X
X	if (rcsdir && !strncmp(rcsdir, cp, s_rcsdir))
X		cp += s_rcsdir;
X	(void) sprintf(tdir, "%s%s", rcswrk ? rcswrk : homedir, cp);
X}
X
X/*--------------------------------------------------- getsrcdir() -----------
X/ get $RCSSRC + tail
X/---------------------------------------------------------------------------*/
Xvoid
Xgetsrcdir(tdir, sdir)
Xchar *tdir, *sdir;
X{
X	register char *cp = sdir;
X
X	if (rcsdir && !strncmp(rcsdir, cp, s_rcsdir))
X		cp += s_rcsdir;
X	if(homedir && !strncmp(homedir, cp, s_homedir))
X		cp += s_homedir;
X	(void) sprintf(tdir, "%s%s", srcdir ? srcdir : homedir, cp);
X}
X
X/*--------------------------------------------------- getfinput() ------------
X/ get a title file.
X/---------------------------------------------------------------------------*/
Xint
Xgetfinput(name, type)
Xchar *name;		/* buffer to put file name into. */
Xint type;		/* what data we want. */
X{
X	int stat_loc;
X
X	(void) strcpy(name, tmpnam(editfile));
X	if (fork())
X	{	/* parent just waits for the child to finish */
X		(void) wait(&stat_loc);
X	}
X	else
X	{	/* child does his/her stuff */
X		(void) signal(SIGINT, SIG_DFL);
X		(void) signal(SIGQUIT, SIG_DFL);
X		exit(child_getfinput(name, type) == TRUE ? 0 : -1);
X	}
X	return((stat_loc >> 8) & 0xff);
X}
X
X/*--------------------------------------------------- child_getfinput() -----
X/ actual get title file.
X/---------------------------------------------------------------------------*/
Xstatic int
Xchild_getfinput(name, type)
Xchar *name;		/* buffer to put file name into. */
Xint type;		/* what data we want. */
X{
X	static char *input_type[2] = { "log", "title" };
X	FILE *fp, *xfp;
X	register char *st;
X	int c, done = FALSE;
X	char buf[82];			/* just larger than input buffer. */
X
X	(void) setuid(real_user_id);		/* user's real user id */
X	if((fp = fopen(name, "w")) == NULL) /* failed open. */
X	{
X		(void) unlink(name);		/* remove it. */
X		name[0] = '\0';
X		(void) printf("Unable to create tmp file.\n");
X		return(FALSE);
X	}
X	(void) printf("Enter %s message, <ret>.<ret> or Control-D to end:\n",
X		input_type[type]);
X	while (!done)
X	{
X		(void) printf(">>");
X		if(strrd(buf, 80, stdin) == -1) /* read in one line. */
X		{
X			/* ok, read somewhere that this is possible.  By doing this,
X			   we should be able to continue after a control-D.
X			*/
X			clearerr(stdin);
X			break;
X		}
X		if(!strcmp(".", buf))	/* end of message */
X			break;
X		if (buf[0] == '~')	/* special command */
X		{
X			switch (buf[1])	/* command character */
X			{
X			case '?':	/* print usage, help message */
X				(void) printf("\n");
X				(void) printf("~.    End of input\n");
X				(void) printf("~!    Invoke shell\n");
X				(void) printf("~e    Edit message using an editor\n");
X				(void) printf("~p    Print message buffer\n");
X				(void) printf("~r    Read in a file\n");
X				(void) printf("~w    Write message to a file\n");
X				(void) printf("~?    Print this message\n");
X				(void) printf("\n");
X				break;
X			case '!':	/* shell */
X				st = getenv("SHELL");
X				(void) system(st ? st : "/bin/sh");
X				(void) printf("[Press RETURN to continue]");
X				(void) strrd(buf, 20, stdin);
X				break;
X			case 'p':	/* print message buffer content */
X				(void) fclose(fp);
X				fp = fopen(name, "r");
X				while ((c = fgetc(fp)) != EOF)
X					(void) fputc(c, stdout);
X				(void) fclose(fp);
X				fp = fopen(name, "a");
X				(void) printf("Continue entering %s message.\n",
X					input_type[type]);
X				break;
X			case 'e':	/* editor */
X			case 'v':	/* visual */
X				(void) fclose(fp);
X				st = getenv(buf[1] == 'e' ?"EDITOR":"VISUAL");
X				(void) sprintf(buf, "%s %s",
X					st ? st : "/usr/bin/vi", name);
X				(void) system(buf);
X				fp = fopen(name, "a");
X				(void) printf("Continue entering %s message.\n",
X					input_type[type]);
X				break;
X			case 'r':	/* read in a file */
X				st = &buf[2];
X				while (isspace(*st))
X					st++;
X				if (!*st)
X				{
X					(void) printf("File name missing!\n");
X					break;
X				}
X				if (xfp = fopen(st, "r"))
X				{
X					while ((c = fgetc(xfp)) != EOF)
X						(void) fputc(c, fp);
X					(void) fclose(xfp);
X				}
X				else
X					(void) printf("Unable to open %s.\n",
X							st);
X				break;
X			case 'w':	/* write message to a file */
X				st = &buf[2];
X				while (isspace(*st))
X					st++;
X				if (!*st)
X					(void) printf("File name missing!\n");
X				else
X				{
X					if (xfp = fopen(st, "a"))
X					{
X						(void) fclose(fp);
X						fp = fopen(name, "r");
X						while ((c = fgetc(fp)) != EOF)
X							(void) fputc(c, xfp);
X						(void) fclose(xfp);
X						(void) fclose(fp);
X						fp = fopen(name, "a");
X					}
X					else
X						(void) printf("Unable to open %s.\n",
X							st);
X				}
X				break;
X			case '.':	/* end of message */
X				done = TRUE;
X				break;
X			default:	/* user doesn't know */
X				(void) printf("Unrecognized command %c -- ignored\n",
X					buf[1] & 0x7f);
X				break;
X			}
X			continue;
X		}
X		(void) fprintf(fp, "%s\n", buf);
X	}
X	(void) fclose(fp);
X	(void) printf("\n");
X	return(TRUE);
X}
X
X/*--------------------------------------------------- justname() ------------
X/ extract just filename from a full path
X/---------------------------------------------------------------------------*/
Xchar *
Xjustname(fpath)
Xchar *fpath;
X{
X	register char *cp;
X
X	if (cp = strrchr(fpath, '/'))
X		return(++cp);
X
X	return(fpath);
X}
X
X/*--------------------------------------------------- memalloc() ------------
X/ allocate specified amount of memory.  If not successful, exit.
X/---------------------------------------------------------------------------*/
Xchar *
Xmemalloc(size)
Xregister int size;
X{
X	register char *cp;
X
X	if (!(cp = malloc((unsigned)size)))
X	{
X		perror(prognam);
X		exit(99);
X	}
X	return(cp);
X}
X
X/*--------------------------------------------------- get_final_id() --------
X/ Get the RCSOWN user id to create files with.  If none found, use user id.
X/---------------------------------------------------------------------------*/
Xvoid
Xget_final_id()
X{
X	FILE *fp;
X
X	if(!rcsown)			/* if there isn't one of these. */
X#ifdef V_RCS
X		rcsown = "rcsfiles";	/* default name. */
X#else
X		rcsown = "sccsfiles";	/* default name. */
X#endif
X
X	if ((fp = fopen (pwdfile, "r")) == NULL)
X	{
X#ifdef DEBUG
X		(void) fprintf(stderr, "setpwent: %s non-existant or unreadable.\n",
X				pwdfile);
X#endif
X		user_id = real_user_id;		/* make sure it's owners id now. */
X		return;		/* couldn't do it. */
X	}
X	while (nextent(fp))		/* while entries in file.. */
X		if (!strcmp(pwdname, rcsown))   /* If name matches. */
X			break;
X	(void) fclose(fp);	/* close the file. */
X	return;			/* found it or not, return. */
X}
X
X/*--------------------------------------------------- nextent() -------------
X/ get one entry from a password file.  Return TRUE if there is one found,
X/ FALSE otherwise.
X/---------------------------------------------------------------------------*/
Xint
Xnextent(fle)
XFILE *fle;	 /* file pointer. */
X{
X	register char *cp, *pwp;
X	char savbuf[200];	/* usually large enough for a password entry. */
X
X	while (strrd(savbuf, (int) (sizeof (savbuf)), fle) != -1)
X	{
X		pwp = pwdname;
X		cp = savbuf;		/* get user name */
X		while (*cp && *cp != ':')
X			*pwp++ = *cp++;
X		*pwp = '\0';		/* terminate name. */
X		for (cp++; *cp && *cp != ':'; cp++)
X			;		/* skip over password. */
X		user_id = atoi(++cp);	/* ok, save this users id number. */
X		return (TRUE);
X	}
X	user_id = real_user_id;		/* make sure it's owners id now. */
X	return (FALSE);
X}
X
X/*----------------------------- End of cio.c -------------------------------*/
END_OF_FILE
  if test 46876 -ne `wc -c <'cio.c'`; then
    echo shar: \"'cio.c'\" unpacked with wrong size!
  fi
  # end of 'cio.c'
fi
if test -f 'cio.man' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'cio.man'\"
else
  echo shar: Extracting \"'cio.man'\" \(10965 characters\)
  sed "s/^X//" >'cio.man' <<'END_OF_FILE'
X.TH CIO 1
X.SH NAME
Xcio - a pair of RCS user interface programs
X.SH SYNOPSIS
X.P 1
Xcii [cii options] [ci options] [filename ...] [dirname ... ]
X.P 1
Xcoo [coo options] [co options] [filename ...] [dirname ... ]
X.SH DESCRIPTION
X.I Cii
Xand
X.I coo
Xare two programs that will provide an interface to the RCS programs
X.I ci
Xand
X.I co.
XThey add:
X.P 1
XRecursively check in/out directory structures
X.P 1
XStore RCS files in a separate directory structure
X.P 1
XDefault stores only ASCII files, enabling program to
Xbe run on directories with both source and binary
X.sp
X.PP
XCommand line options include:
X.TP 5
X.B -A
XAll files.  Forces a "cp" of files that are non-ASCII.
X.TP 5
X.B -H
XInsert RCS header.  If a valid RCS header not found, a template header
Xwill be inserted at the beginning of the file being checked in.
X.TP 5
X.B -N
XNo operation.  Causes
X.I cii
Xor
X.I coo
Xto display the action that would have resulted. Nothing is executed.
X.TP 5
X.B -R
XRecursively walk down directories to check in/out
X.TP 5
X.B -T
XForce title request at start of program (Default only gets title
Xwhen required.)
X.TP 5
X.B -U
XUpdate source directory.  During a check-in, a copy is made to the
Xdirectory structure specified by $RCSSRC.  If $RCSSRC is not defined,
X$HOME will be used instead.
X.TP 5
X.B -V
XVerbose.  Be real talkative about the work being done.
X.TP 5
X.B [all ci/co options]
XPasses all other options  on to
X.I ci
Xor
X.I co.
X.TP 5
X.B filename
XOptional file names.  If not provided, the
X.I cii
Xprogram will take all files in the current directory, or
X.I coo
Xwill take all files saved in the RCS directory.
X(Binary files only if -A was also specified.)
X.TP 5
X.B dirname
XBoth programs will take a directory name as an argument, however: the
X-R option must also be specified for it to work.
X.sp 4
X.SH ENVIRONMENT
X.TP 5
X.B RCSDIR
XIf defined, names a path that will be used to store the RCS files. Default
Xis $HOME/RCS.  Final path is computed by removing $HOME and/or $RCSWORK
Xfrom current path.
X.sp
X.TP 5
X.B RCSWORK
XIf defined, an alternate prefix for current working directory.  Allows
Xhaving multiple directory structures with different prefix's.
X.sp
X.TP 5
X.B HOME
XMust be defined as the users home directory.
X.sp
X.TP 5
X.B RCSSRC
XIf defined and -U flag is specified, during a
X.I cii
Xprocedure, a copy of
Xwhat's being checked in will be made in the directory structure starting
Xat $RCSSRC.  If not specified,
X.I cii
Xuses $HOME instead.
X.sp
X.TP 5
X.B RCSHEAD
XIf -H flag is specified,
X.I cii
Xfinds template header from $RCSHEAD directory.
X.sp
X.SH SAMPLES
X.P 1
XA user with a home directory of /usr/bog, has a directory structure called
Xsample/arix.  He want's to store all of the files in /usr/bog/sample/arix
Xinto the RCS system.  If neither RCSDIR or RCSWORK is defined, the RCS
Xpath defaults to his home directory, followed by RCS, followed by the
Xcurrent path.  So:
X
X.DS
X        Current directory   :  /usr/bog/sample/arix
X        Home directory      :  /usr/bog
X        RCSDIR              :
X        RCSWORK             :
X        RCSSRC              :
X
X        Final RCS storage   :  /usr/bog/RCS/sample/arix
X        Final source storage:  /usr/bog/sample/arix
X.DE
X.P 1
XThe net effect of this command is to create the RCS directory, then to
Xduplicate the directory structure in a defined place.  In this case, since
Xthe RCSDIR was not defined, it defaulted to the users home directory.
X.P 1
XIt is not necessary to build the directories in RCS, the program will
Xbuild all necessary directories (Including the RCS dir, if needed).
X.P 1
XAnother example:  All RCS files should reside in /usr/RCS, so the 
Xabove example turns into:
X
X.DS
X        Current directory   :  /usr/bog/sample/arix
X        Home directory      :  /usr/bog
X        RCSDIR              :  /usr/RCS
X        RCSWORK             :
X        RCSSRC              :
X
X        Final RCS storage   :  /usr/RCS/sample/arix
X        Final source storage:  /usr/bog/sample/arix
X.DE
X.P 1
XHere is an example showing use of the RCSWORK variable.  Some systems
Xmay have more than one person working on file.  In this case, the path
Xnames will have to be similar, but only to a point.  Example, /usr/tog
Xis also working in a directory, called sample/arix.  His system would look
Xlike:
X
X.DS
X        Current directory   :  /usr/tog/sample/arix
X        Home directory      :  /usr/tog
X        RCSDIR              :  /usr/RCS
X        RCSWORK             :
X        RCSSRC              :
X
X        Final RCS storage   :  /usr/RCS/sample/arix
X        Final source storage:  /usr/tog/sample/arix
X.DE
X.P 1
XNote that the RCS dir is the same for him.  Now, let's take a user who
Xhas decided to work somewhere other than his home directory.
X
X.DS
X        Current directory   :  /usr/src/rog/sample/arix
X        Home directory      :  /usr/rog
X        RCSDIR              :  /usr/RCS
X        RCSWORK             :  /usr/src/rog
X        RCSSRC              :
X
X        Final RCS storage   :  /usr/RCS/sample/arix
X        Final source storage:  /usr/rog/sample/arix
X.DE
X.P 1
XIf RCSSRC is specified to keep the current source (very useful to
Xwhen one wants to browse through the current source files) in a
Xdirectory other than his HOME.
X
X.DS
X        Current directory   :  /usr/src/rog/sample/arix
X        Home directory      :  /usr/rog
X        RCSDIR              :  /usr/RCS
X        RCSWORK             :  /usr/src/rog
X        RCSSRC              :  /usr/group
X
X        Final RCS storage   :  /usr/RCS/sample/arix
X        Final source storage:  /usr/group/sample/arix
X.DE
X.P 1
XThe RCSWORK variable was removed from the current path, before the
Xdirectory structure was defined.  Thus, it is possible to be working in
Xjust about anywhere on the system and still use the same directory structure
Xand RCS files.
X
X.P 1
XIf you want to recover a directory (or multiple ones) into a new working
Xdirectory, simply create whatever part of the path you need, set the 
XRCSWORK variable to be the first part of the path, and type "coo [-R]".
X.P 1
XExample:  A new user (/usr/log) has decided to examine the sample/*
Xfiles.  Here are the steps:
X
X.DS
X      Current working directory: /usr/llog
X      mkdir sample
X      chdir sample
X      RCSDIR=/usr/RCS  export RCSDIR
X      RCSWORK=/usr/log  export RCSWORK
X.DE
X
XThis is the final setup:
X
X.DS
X      Current directory:  /usr/log/sample
X      Home directory   :  /usr/llog
X      RCSDIR           :  /usr/RCS
X      RCSWORK          :  /usr/log
X
X      Final RCS storage:  /usr/RCS/sample/arix
X.DE
X
X.SH SECURITY
X.ce 1
XSecure archives
X.P 1
X.I Cii/coo
Xwill also secure archives.  Changing ownership of the cii
Xor coo program to root, and making it suid will allow private archives.
XWith this option, an additional environment variable is searched for:
X"RCSOWN".  If this is not found, the default name "rcsfiles" is used
Xin the following step.
X.P 1
XThe user name is searched for in the /etc/password.  If not found,
Xthe real users UID will be used instead.  In this fashion, the
Xci and co programs will be called with the appropriate uses abilities to
Xcreate directories, and save files.  Only the user who owns the RCS files
Xhas write ability without going through the cii or coo programs.
X.P 1
XThe only condition that allows the program to remain in the root owned
Xmode is to have the rcsfiles have a root account.  Otherwise, it moves out
Xof the root as soon as it's found the user.
X.sp
X.SH OTHER NOTES
X.P 1
X.I Cii
Xwill not unlink files not owned by the user who is checking in the files.
XThis prevents users from deleting files not owned by them, possibly
Xcausing harm. (I.E., checking in the /etc/passwd file.)
X.I coo
Xdoes not run as root, it runs as the real user.  Hence, it is not possible
Xfor a user to overwrite files or directories they normally do not have
Xwrite access to.
X.P 1
X.I Cii/coo
Xonly will allow extraction of code by the users in the same group as the
Xuser who checked the sources in.
X.sp
X.P 1
XWe highly suggest that you exmine the code in the security area if you
Xplan on running it secure.  We've tried it secure, and it seems to work.
XHowever...  We are by no means the "great U*IX hackers", so there is
Xprobably some way to run the program that we missed that can allow others
Xaccess to restricted files.  Check it out! If you *do* find something we
Xmissed, please send us mail.
X.sp
X.SH EDITOR COMMANDS
X.P 1
XThe input editor for the logfile entries and title file entries have
Xtilde (~) command options available.  These are:
X.DS
X        ?  -  Print a help menu.
X        .  -  End of input.
X        !  -  Invoke a user shell.  Note: Shell is invoked
X              as the real user-id; No arguments are passed
X              or allowed.  The ENV variable "SHELL" is
X              searched for, if not present /bin/sh will be
X              invoked.
X        e  -  Edit the message using a default editor.
X              Searches for the ENV variable VISUAL or
X              EDITOR.  If not found, /usr/bin/vi is the
X              default editor.
X        p  -  Print message buffer.  Displays the current
X              contents of the message buffer.
X        r  -  Read in a file.  Requires file name as an
X              argument.  Named file will be appended to
X              current buffer.
X        w  -  Write message to a file.  Requires file name
X              as an argument.  Appends message buffer to
X              file name given.  If file does not exist,
X              creates file.  Does not create directories.
X.DE
X.sp 2
X.SH AUTHORS
XJason P. Winters (jason at grinch.uucp) and Craig J. Kim (cjkim at aeras.uucp)
X.SH FILES
XThe following template files can be created to insert RCS header into
Xsource files during
X.I cii
Xprocess with -H option.  The location for these files is specified by
Xsetting $RCSHEAD environment variable.  If $RCSHEAD is not set, $HOME
Xwill be used instead.  File types are determined by examining the
Xcontent via file(1) program and by file extensions (e.g. .c for C
Xprograms, .mk for makefiles, .1 for nroff, and .s for assembly).
X
X.DS
X        .rcshead       - default template
X        .rcshead.c     - C program template
X        .rcshead.s     - Assembly source template
X        .rcshead.sh    - Shell script template
X        .rcshead.f     - Fortran program template
X        .rcshead.mk    - makefile template
X        .rcshead.h     - C header file template
X        .rcshead.roff  - nroff, troff, man file template
X.DE
X.sp
X.SH DIAGNOSTICS
X.P 1
XLinking cio to ciitest or cootest will cause the program to parse the
Xdirectory paths, and print out the final pathnames.  Nothing else happens.
XUsing the -N option will cause the program to execute as normal, except
Xthat no files or directories will be created.
X.sp
X.SH BUGS
XOn some systems, the Control-D as end of input to a log or title entry
Xcan cause problems with STDIN.  We've added a call to clearerr(), but
Xhave not tested if that fixes it. ( It doesn't break it, so... )
X.P 1
XAny other bugs we would like to hear about.  We might even make a new
Xrelease, if people actually use this.  :)
X.SH AUTHOR
XJason Winters, grinch!jason
END_OF_FILE
  if test 10965 -ne `wc -c <'cio.man'`; then
    echo shar: \"'cio.man'\" unpacked with wrong size!
  fi
  # end of 'cio.man'
fi
echo shar: End of archive.
exit 0
exit 0 # Just in case...
-- 
Please send comp.sources.unix-related mail to rsalz at uunet.uu.net.
Use a domain-based address or give alternate paths, or you may lose out.



More information about the Comp.sources.unix mailing list