From:	CRDGW2::CRDGW2::MRGATE::"SMTP::CRVAX.SRI.COM::RELAY-INFO-VAX" 20-APR-1991 16:05:00.77
To:	ARISIA::EVERHART
CC:	
Subj:	re: Lookin for VMS version of head

From:	RELAY-INFO-VAX@CRVAX.SRI.COM@SMTP@CRDGW2
To:	Everhart@Arisia@MRGATE

Received:  by crdgw1.ge.com (5.57/GE 1.97)
	 id AA08760; Sat, 20 Apr 91 15:53:08 EDT
Received: From BULLDOG.CS.YALE.EDU ([130.132.1.2]) by CRVAX.SRI.COM with TCP; Sat, 20 APR 91 07:08:44 PDT
Received: from lrw.UUCP by BULLDOG.CS.YALE.EDU via UUCP; Sat, 20 Apr 91 09:50:43 EDT
Message-Id: <9104201350.AA25810@BULLDOG.CS.YALE.EDU>
Received: by lrw.UUCP (DECUS UUCP w/Smail);
          Sat, 20 Apr 91 09:29:06 EDT
Date: Sat, 20 Apr 91 09:29:06 EDT
From: Jerry Leichter <leichter@LRW.COM>
To: INFO-VAX@KL.SRI.COM
Subject: re: Lookin for VMS version of head

	I am looking for an implementation of unix head for VMS.  Anyone know
	where I can get a working version (that doesn't need non-existant
	header files (which is all I've found so far))?

Jeez, head is so trivial a program you could probably write one for yourself
in the time it takes to post a message.

Enclosed below is a simple-minded combined head and tail program.  I define

	$ HEAD :== $<wherever>TAIL +
	$ TAIL :== $<wherever>TAIL +

which looks kind of funny, but works just fine.  (Note that the program, when
used to read the tail of a large file, can be very slow.  Much cleverer and
faster programs to do that have been posted.)
							-- Jerry

/*
 *		t a i l
 *
 * Show tail (or head) of a file.
 *
 *	Module:	ydetail.c
 *		David Elins Q&D version of tail - only reads from front
 *		-n displays n lines from the end
 *		+n displays n lines from the beginning
 *		if n is omitted,zero,or starts with a non-digit,
 *		 it defaults to DEFCOUNT(=10)
 *		 atoi is used to calculate n, so an option such as -9x is
 *		 equivalent to -9
 *
 *	Routines:
 *		showforw()	displays lines from the front of the file
 *		showback()	displays lines from the rear of the file
 *
 *		copyright 1986, David Elins
 *		any usage of this program is allowed as long as you acknow-
 *		ledge the natural superiority of the human race (compared to
 *		what, you might ask)
 *
 * Modification history
 * 1.0	??-???-?? YDE	Creation
 * 1.1	 1-Jan-86 JSL	Modification to DECUSC library standards.
 * 1.2	19-May-86 JSL	Added redirection, usage information.
 */

/*)BUILD
 */

char *documentation[] = {
"Head and tail utility.  Usage:",
"",
"	head [-<n>] [+<m>] [-f] [filespecs]",
"	tail [-<n>] [+<m>] [-f] [filespecs]",
"",
"	-f	flips the filename display switch.  By default, it is ON",
"		if there is more than one filespec, off otherwise.",
"	-<n>	means display the last n lines (default n = 10);",
"	+<m>	means display the first m lines (default m = 10);",
"",
"If both are present, the last (rightmost) is used.  Output is always to",
"stdout.  Input comes from stdin if no filespecs are given, else from the",
"files given.  Wildcards are permitted.",
0 };

#ifdef vms
#include ctype
#include ssdef
#include stsdef
#define EXITOK SS$_NORMAL | STS$M_INHIB_MSG
#define EXITERR SS$_ABORT
#else
#define EXITOK 0
#define EXITERR 1
#endif
#include <stdio.h>
#include <errno.h>

#define DEFCOUNT 10	/* default number of lines to display */
#define MAXLINE 512	/* size allocated for each line */
extern int errno;	/* needed on some systems for perror to work right */

typedef struct LINE {	/* for linked list of lines read */
	struct LINE *next;
	char line[MAXLINE];
}
LINE;

char filename[512];

main(argc, argv)
int argc;       /* how many command line arguments */
char *argv[];   /* command line arguments */
{
    int showforw(), showback();	/* functions to show actual lines */
    int (*showline)()=showback;	/* default direction is backward */
    int showname;	/* should file names be displayed?  - default no */
    int count;		/* how many lines to display */
    int ctr;
    int fcnt;
    int flipname;
    FILE *f;

    argc = getredirection(argc,argv);
    setbuf(stdout, NULL);				/* no buffering */

    flipname = 0;
    count=DEFCOUNT;

    if (argc == 2 && streq(argv[1],"?")) {
	help();
	exit(EXITOK);
    }

    fcnt = argc - 1;
    /*
     * check for options
     */
    for (ctr=1; ctr < argc ; ctr++) {

	switch (argv[ctr][0]) {
	case '+':				/* change direction */
		showline = showforw;
		fcnt--;
		if (!isdigit(argv[ctr][1]) && argv[ctr][1] != '\0') {
			fprintf(stderr,"Illegal forward count \"%s\"\n",
				argv[ctr]);
			usage();
		}
		if (argv[ctr][1] != '\0')
			count = atoi(&argv[ctr][1]);
		argv[ctr] = NULL;
		break;

	case '-':
		fcnt--;
		if (argv[ctr][1] == '\0' || isdigit(argv[ctr][1])) {
			showline = showback;
			if (argv[ctr][1] != '\0')
				count = atoi(&argv[ctr][1]);
			argv[ctr] = NULL;
			break;
		}
		if (tolower(argv[ctr][1]) == 'f') {
			flipname = 1;
			argv[ctr] = NULL;
			break;
		}
		fprintf(stderr,"Unknown option \"%s\"\n",argv[ctr]);
		usage();
	}
    }
    if (count <= 0) {				/* invalid count? */
	fprintf(stderr,"Illegal count %d\n",count);
	usage();
    }

    /*
     * direction and count have been set - time to display file date
     */
    if (fcnt == 0) {				/* no files specified */
	(*showline)(count,stdin);		/* show standard input */
	exit(EXITOK);
    }

    showname = (fcnt > 1) ^ flipname;

    for (ctr = 1; ctr < argc; ctr++) {
	if (argv[ctr] == NULL)
		continue;
	if ((f = fwild(argv[ctr],"r")) == NULL) {
		fprintf(stderr,"Can't open \"%s\" - ",argv[ctr]);
		perror(argv[ctr]);
		continue;
	}
	while (fnext(f) != NULL) {
		if (showname)
			printf("File %s:\n",fgetname(f,filename));
		(*showline)(count,f);
	}
    }

    exit(EXITOK);
}

showforw(count,f)
int count;
FILE *f;
{
    char line[MAXLINE];

    while (count-- > 0) {
	if (fgets(line, sizeof(line),f) != line)
		return (TRUE);	/* error or eof */
	fputs(line,stdout);	/* display line */
    }
    return(TRUE);
}

showback(count,f)
int count;
FILE *f;
{
    LINE *curr, *head, *tail;
    LINE *malloc();	/* pretend malloc allocates LINEs */

    tail = head = malloc(sizeof(LINE)); /* space for first line */
    head->next = head;	/* points to itself */

    /* read lines into a ring buffer of one more element than necessary */
    while (fgets(tail->line,sizeof (tail->line), f) == tail->line) {
	if (--count >= 0) {	/* need to get space for line */
		curr = tail;	/* get it at the end of the ring */
		tail = malloc(sizeof(LINE)); /* space for next line */
		curr->next = tail;
		tail->next = head;
	}
	else {	/* don't need more space */
	    count = -1;		/* don't let count go to far negative */
	    tail = head;	/* prepare to read over oldest line */
	    head = head->next;
	}
    }

    /* display all the nodes in the linked list (possibly none) */
    for (curr = head; curr != tail; ) {
	fputs(curr->line,stdout);	/* display line */
	head = curr;			/* save pointer to current node */
	curr = curr->next;		/* point to next node before */
	free(head);			/* releasing the space */
    }
    free(tail);		/* be nice and free extra node, too */
    return(TRUE);	/* and leave */
}

help()
{	char **p;

	for (p = documentation; *p; p++)
		printf("%s\n",*p);
}

usage()
{	fprintf(stderr,"Usage:\n");
	fprintf(stderr,"head [-<n>] [+<m>] [-f] [filespecs]\n");
	fprintf(stderr,"tail [-<n>] [+<m>] [-f] [filespecs]\n");
	exit(EXITERR);
}

/*
 * getredirection() is intended to aid in porting C programs
 * to VMS (Vax-11 C) which does not support '>' and '<'
 * I/O redirection.  With suitable modification, it may
 * useful for other portability problems as well.
 *
 * Modified, 24-Jan-86 by Jerry Leichter
 *	When creating a new output file, force the maximum record size to
 *	512; otherwise, it ends up as 0 (though the C I/O system won't write
 *	a record longer than 512 bytes anyway) which will cause problems if
 *	the file is later opened for APPEND - if the maximum record size is
 *	0, C will use the length of the longest record written to the file
 *	for its buffer!
 */

#ifdef	vms
#include	<stdio.h>
#include	<errno.h>

int
getredirection(argc, argv)
int		argc;
char		**argv;
/*
 * Process vms redirection arg's.  Exit if any error is seen.
 * If getredirection() processes an argument, it is erased
 * from the vector.  getredirection() returns a new argc value.
 *
 * Warning: do not try to simplify the code for vms.  The code
 * presupposes that getredirection() is called before any data is
 * read from stdin or written to stdout.
 *
 * Normal usage is as follows:
 *
 *	main(argc, argv)
 *	int		argc;
 *	char		*argv[];
 *	{
 *		argc = getredirection(argc, argv);
 *	}
 */
{
	register char		*ap;	/* Argument pointer	*/
	int			i;	/* argv[] index		*/
	int			j;	/* Output index		*/
	int			file;	/* File_descriptor 	*/

	for (j = i = 1; i < argc; i++) {   /* Do all arguments	*/
	    switch (*(ap = argv[i])) {
	    case '<':			/* <file		*/
		if (freopen(++ap, "r", stdin,"shr=put") == NULL) {
		    perror(ap);		/* Can't find file	*/
		    exit(errno);	/* Is a fatal error	*/
		}
		break;

	    case '>':			/* >file or >>file	*/
		if (*++ap == '>') {	/* >>file		*/
		    /*
		     * If the file exists, and is writable by us,
		     * call freopen to append to the file (using the
		     * file's current attributes).  Otherwise, create
		     * a new file with "vanilla" attributes as if
		     * the argument was given as ">filename".
		     * access(name, 2) is TRUE if we can write on
		     * the specified file.
		     */
		    if (access(++ap, 2) == 0) {
			if (freopen(ap, "a", stdout) != NULL)
			    break;	/* Exit case statement	*/
			perror(ap);	/* Error, can't append	*/
			exit(errno);	/* After access test	*/
		    }			/* If file accessable	*/
		}
		/*
		 * On vms, we want to create the file using "standard"
		 * record attributes.  create(...) creates the file
		 * using the caller's default protection mask and
		 * "variable length, implied carriage return"
		 * attributes. dup2() associates the file with stdout.
		 */
		if ((file = creat(ap, 0, "rat=cr", "rfm=var", "mrs=512"))
			== -1
		 || dup2(file, fileno(stdout)) == -1) {
		    perror(ap);		/* Can't create file	*/
		    exit(errno);	/* is a fatal error	*/
		}			/* If '>' creation	*/
		break;			/* Exit case test	*/

	    default:
		argv[j++] = ap;		/* Not a redirector	*/
		break;			/* Exit case test	*/
	    }
	}				/* For all arguments	*/
	argv[j] = NULL;			/* Terminate argv[]	*/
	return (j);			/* Return new argc	*/
}
#else
getredirection(argc, argv)
int		argc;
char		*argv[];
/*
 * Dummy routine.
 */
{
	return (argv[0], argc);
}
#endif

/*
 * Fwild and fnext for vms.  See rmsio.c for more information.
 */

#include	<stdio.h>
#include	<rms.h>
#include	<ssdef.h>
#include	<descrip.h>
#include	<ctype.h>

#define	TRUE		1
#define	FALSE		0
#define	EOS		0

typedef struct rmsstuff {
	int		flag;	/* Flag for nonwildcard calls	*/
	char		*wildmode;
	struct	FAB	fab;
	struct	NAM	nam;
	char		starname[NAM$C_MAXRSS + 1];
	char		filename[NAM$C_MAXRSS + 1];
} RMSSTUFF;

static RMSSTUFF *wilddata[_NFILE];	/* RMS struct for each file	*/

/*
 * rms->flag can take on the following values:
 *	ISWILD		A file with wild-card bytes.
 *	UNWILD		A file without wild-cards, unopened.
 *	UNWILD_OPENED	A file without wild-cards, opened.
 */
#define	ISWILD		0
#define	UNWILD		1
#define	UNWILD_OPENED	2

extern FILE	*cleanup();

FILE *
fwild(filename, mode)
char		*filename;
char		*mode;
/*
 * Do wildcard setup
 */
{
	register FILE		*fd;
	register RMSSTUFF	*r;
	register int		index;

	/*
	 * First get a file to work with.  In the process, make sure the mode
	 * we got passed is valid.  Note that every file we will open from
	 * here on already exists, so an open for write will fail (since the
	 * open will include the fully-expanded file name, including the
	 * version number).  Unfortunately, opening a file on the null device
	 * always succeeds.  So, to simplify matters, we just reject a "w"
	 * mode request right here.  (We could as well let it go and then find
	 * "no matching files" when all later open requests fail.  But this
	 * makes more sense.)
	 */	
	if (*mode == 'w' || (fd = fopen("_nl:", mode)) == NULL) {
		return (NULL);
	}
	/*
	 * Warning: the package depends on fileno(fd) remaining unchanged
	 * after calls to freopen().
	 */
	index = fileno(fd);
	/*
	 * If we've been here before, make sure buffers are released.
	 */
	cleanup(index);
	if ((r = malloc(sizeof (RMSSTUFF))) == NULL
	 || (r->wildmode = malloc(strlen(mode) + 1)) == NULL)
	    return(cleanup(index));
	strcpy(r->wildmode, mode);
	wilddata[index] = r;
	/*
	 * Setup the fab and nam blocks.
	 */
	r->fab = cc$rms_fab;			/* Initialize fab	*/
	r->nam = cc$rms_nam;			/*   and nam blocks	*/
	r->fab.fab$l_nam = &r->nam;		/* fab -> nam		*/
	r->fab.fab$l_fna = filename;		/* Argument filename	*/
	r->fab.fab$b_fns = strlen(filename);	/* filename size	*/
	r->nam.nam$l_esa = r->starname;		/* Expanded file name	*/
	r->nam.nam$b_ess = NAM$C_MAXRSS;	/* ... size		*/
	r->nam.nam$l_rsa = r->filename;		/* Result filename	*/
	r->nam.nam$b_rss = NAM$C_MAXRSS;	/* ... size		*/
	/*
	 * Parse the file name
	 */
	if (sys$parse(&r->fab) != RMS$_NORMAL)
	    return (cleanup(index));
	/*
	 * Success.  Null-terminate expanded file name and set flag to
	 * distinguish between "wild" and "non-wild" filenames.
	 */
	((char *)r->nam.nam$l_esa)[r->nam.nam$b_esl] = EOS;
	r->flag = ((r->nam.nam$l_fnb & NAM$M_WILDCARD) == 0) ? UNWILD : ISWILD;
	return (fd);
}

FILE *
fnext(fd)
FILE		*fd;
/*
 * Open the next valid file. return fd if successful, NULL if finished.
 */
{
	register int		index;
	register RMSSTUFF	*r;
	register int		errorcode;
	FILE			*test;

	index = fileno(fd);
	if ((r = wilddata[index]) == NULL || r->flag == UNWILD_OPENED) {
	    /*
	     * It wasn't ours, or wasn't a wildcard and
	     * has already been processed.
	     */
	    fclose(fd);
	    fd = NULL;
	}
	else if (r->flag == UNWILD) {
	    /*
	     * Not a wildcard file, first time through
	     */
	    fd = freopen(r->starname, r->wildmode, fd);
	    r->flag = UNWILD_OPENED;
	}
	else {
	    /*
	     * Look for the next match -- who says you can't write
	     * obscure structured code?
	     */
	    for (;;) {
		/*
		 * Look 'em up but skip any with
		 * protection violation errors.
		 */
		r->fab.fab$w_ifi = 0;		/* Internal file index	*/
		if ((errorcode = sys$search(&r->fab)) == RMS$_NORMAL) {
		    /*
		     * We have a file.  Open it if we have access rights.
		     * Determining this isn't as simple as it might be because
		     * the access() function checks the wrong thing:  It
		     * ignores ACL's and any special privileges.  So we just
		     * open the file twice.  (We have to check first since if
		     * we fail, freopen will free up our file pointer and we
		     * can't be sure of getting it back.)  Opening the file
		     * twice has one unfortunate side effect - it bumps the
		     * file revision by 2.  That's life.
		     */
		    ((char *)r->nam.nam$l_rsa)[r->nam.nam$b_rsl] = EOS;
		    if ((test = fopen(r->filename, r->wildmode)) == NULL) {
			/*
			 * Couldn't open it; try for another.
			 */
			continue;
		    }
		    else {
			/*
			 * Close up the test channel first (to avoid problems
			 * with sharing; then open the file.  We should
			 * normally be able to open the file.  It this
			 * fails, something is wrong inside the C library.
			 * (More likely, a timing problem occured - between
			 * our test and our actual open, someone deleted the
			 * file, or between our close and re-open, someone
			 * else opened it in an incompatible mode.)  In any
			 * case, we've lost our file descriptor, and there is
			 * no good way to recover; so we just return NULL and
			 * let the caller think that's all there is.
			 */
			fclose(test);
			fd = freopen(r->filename, r->wildmode, fd);
			break;
		    }
		}
		else if (errorcode == RMS$_PRV) {
		    /*
		     * sys$search() found something, but we don't have
		     * privileges to open it.  Look for another.
		     */
		    continue;
		}
		else {
		    /*
		     * Can't find a file.  This should be RMS$_NMF.
		     */
		    fclose(fd);
		    fd = NULL;
		    break;
		}
	    }
	}
	/*
	 * Cleanup if any errors
	 */
	if (fd == NULL) {
	    cleanup(index);
	}
	return (fd);
}

static FILE *
cleanup(index)
register int	index;
/*
 * Empty out any stored information
 */
{
	register RMSSTUFF	*r;

	r = wilddata[index];
	if (r != NULL) {
	    if (r->wildmode != NULL) {
		free(r->wildmode);
	    }
	    free(r);
	    wilddata[index] = NULL;
	}
	return (NULL);
}

