[net.sources] Program to verify vi arguments

dbell@daisy.UUCP (David I. Bell) (11/25/84)

The following program has saved me much frustration when using vi.  I used
to get angry at vi when I mistyped a filename or tried to use a bad "tags"
file argument.  For vi would happily start up, clear the screen, and THEN
tell me that my argument was bogus.  ARRRGH!!  The proper fix is to make vi
quit early on certain errors, but hacking the guts of vi is too icky for me.
I figured that a little program could handle most of the common errors.
And so here it is.  I have an "alias vi tv" command in my .cshrc so that the
program runs without me having to think about it.  Enjoy!

----------------------------- cut here --------------------------------------
/*
 * tv.c: "test vi"
 * Front end to vi to verify that arguments are reasonable before running vi.
 * Checks for file existence and readability.  Checks tags argument on -t
 * option.  Also checks for write access if our program name is tw.
 * 					David I. Bell
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdio.h>

#define	EDITOR	"vi"			/* editor name */
#define	WRITENAME "tw"			/* our name when checking write */

static	char	*tagfile = "tags";	/* tags file name */
static	char	*ttyfile = "/dev/tty";	/* tty device name */
static	int	filesize;		/* size of file being checked */
static	int	testmode;		/* mode to test for opening */
extern	int	errno;			/* error code */
extern	char	*rindex();		/* find last occurance of char */


main(argc, argv)
	register char	**argv;
{
	register char	*cp;		/* pointer to our program name */

	cp = rindex(*argv, '/');
	if (cp) cp++; else cp = *argv;
	if (strcmp(cp, WRITENAME) == 0) testmode = 2;	/* test write */
	*argv = EDITOR;
	testit(argc, argv);		/* check the arguments */
	execvp(*argv, argv);		/* all ok, run vi!! */
	perror(*argv);
	exit(1);
}


/*
 * See if the first file in the argument list is reasonable, or that
 * a tags argument is reasonable.  If not, we exit.
 */
testit(argc, argv)
	register char	**argv;
{
	argv++;
	while ((argc > 1) && ((**argv == '-') || (**argv == '+'))) {
		if ((**argv == '-') && (argv[0][1] == 't')) {
			checktags(argv[1]);	/* check on tags file */
			return;
		}
		argv++;
		argc--;
	}
	if (argc > 1) close(checkfile(*argv, testmode, 1));	/* test file */
}


/*
 * Check a file for its existence, that it is a normal file, and that it
 * is readable (and maybe writeable, as given by mode).  Opens it and
 * returns the file descriptor if it is accessible, and saves the size of
 * the file in the variable filesize for later use.  If create is set, then
 * creating of new files is allowed.  Exits on any error.
 */
checkfile(name, mode, create)
	register char	*name;		/* file name to test */
{
	register int	fd;		/* file descriptor */
	struct	stat	statb;		/* status of file */

	if (stat(name, &statb) < 0) {	/* check to see if file exists */
		if ((errno != ENOENT) || (create == 0)) {
			perror(name);
			exit(1);
		}
		checknewfile(name);	/* check if ok to make new file */
		return(-1);
	}
	if ((statb.st_mode & S_IFMT) != S_IFREG) {	/* must be regular */
		fprintf(stderr, "%s: not a regular file\n", name);
		exit(1);
	}
	filesize = statb.st_size;	/* save size for later use */
	fd = open(name, mode);		/* must be readable, maybe writable */
	if (fd < 0) {
		perror(name);
		exit(1);
	}
	return(fd);			/* return file descriptor */
}


/*
 * Check to see if a new file can be created as desired.  This means that
 * the directory that the file is to be created in is existent and writable.
 * If so, the user is then asked whether or not editing of a new file is ok.
 * Exists if anything is wrong.
 */
checknewfile(name)
	char	*name;			/* file name to check on */
{
	register char	*dirname;	/* directory name to check */
	register char	*slash;		/* pointer to last slash if any */

	dirname = name;
	slash = rindex(dirname, '/');
	if (slash == NULL) dirname = "."; else *slash = '\0';
	if (slash == dirname) dirname = "/";
	if (access(dirname, 2) < 0) {	/* see if directory is writable */
		perror(dirname);
		exit(1);
	}
	if (slash != NULL) *slash = '/';
	fprintf(stderr, "ok to edit new file %s? ", name);
	if (answeryes() == 0) exit(1);
}


/*
 * Return a yes or no answer from the user by reading from the tty device.
 * Returns nonzero if the answer is yes.  A yes answer is any response whose
 * first non-blank character is the letter 'y'.
 */
answeryes()
{
	register int	fd;		/* descriptor for tty */
	register int	n;		/* character count */
	register char	*cp;		/* current character */
	char	buf[200];		/* input line */

	fd = open(ttyfile, 0);
	if (fd < 0) {
		perror(ttyfile);
		exit(1);
	}
	n = read(fd, buf, sizeof(buf) - 1);
	if (n < 0) {
		perror(ttyfile);
		exit(1);
	}
	close(fd);
	buf[n] = '\0';
	for (cp = buf; ((*cp == ' ') || (*cp == '\t')); cp++) ;
	return((*cp == 'y') || (*cp == 'Y'));	/* yes if begins with 'y' */
}


/*
 * Check an argument for a tags file search.  This means we open the tags
 * file and search for the argument in it.  If we cannot find the argument,
 * or if the file name specified by the argument is bad, the program exits.
 */
checktags(str)
	register char	*str;		/* string to find */
{
	register char	*cp;		/* current location in file */
	register char	*endcp;		/* end of file data */
	register int	fd;		/* file descriptor */

	if ((str == NULL) || (*str == '\0')) {
		fprintf(stderr, "missing tags argument\n");
		exit(1);
	}
	fd = checkfile(tagfile, 0, 0);		/* make sure tags file exists */
	cp = (char *) malloc(filesize + 1);	/* get room for file */
	if (cp == NULL) {
		perror("malloc");
		exit(1);
	}
	if (read(fd, cp, filesize) < 0) {	/* read it in */
		perror(tagfile);
		exit(1);
	}
	close(fd);
	endcp = cp + filesize;
	*endcp = '\n';				/* make sure file ends nice */
	while (abbrev(str, cp) == 0) {		/* search for tags line */
		while (*cp++ != '\n') ;
		if (cp >= endcp) {
			fprintf(stderr, "%s: not in tags file\n", str);
			exit(1);
		}
	}
	/* now check file name for goodness */
	while ((*cp != ' ') && (*cp != '\t')) cp++;
	while ((*cp == ' ') || (*cp == '\t')) cp++;
	endcp = cp;
	while ((*endcp!=' ') && (*endcp!='\t') && (*endcp!='\n')) endcp++;
	*endcp = '\0';
	close(checkfile(cp, testmode, 0));
}


/*
 * Check that the first string argument is an abbreviation of the second
 * string argument.  Abbreviation means that the second string contains
 * the whole first string, and that a space or tab immediately follows.
 * Returns nonzero if true.
 */
abbrev(s1, s2)
	register char	*s1;		/* string to test */
	register char	*s2;		/* string to test against */
{
	while (*s1) if (*s1++ != *s2++) return(0);
	return((*s2 == ' ') || (*s2 == '\t'));
}