4.4BSD/usr/src/contrib/calc-1.26.4/input.c
/*
* Copyright (c) 1993 David I. Bell
* Permission is granted to use, distribute, or modify this source,
* provided that this copyright notice remains intact.
*
* Nested input source file reader.
* For terminal input, this also provides a simple command stack.
*/
#include <ctype.h>
#include <pwd.h>
#include "calc.h"
#include "config.h"
#include "hist.h"
#define MAXSAVE 255 /* number of saved terminal lines */
#define TTYSIZE 100 /* reallocation size for terminal buffers */
#define DEPTH 10 /* maximum depth of input */
#define IS_READ 1 /* reading normally */
#define IS_REREAD 2 /* reread current character */
#define chartoint(ch) ((ch) & 0xff) /* make sure char is not negative */
typedef struct {
short i_state; /* state (read, reread) */
short i_char; /* currently read char */
long i_line; /* line number */
char *i_str; /* current string for input (if not NULL) */
char *i_origstr; /* original string so it can be freed */
char *i_ttystr; /* current character of tty line (or NULL) */
FILE *i_fp; /* current file for input (if not NULL) */
char *i_name; /* file name if known */
} INPUT;
static int stacksize; /* number of elements in command stack */
static int stackindex; /* current index into command stack */
static int cmdsize; /* current max size of terminal buffer */
static int editsize; /* current max size of edit buffer */
static int linesize; /* current max size of input line */
static char *linebuf; /* current input line buffer */
static char *cmdbuf; /* current command line buffer */
static char *editbuf; /* edit buffer */
static char **cmdstack; /* command stack */
static char *prompt; /* current prompt for terminal */
static BOOL noprompt; /* TRUE if should not print prompt */
static int depth; /* current input depth */
static INPUT *cip; /* current input source */
static INPUT inputs[DEPTH]; /* input sources */
static int openfile();
static int ttychar();
extern struct passwd *getpwnam();
/*
* Open an input file by possibly searching through a path list
* and also possibly applying the specified extension. For example:
* opensearchfile("barf", ".:/tmp", ".c") searches in order for the
* files "./barf", "./barf.c", "/tmp/barf", and "/tmp/barf.c".
*
* Returns -1 if all specified files cannot be opened.
*/
opensearchfile(name, pathlist, extension)
char *name; /* file name to be read */
char *pathlist; /* list of colon separated paths (or NULL) */
char *extension; /* extra extension to try (or NULL) */
{
int i;
char *cp;
char path[PATHSIZE+1]; /* name being searched for */
/*
* Don't try the extension if the filename already contains it.
*/
if (extension) {
i = strlen(name) - strlen(extension);
if ((i >= 0) && (strcmp(&name[i], extension) == 0))
extension = NULL;
}
/*
* If the name is absolute, or if there is no path list, then
* make one which just searches for the name straight. Then
* search through the path list for the file, without and with
* the specified extension.
*/
if (name[0] == PATHCHAR ||
name[0] == HOMECHAR ||
(name[0] == DOTCHAR && name[1] == PATHCHAR) ||
pathlist == NULL) {
pathlist = "";
}
pathlist--;
do {
pathlist++;
cp = path;
while (*pathlist && (*pathlist != LISTCHAR))
*cp++ = *pathlist++;
if (cp != path)
*cp++ = PATHCHAR;
strcpy(cp, name);
i = openfile(path);
if ((i < 0) && extension) {
strcat(path, extension);
i = openfile(path);
}
} while ((i < 0) && *pathlist);
return i;
}
/*
* Given a filename with a leading ~, expand it into a home directory for
* that user. This function will malloc the space for the expanded path.
*
* If the path is just ~, or begins with ~/, expand it to the home
* directory of the current user. If the environment variable $HOME
* is known, it will be used, otherwise the password file will be
* consulted.
*
* If the path is just ~username, or ~username/, expand it to the home
* directory of that user by looking it up in the password file.
*
* If the password file must be consulted and the username is not found
* a NULL pointer is returned.
*/
static char *
homeexpand(name)
char *name; /* a filename with a leading ~ */
{
struct passwd *ent; /* password entry */
char *home2; /* fullpath of the home directory */
char *fullpath; /* the malloced expanded path */
char *after; /* after the ~user or ~ */
char username[PATHSIZE+1]; /* extratced username */
/* firewall */
if (name[0] != HOMECHAR)
return NULL;
/*
* obtain the home directory component
*/
switch (name[1]) {
case PATHCHAR: /* ~/... */
case '\0': /* ~ */
home2 = home;
after = name+1;
break;
default: /* ~username or ~username/... */
/* extract the username after the ~ */
after = (char *)strchr(name+2, PATHCHAR);
if (after == NULL) {
/* path is just ~username */
ent = getpwnam(name+1);
if (ent == NULL) {
/* unknown user */
return NULL;
}
/* just malloc the home directory and return it */
fullpath = (char *)malloc(strlen(ent->pw_dir)+1);
strcpy(fullpath, ent->pw_dir);
return fullpath;
}
if (after-name > PATHSIZE+1) {
/* username is too big */
return NULL;
}
strncpy(username, name+1, after-name-1);
username[after-name-1] = '\0';
/* get that user's home directory */
ent = getpwnam(username);
if (ent == NULL) {
/* unknown user */
return NULL;
}
home2 = ent->pw_dir;
break;
}
/*
* build the fullpath given the home directory
*/
fullpath = (char *)malloc(strlen(home2)+strlen(after)+1);
sprintf(fullpath, "%s%s", home2, after);
return fullpath;
}
/*
* f_open - ~-expand a filename and fopen() it
*/
FILE *
f_open(name, mode)
char *name; /* the filename to open */
char *mode; /* the fopen mode to use */
{
FILE *fp; /* open file descriptor */
char *fullname; /* file name with HOMECHAR expansion */
/*
* expand ~ if needed
*/
if (name[0] == HOMECHAR) {
fullname = homeexpand(name);
if (fullname == NULL)
return NULL;
fp = fopen(fullname, mode);
free(fullname);
} else {
fp = fopen(name, mode);
}
return fp;
}
/*
* Setup for reading from a input file.
* Returns -1 if file could not be opened.
*/
static
openfile(name)
char *name; /* file name to be read */
{
FILE *fp; /* open file descriptor */
if (depth >= DEPTH)
return -1;
fp = f_open(name, "r");
if (fp == NULL)
return -1;
cip++;
cip->i_state = IS_READ;
cip->i_char = '\0';
cip->i_str = NULL;
cip->i_origstr = NULL;
cip->i_ttystr = NULL;
cip->i_fp = fp;
cip->i_line = 1;
cip->i_name = (char *)malloc(strlen(name) + 1);
strcpy(cip->i_name, name);
depth++;
return 0;
}
/*
* Open a string for scanning. String is ended by a null character.
* String is copied into local memory so it can be trashed afterwards.
* Returns -1 if cannot open string.
*/
openstring(str)
char *str; /* string to be opened */
{
char *cp; /* copied string */
if ((depth >= DEPTH) || (str == NULL))
return -1;
cp = (char *)malloc(strlen(str) + 1);
if (cp == NULL)
return -1;
strcpy(cp, str);
cip++;
cip->i_state = IS_READ;
cip->i_char = '\0';
cip->i_str = cp;
cip->i_origstr = cp;
cip->i_fp = NULL;
cip->i_name = NULL;
cip->i_ttystr = NULL;
cip->i_line = 1;
depth++;
return 0;
}
/*
* Set to read input from the terminal.
* Returns -1 if there is no more depth for input.
*/
openterminal()
{
if (depth >= DEPTH)
return -1;
if (cmdsize == 0) {
cmdbuf = (char *)malloc(TTYSIZE + 1);
if (cmdbuf == NULL)
return -1;
cmdsize = TTYSIZE;
}
if (editsize == 0) {
editbuf = (char *)malloc(TTYSIZE + 1);
if (editbuf == NULL)
return -1;
editsize = TTYSIZE;
}
if (stacksize == 0) {
cmdstack = (char **) malloc(MAXSAVE * sizeof(char *));
if (cmdstack == NULL)
return -1;
stacksize = MAXSAVE;
for (stackindex = 0; stackindex < MAXSAVE; stackindex++)
cmdstack[stackindex] = NULL;
stackindex = 0;
}
cip++;
cip->i_state = IS_READ;
cip->i_char = '\0';
cip->i_str = NULL;
cip->i_origstr = NULL;
cip->i_ttystr = NULL;
cip->i_fp = NULL;
cip->i_name = NULL;
cip->i_line = 1;
depth++;
return 0;
}
/*
* Close the current input source.
*/
static void
closeinput()
{
if (depth <= 0)
return;
if (cip->i_origstr)
free(cip->i_origstr);
if (cip->i_fp)
fclose(cip->i_fp);
if (cip->i_name)
free(cip->i_name);
cip--;
depth--;
}
/*
* Reset the input sources back to the initial state.
*/
void
resetinput()
{
while (depth > 0)
closeinput();
cip = inputs;
noprompt = FALSE;
}
/*
* Set the prompt for terminal input.
*/
void
setprompt(str)
char *str;
{
prompt = str;
noprompt = FALSE;
}
/*
* Read the next character from the current input source.
* End of file returns newline character and closes current input source,
* except for the last input source, which returns EOF.
*/
int
nextchar()
{
int ch; /* current input character */
if (depth == 0) /* input finished */
return EOF;
if (cip->i_state == IS_REREAD) { /* rereading current char */
ch = cip->i_char;
cip->i_state = IS_READ;
if (ch == '\n')
cip->i_line++;
return ch;
}
if (cip->i_str) { /* from string */
ch = chartoint(*cip->i_str++);
if (ch == '\0')
ch = EOF;
} else if (cip->i_fp) { /* from file */
ch = fgetc(cip->i_fp);
} else { /* from terminal */
ch = ttychar();
}
if (ch == EOF) { /* fix up end of file */
closeinput();
ch = '\n';
if (depth <= 0)
ch = EOF;
}
if (depth > 0)
cip->i_char = (char)ch; /* save for rereads */
if (ch == '\n')
cip->i_line++;
return ch;
}
/*
* Read in the next line of input from the current input source.
* The line is terminated with a null character, and does not contain
* the final newline character. The returned string is only valid
* until the next such call, and so must be copied if necessary.
* Returns NULL on end of file.
*/
char *
nextline()
{
char *cp;
int ch;
int len;
cp = linebuf;
if (linesize == 0) {
cp = (char *)malloc(TTYSIZE + 1);
if (cp == NULL)
error("Cannot allocate line buffer");
linebuf = cp;
linesize = TTYSIZE;
}
len = 0;
for (;;) {
noprompt = TRUE;
ch = nextchar();
noprompt = FALSE;
if (ch == EOF)
return NULL;
if (ch == '\0')
continue;
if (ch == '\n')
break;
if (len >= linesize) {
cp = (char *)realloc(cp, linesize + TTYSIZE + 1);
if (cp == NULL)
error("Cannot realloc line buffer");
linebuf = cp;
linesize += TTYSIZE;
}
cp[len++] = (char)ch;
}
cp[len] = '\0';
return linebuf;
}
/*
* Read the next character from the terminal.
* The routines in the history module are called so that the user
* can use a command history and emacs-like editing of the line.
*/
static int
ttychar()
{
int ch; /* current char */
int len; /* length of current command */
static char charbuf[1000];
/*
* If we have more to read from the saved command line, then do that.
* When we see a newline character, then clear the pointer so we will
* read a new line on the next call.
*/
if (cip->i_ttystr) {
ch = chartoint(*cip->i_ttystr++);
if (ch == '\n')
cip->i_ttystr = NULL;
return ch;
}
/*
* We need another complete line.
*/
abortlevel = 0;
inputwait = TRUE;
len = hist_getline(noprompt ? "" : prompt, charbuf, sizeof(charbuf));
if (len == 0) {
inputwait = FALSE;
return EOF;
}
inputwait = FALSE;
/*
* Handle shell escape if present
*/
if (charbuf[0] == '!') { /* do a shell command */
char *cmd;
cmd = charbuf + 1;
if (*cmd == '\0' || *cmd == '\n')
cmd = shell;
system(cmd);
return '\n';
}
hist_saveline(charbuf, len);
/*
* Return the first character of the line, and set up to
* return the rest of it with later calls.
*/
ch = chartoint(charbuf[0]);
if (ch != '\n')
cip->i_ttystr = charbuf + 1;
return ch;
}
/*
* Return whether or not the input source is the terminal.
*/
BOOL
inputisterminal()
{
return ((depth <= 0) || ((cip->i_str == NULL) && (cip->i_fp == NULL)));
}
/*
* Return the name of the current input file.
* Returns NULL for terminal or strings.
*/
char *
inputname()
{
if (depth <= 0)
return NULL;
return cip->i_name;
}
/*
* Return the current line number.
*/
long
linenumber()
{
if (depth > 0)
return cip->i_line;
return 1;
}
/*
* Restore the next character to be read again on the next nextchar call.
*/
void
reread()
{
if ((depth <= 0) || (cip->i_state == IS_REREAD))
return;
cip->i_state = IS_REREAD;
if (cip->i_char == '\n')
cip->i_line--;
}
/*
* Process all startup files found in the $CALCRC path.
*/
void
runrcfiles()
{
char path[PATHSIZE+1]; /* name being searched for */
char *cp;
char *newcp;
char *p;
int i;
/* execute each file in the list */
for (cp=calcrc, newcp=(char *)strchr(calcrc, LISTCHAR);
cp != NULL && *cp;
cp = newcp,
newcp=(newcp) ? (char *)strchr(newcp+1, LISTCHAR) : NULL) {
/* load file name into the path */
if (newcp == NULL) {
strcpy(path, cp);
} else {
strncpy(path, cp, newcp-cp);
path[newcp-cp] = '\0';
}
/* find the start of the path */
p = (path[0] == ':') ? path+1 : path;
if (p[0] == '\0') {
continue;
}
/* process the current file in the list */
i = openfile(p);
if (i < 0)
continue;
getcommands();
}
}
/* END CODE */