From 40e5a5811c6ffce9b0974e93cdd927cbcf60c157 Mon Sep 17 00:00:00 2001 From: Joe Hunkeler Date: Tue, 11 Aug 2015 16:51:37 -0400 Subject: Repatch (from linux) of OSX IRAF --- pkg/cl/history.c | 1159 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1159 insertions(+) create mode 100644 pkg/cl/history.c (limited to 'pkg/cl/history.c') diff --git a/pkg/cl/history.c b/pkg/cl/history.c new file mode 100644 index 00000000..1cdbe050 --- /dev/null +++ b/pkg/cl/history.c @@ -0,0 +1,1159 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#define import_spp +#define import_libc +#define import_stdio +#define import_fset +#define import_ctype +#include + +#include "config.h" +#include "errs.h" +#include "mem.h" +#include "operand.h" +#include "param.h" +#include "task.h" +#include "clmodes.h" +#include "grammar.h" +#include "proto.h" + + +/* + * HISTORY.C -- Routines for character input to the parser (actually, + * the lexical analyser). Includes the history mechanism, the logfile, + * and prompting. + */ + +extern int cldebug; + +#define HISTCHAR '^' /* primary history metacharacter */ +#define FIRSTARG '^' /* first argument macro ("^^") */ +#define LASTARG '$' /* last argument macro ("^$") */ +#define ALLARGS '*' /* all arguments macro ("^*") */ +#define ARGCHARS "$^*" /* argument substitution chars */ +#define MATCH_ANYWHERE '?' /* match string anywhere in cmd */ +#define MATCH_ALL 'g' /* match all occurrences */ +#define NO_EXECUTE ":p" /* print but do not execute command */ +#define MAXCOL 80 /* form width for formatting output */ +#define SZ_LOGBUF 1024 /* putlog buffer size */ + +#define EOS '\0' +#define NOCLOSURE ">>" /* parser needs more input (pprompt) */ +#define MAX_SHOWHIST 800 /* maximum history cmds to show */ + +/* History, command block, yy_getc, logfile database. + */ +char raw_cmdblk[SZ_CMDBLK+1];/* saves raw command for history (for scripts)*/ +char cmdblk[SZ_CMDBLK+1]; /* command block buffer */ +char *op_cmdblk=cmdblk; /* next output line in cmdblk */ +char *ip_cmdblk=cmdblk; /* next input char in cmdblk */ +int cmdblk_line=0; /* line number within cmd block */ +int cmdblk_save=0; /* set if cmdblk filled interactively */ + +char histbuf[SZ_HISTBUF+1]; /* history buffer */ +char *op_hist=histbuf; /* next location in history buffer */ +int histbuf_full=0; /* set when buffer wraps around */ +int share_logfile=SHARELOG; /* share logfile with other processes? */ + +FILE *logfp=NULL; /* file pointer for command logfile */ +int histnum = 0; /* history command block number */ +int history_number; /* the current history record */ + +extern int _lexmodes; /* enable lexical mode switching */ +extern char *ifseen; /* Processing an IF statement? */ + + +/* YY_GETC -- Called by the modified yylex() "input" macro in the lexical + * analysis stage of the parser to get the next character from the input + * stream. When EOF is reached on the stream, add the "bye" command to + * the logfile. + */ +int +yy_getc ( + FILE *fp +) +{ + register char ch; + + while ((ch = *ip_cmdblk++) == EOS) + if (get_command (fp) == EOF) { + if (currentask->t_flags & T_INTERACTIVE) + if (log_commands()) + put_logfile ("bye\n"); + return (EOF); + } + + return (ch); +} + + +/* YY_STARTBLOCK -- Terminate the last command block and start a new one. + * Save old command block in history (if interactive) and in logfile (if + * interactive, logging is enabled, and logflag argument is true). Even + * if logging is enabled, a command will not be logged which aborts or is + * interrupted. + */ +void +yy_startblock ( + int logflag +) +{ + register char *ip; + + if (cldebug) + eprintf ("startblock (%d)\n", logflag); + + /* Log cmdblk only if it was filled by an interactive task. We must + * make the test when the new block is initialized since the write is + * delayed. + */ + if (cmdblk_save) { + /* Do not record commands which consist only of whitespace. + */ + for (ip=cmdblk; isspace (*ip); ip++) + ; + if (*ip != EOS) { + /* Use the raw_cmdblk, saved in get_command(). + */ + put_history (raw_cmdblk); + if (logflag && log_commands()) + put_logfile (raw_cmdblk); + } + } + + if (cldebug) + eprintf ("startblock: ifseen=%d\n", ifseen); + + if (!ifseen) { + ip_cmdblk = op_cmdblk = cmdblk; + *ip_cmdblk = EOS; + } + cmdblk_line = 0; + cmdblk_save = (currentask->t_flags & T_INTERACTIVE); + + /* Mode switching of the lexical analyzer is enabled by this call + * if the CL parameter lexmodes is set. Called between blocks + * entered interactively and also during error recovery. + */ + lexinit(); +} + + +/* CURCMD -- Return a pointer to the command block currently being interpreted. + */ +char * +curcmd (void) +{ + return (cmdblk); +} + + +/* GET_COMMAND -- Get command line from the input stream. If not interactive, + * all we do is read the line into the cmdblk buffer. If called when parsing + * command input to an interactive task, we must output a prompt before + * reading in the command line. The prompt changes depending on whether or + * not the command is the first in a command block (whether or not we have + * closure). After reading the command, we check if it is a history directive + * and process it if so. Otherwise we must still process it to expand any + * history macros. Ignore all blank or comment lines. These are + * any line in which the first non-blank character is a newline or a + * '#'. This will make some things a bit more efficient, but is + * actually to allow the if/else parsing to work properly. + * + * N.B.: We must directly or indirectly set ip_cmdblk so that yy_getc takes + * the next character from the right place. This is either done directly + * or by a call to yy_startblock. + */ +int +get_command ( + FILE *fp +) +{ + register char *ip, *op; + char raw_cmd[SZ_LINE+1]; /* buffer for raw command line */ + char new_cmd[SZ_CMDBLK+1]; /* temporary for processed cmd */ + int execute=1, temp, status; + + + if (!(currentask->t_flags & T_INTERACTIVE) || + parse_state == PARSE_PARAMS) { + + /* Ensure that searches through string terminate. */ + cmdblk[SZ_LINE] = '\0'; + ip_cmdblk = cmdblk; + + while (YES) { + currentask->t_scriptln++; /* noninteractive mode */ + + status = (fgets (cmdblk, SZ_LINE, fp) == NULL ? EOF : OK); + if (status == EOF) { + cmdblk[0] = '\0'; + break; + } + + /* Check if this is a blank line. */ + for (ip = cmdblk; *ip == ' ' || *ip == '\t'; ip++) + ; + if (*ip == '\n' || *ip == '\0') + continue; + + /* Check for the #{ ... #} lexmode toggle sequences. These + * are matched only at the beginning of a line. #{ sets + * command mode on the command input stream and #} clears it. + */ + if (*ip == '#') { + if (ip == cmdblk) { + if (*(ip+1) == '{') { + lex_setcpumode (fp); + lexinit(); + } else if (*(ip+1) == '}') { + lex_clrcpumode (fp); + lexinit(); + } + } + continue; + } + + break; + } + + if (cldebug || echocmds()) + eprintf ("%s", status == EOF ? "bye\n" : cmdblk); + + return (status); + } + + raw_cmd[SZ_LINE] = '\0'; + while (YES) { + /* Prompt the user for a new command if the input buffer is empty. + * The CL prompt clears raw mode in case it is left in effect by a + * program abort. + */ +input_: + if (c_fstati (fileno(fp), F_UNREAD) == 0) { + if (c_fstati ((XINT)STDIN, F_RAW) == YES) + c_fseti ((XINT)STDIN, F_RAW, NO); + if (cmdblk_line == 0) + pprompt (curpack->pk_name); + else + pprompt (NOCLOSURE); + } + + /* Read the next command line. */ + if (fgets (raw_cmd, SZ_LINE, fp) == NULL) + return (EOF); + + /* Check for the #{ ... #} lexmode toggle sequences. These + * are matched only at the beginning of a line. #{ sets + * command mode on the command input stream and #} clears it. + */ + if (*(ip=raw_cmd) == '#') { + if (*(ip+1) == '{') { + lex_setcpumode (fp); + lexinit(); + } else if (*(ip+1) == '}') { + lex_clrcpumode (fp); + lexinit(); + } + } + + /* Skip leading whitespace. */ + for (ip=raw_cmd; *ip == ' ' || *ip == '\t'; ip++) + ; + + /* For interactive comments, make sure we store them in the + * history and the logfile. This is so that users can add + * comments into the logfile interactively. + */ + if (*ip == '#') { + put_history (raw_cmd); + if (log_commands()) + put_logfile (raw_cmd); + } else if (*ip != '\n' && *ip != '\0') { + cmdblk_line++; + break; + } + } + + /* If history directive, transform the directive into an executable + * command block using the history data. Echo the new command as + * if the user had typed it, for verification. + */ + if (*raw_cmd == HISTCHAR) { + /* Use screen style history editing only if the CL parameter + * "ehinit" contains the boolean variable "verify" (or if the + * cmd is "ehistory", below). + */ + if (eh_verify) + execute = edit_history_directive (raw_cmd+1, new_cmd); + else { + execute = process_history_directive (raw_cmd, new_cmd); + fputs (new_cmd, currentask->t_stdout); + } + + } else if (expand_history_macros (raw_cmd, new_cmd)) { + fputs (new_cmd, currentask->t_stdout); + + } else { + static char ehist[] = "ehistory"; + int n; + + for (n=0, ip=raw_cmd, op=ehist; (*ip == *op); ip++, op++) + n++; + if (n > 0 && isspace (*ip)) { + while (isspace (*ip)) + ip++; + execute = edit_history_directive (ip, new_cmd); + } + } + + /* If user deletes entire line go back and get another command. */ + for (ip=new_cmd; isspace (*ip); ip++) + ; + if (*ip == EOS) { + cmdblk_line = 0; + execute = 1; + goto input_; + } + + /* Now move the processed command into the cmdblk buffer. If there + * is not enough storage remaining in the cmdblk buffer, we have to + * break the actual (large) command block up, calling yy_startblock to + * start a new block, but without changing the line number within the + * block. We must not let the history mechanism limit the size of a + * command block. + */ + op_cmdblk = ip_cmdblk - 1; /* back up to EOS */ + if (strlen (new_cmd) > (cmdblk + SZ_CMDBLK - op_cmdblk)) { + temp = cmdblk_line; + yy_startblock (LOG); + cmdblk_line = temp; + } + ip_cmdblk = op = op_cmdblk; + for (ip=new_cmd; (*op++ = *ip++) != EOS; ) + ; + + /* Save the "raw command" here for use in yy_startblock. This is + * to handle the problem of procedure script parsing overwriting + * the raw command in cmdblk. + */ + strcpy (raw_cmdblk, cmdblk); + + if (!execute) + yy_startblock (NOLOG); + + fflush (currentask->t_stdout); + return (OK); +} + + +/* PROCESS_HISTORY_DIRECTIVE -- Transform a history directive into an + * executable command or command block. There are two classes of + * directives: (1) string substitution editing of the last command block, + * and (2) search for an earlier command by some means and return that. + * If ":p" follows a directive, we generate the command and return false + * (no execute) as the function value. Any text which follows the directive + * is appended to the new command block. + */ +int +process_history_directive ( + char *directive, + char *new_command_block +) +{ + register char *ip, *op, *p; + char last_command_block[SZ_CMDBLK+1]; + int execute=1, edit=0; + int record; + char *rindex(); + + ip = directive + 1; /* skip the '^' */ + op = new_command_block; + + /* Chop the newline. */ + if ((p = rindex (ip, '\n')) != NULL) + *p = EOS; + + /* Scan the directive string to determine whether or not we have + * an edit directive. We have an edit directive if there is a second + * (unescaped) history metacharacter in the directive. + */ + for (p=ip, edit=0; *p != EOS; p++) + if (*p == '\\' && *(p+1) != EOS) + p++; + else if (*p == HISTCHAR) { + edit = 1; + break; + } + + /* Directives "^^", "^str1^str2^", and "^str1^str2^g". */ + if (edit) { + /* Get last command and edit it */ + if (get_history (1, last_command_block, SZ_CMDBLK) == ERR) + cl_error (E_UERR, "Nothing in history buffer to edit"); + ip = directive + + stredit (directive, last_command_block, new_command_block); + + /* Directives "^absnum" and "-relnum". */ + } else if ((*ip == '-' && isdigit (*(ip+1))) || isdigit (*ip)) { + if (*ip == '-') + record = -atoi(ip++); + else + record = histnum - atoi(ip) + 1; + if (get_history (record, new_command_block, SZ_CMDBLK) == ERR) + cl_error (E_UERR, "History record not found"); + while (isdigit (*ip)) + ip++; + + /* Directives "^", "^str", and "^?str". */ + } else + ip = directive + search_history (directive, new_command_block); + + /* Check for the ":p" no execute suffix */ + execute = (strncmp (ip, NO_EXECUTE, strlen(NO_EXECUTE)) != 0); + if (!execute) + ip += strlen (NO_EXECUTE); + + /* Append any text remaining in the history directive to the new + * command block, BEFORE the final newline. + */ + op += strlen (new_command_block); + while (isspace (*(op-1))) + --op; + expand_history_macros (ip, op); + + /* Make sure the new command line ends with a newline. */ + while (*op != EOS) + op++; + while (isspace (*(op-1))) + --op; + *op++ = '\n'; + *op = EOS; + + return (execute); +} + + +/* SEARCH_HISTORY -- Search for the occurrence of the given string in the + * history buffer, leaving the corresponding command in the output buffer + * if it matches the pattern. Return the number of directive characters used. + * The "repeat last command" directive "^" is a special case: the null string + * matches anything. + */ +int +search_history ( + char *directive, + char *new_command_block +) +{ + register char *ip, *op, *p; + char pattern[SZ_FNAME]; + int match_only_at_bol=1, record, patlen; + + ip = directive + 1; /* skip the '^' */ + + if (*ip == '\\' && *(ip+1) == MATCH_ANYWHERE) + ip++; + else if (*ip == MATCH_ANYWHERE) { + ip++; + match_only_at_bol = 0; + } + + /* Extract pattern, delimited by whitespace, EOS, ?, or ":p", + * depending on whether we have ?? delimiters. + */ + patlen = strlen (NO_EXECUTE); + for (op=pattern; (*op = *ip) != EOS; op++, ip++) + if (match_only_at_bol) { + if (isspace (*ip)) + break; + else if (strncmp (ip, NO_EXECUTE, patlen) == 0) + break; + } else if (*ip == '\\' && *(ip+1) == MATCH_ANYWHERE) { + *op = *++ip; + } else if (*ip == MATCH_ANYWHERE) { + ip++; + break; + } + *op++ = EOS; + + /* Search backwards in history buffer until command is found + * which matches the pattern. The null pattern matches anything. + */ + patlen = strlen (pattern); + record = 1; + + while (get_history (record++, new_command_block, SZ_CMDBLK) != ERR) { + if (patlen == 0) { + break; + } else if (match_only_at_bol) { + if (strncmp (new_command_block, pattern, patlen) == 0) + break; + } else { + for (p=new_command_block; *p != EOS; p++) { + if (*p == *pattern && strncmp(p,pattern,patlen) == 0) + break; + } + if (*p != EOS) + break; + } + } + + if (strlen (new_command_block) == 0) + cl_error (E_UERR, "Event not found"); + + return (ip - directive); +} + + +/* STREDIT -- Edit string "in_text" according to the editing directive + * string given as the first argument, placing the edited string in the + * buffer "out_text". Return the number of characters used in the + * edit directive string. + * This is actually a general purpose string editor. For the history code, + * the edit directives are "^^", "^str", and "^?str". The directive "^^" + * is actually an edit directive wherein the match and substitute strings + * are both null, causing the last command to be repeated without change. + * The first character in the edit directive is taken to be the edit + * metacharacter (i.e., "^", "/", etc.). + */ +int +stredit ( + char *edit_directive, /* e.g., "^str1^str2^" */ + char *in_text, /* text to be edited */ + char *out_text /* buffer for output text */ +) +{ + register char *ip, *op, *pp; + char metacharacter; + char pattern[SZ_LINE+1], text[SZ_LINE+1]; + int replace_all_occurrences=0; + int patlen, len_directive, nmatches; + + /* Extract pattern and substitution strings. The history metacharacter + * may be included in a string if escaped. Otherwise, we leave + * escape sequences completely alone. + */ + ip = edit_directive; + metacharacter = *ip++; + + for (op=pattern; (*op = *ip) != EOS; ip++, op++) + if (*ip == '\\' && *(ip+1) == metacharacter) + *op = *++ip; + else if (*ip == metacharacter) { + ip++; + break; + } + *op = EOS; + patlen = strlen (pattern); + + /* If the directive is "^^", we do not permit a substitution string + * so that the directive may be used to append text to the previous + * command. We interpret the sequences "^\n" and "^\t" as newline + * and tab, respectively. + */ + if (patlen > 0) { + for (op=text; (*op = *ip) != EOS; ip++, op++) + if ((*ip == metacharacter && *(ip+1) == '\\') && + (*(ip+2) == 'n' || *(ip+2) == 't')) { + ip += 2; + *op = (*ip == 'n') ? '\n' : '\t'; + } else if (*ip == '\\' && *(ip+1) == metacharacter) { + *op = *++ip; + } else if (*op == '\n' || *op == metacharacter) { + ip++; + break; + } + *op = EOS; + if (*ip == MATCH_ALL) { + replace_all_occurrences = 1; + ip++; + } + } else + *text = EOS; + + /* All done processing edit directive; get nchars processed. */ + len_directive = ip - edit_directive; + + + /* Edit the command, unless directive is "^^" (null pattern). */ + nmatches = 0; + + for (ip=in_text, op=out_text; *ip != EOS; ) { + /* Advance to next match */ + for (pp=pattern; (*op = *ip) != EOS; op++, ip++) + if (*ip == *pp && strncmp (ip, pattern, patlen) == 0) { + nmatches++; + break; + } + if (patlen == 0) + break; + else if (nmatches == 0) + cl_error (E_UERR, "No match"); + + /* Copy replacement string, advance input pointer past the + * matched string, if we have a match. + */ + if (*ip == *pp) { + for (pp=text; (*op = *pp++) != EOS; op++) + ; + ip += patlen; + } + + if (!replace_all_occurrences) { + while ((*op = *ip++) != EOS) + op++; + break; + } + } + + *op = EOS; + return (len_directive); +} + + +/* EXPAND_HISTORY_MACROS -- Copy the input string to the output string, + * replacing all occurrences of "^$" by the final argument the last command, + * all occurrences of "^^" by the first argument of the last command, and + * all occurrences of "^*" by the full argument list of the last command. + * If the command block contains more than one line, we assume that the + * argument list spans several lines. If this is not true, the expansion + * will not be what the user wanted (but then they probably screwed up). + * The function returns true if any macros were expanded. + */ +int +expand_history_macros ( + char *in_text, + char *out_text +) +{ + register char *ip, *op, *ap; + char cmdblk[SZ_CMDBLK+1], *argp[100]; + int nargs, nrep, argno, have_arg_strings=0; + char *index(); + + /* Copy the command text. Fetch argument strings from history only + * if a history macro is found. Otherwise the copy is very fast. + */ + for (ip=in_text, op=out_text; (*op = *ip) != EOS; ip++, op++) { + if (*ip == '"') { /* span literal strings */ + while (1) { + *op++ = *ip++; + if (*ip == '"' && *(ip+1) != '"') { + *op = *ip; + break; + } + } + continue; + } else if (*ip == HISTCHAR) { + if (ip > in_text && *(ip-1) == '\\') { + *(--op) = HISTCHAR; /* \^ */ + continue; + } else if (!isdigit(*(ip+1)) && index(ARGCHARS,*(ip+1)) == NULL) + continue; + + /* Parse the argument list of the previous command if have not + * already done so. + */ + if (!have_arg_strings++) { + if (get_history (1, cmdblk, SZ_CMDBLK) == ERR) + cl_error (E_UERR, "Nothing in history buffer"); + nargs = get_arglist (cmdblk, argp); + } + + /* Set up the substitution. + */ + switch (*(ip+1)) { + case FIRSTARG: + argno = 1; + nrep = 1; + break; + case LASTARG: + argno = nargs; + nrep = 1; + break; + case ALLARGS: + argno = 1; + nrep = nargs; + break; + default: + argno = *(ip+1) - '0'; + nrep = 1; + break; + } + + /* Copy the arguments to the output command, overwriting the + * history metacharacter (*op). + */ + while (--nrep >= 0 && argno <= nargs) { + for (ap=argp[argno++]; (*op = *ap++); op++) + ; + if (nrep > 0) + *op++ = ' '; + } + + --op; /* leave pointing at last char output */ + ip++; /* skip the macro type metacharacter */ + } + } + + return (have_arg_strings > 0); +} + + +/* GET_ARGLIST -- Fetch the last command line and return an array of + * pointers to the whitespace delimited argument strings. If parsing a + * full command line, argument "zero" is the task name (the first token), + * and argp[1] the first real argument. The number of arguments + * (excluding the task name) is returned as the function value. + * + * NOTE -- The input argument list is modified (the argp[i] point into it). + * NOTE -- This procedure is used elsewhere in the CL to parse argument lists. + */ +int +get_arglist ( + char *cmdblk, /* buffer to store argument list in */ + char *argp[] /* receives argument pointers */ +) +{ + register char *cp; + register int nargs; + + for (cp=cmdblk, nargs=0; *cp != EOS; ) { + /* Advance to next token; convert newline to EOS. */ + while (*cp == ' ' || *cp == '\t') + cp++; + if (*cp == '\n' || *cp == EOS) { + *cp = EOS; + break; + } + + /* Set argument pointer and bump argument count. */ + argp[nargs++] = cp; + + /* Mark the end of the token. */ + while (*cp && !isspace (*cp)) + cp++; + if (*cp == ' ' || *cp == '\t') + *cp++ = EOS; + } + + return (nargs - 1); +} + + +/* PUT_HISTORY -- Add a new record to the history buffer. Record cannot + * be larger than SZ_CMDBLK, which must be smaller than SZ_HISTBUF. Copy + * chars into histbuf in circular buffer fashion, overwriting old history + * data. EOS delimits records in the history buffer. + */ +void +put_history (char *command) +{ + register char *ip, *op, *otop; + + /* Make sure there is exactly one newline at the end of the command. */ + for (ip = command + strlen(command) - 1; ip >= command; --ip) + if (!isspace (*ip)) + break; + *++ip = '\n'; + *++ip = EOS; + + otop = histbuf + SZ_HISTBUF; + ip = command; + op = op_hist; + + do { + *op++ = *ip; + if (op >= otop) { + op = histbuf; + histbuf_full++; + } + } while (*ip++ != EOS); + + op_hist = op; + histnum++; +} + + +/* GET_HISTORY -- Fetch the indicated command from the history buffer, + * returning OK if found, ERR otherwise. + */ +int +get_history ( + int record, + char *command, + int maxch +) +{ + char *recptr; + char *find_history(); + + if ((recptr = find_history (record)) == NULL) { + *command = EOS; + return (ERR); + } else { + fetch_history (recptr, command, maxch); + return (OK); + } +} + + +/* FETCH_HISTORY -- Extract the command pointed to by the first argument + * from the history buffer into the user buffer (the latter is a nice, + * well behaved linear rather than circular buffer). + */ +void +fetch_history ( + char *recptr, + char *command, + int maxch +) +{ + register char *ip, *op, *itop; + register int n; + + itop = histbuf + SZ_HISTBUF; + ip = recptr; + op = command; + n = ((maxch < SZ_HISTBUF) ? maxch : SZ_HISTBUF) - 1; + + while (--n >= 0 && (*op = *ip++) != EOS) { + *op++; + if (ip >= itop) + ip = histbuf; + } + + *op = EOS; +} + + +/* FIND_HISTORY -- Locate the indicated command record in the history buffer, + * returning a pointer to the first char or NULL. Commands are referenced + * by number, where 1 is the most recent command, 2 the one before that, and + * so on. We are done when we search so far back that we reach the location + * op_hist. To speed up linear searches of the history buffer, we keep track + * of where we are on successive calls, provided the buffer has not been + * written into between calls. We can detect this by saving a copy of + * op_hist in a static variable between calls. + */ +char * +find_history (int record) +{ + register char *ip, *op, *bufptr; + static int current_record = 0; + static char *recptr, *old_ophist = NULL; + + if (histnum == 0 || record <= 0) + return (NULL); + + /* We only search backwards into history: if desired record is + * more recent than the "current record", or if the buffer has + * been written into, reset and search from the beginning. The + * "current record" is the record pointed to by recptr. + */ + if (old_ophist != op_hist || record < current_record) { + current_record = 0; + old_ophist = recptr = op_hist; + } + + ip = recptr; /* start here */ + op = op_hist; /* not found if get here */ + bufptr = histbuf; /* wrap around if get here */ + + /* Search backwards into history for the record, starting from the + * current position (initially record number "0", the next record to + * be filled). Each time through the loop, set recptr for the new + * "current record". + */ + while (current_record < record) { + if (--ip < bufptr) { /* backup to EOS */ + if (!histbuf_full) + return (NULL); + ip = histbuf + SZ_HISTBUF - 1; + } + do { + if (--ip < bufptr) { + /* Initially, before the buffer fill up, there is no EOS + * preceeding the first record. + */ + if (!histbuf_full) + break; + ip = histbuf + SZ_HISTBUF - 1; + } + if (ip == op) + return (NULL); /* cannot find record */ + } while (*ip != EOS); + + /* Advance to first char of next record */ + if (++ip >= histbuf + SZ_HISTBUF) + ip = bufptr; + recptr = ip; + current_record++; + } + history_number = current_record; /* save this globally */ + return (recptr); +} + + +/* SHOW_HISTORY -- Print the contents of the history buffer on the output + * stream, preceeding each command block with a 3 digit command number. + * Show at most min (max_commands, MAX_SHOWHIST) command blocks. + */ +void +show_history ( + FILE *fp, + int max_commands +) +{ + char *recptr[MAX_SHOWHIST]; + char cmdblk[SZ_CMDBLK+1]; + int record; + char *find_history(); + + /* Flush the "history" command so that it shows up in the history. */ + yy_startblock (LOG); + + /* Determine the number of records to show. */ + for (record=0; record < MAX_SHOWHIST; record++) + if ((recptr[record] = find_history (record+1)) == NULL) + break; + if (max_commands > 0) + record = (record < max_commands) ? record : max_commands; + + /* Print the records with the 3 digit record number plus a blank + * on the first line and 4 blanks at the beginning of each successive + * line of the block. + */ + while (record > 0) { + fprintf (fp, "%3d ", (histnum - (--record)) % 1000); + fetch_history (recptr[record], cmdblk, SZ_CMDBLK+1); + print_command (fp, cmdblk, "", " "); + fflush (fp); + } +} + + +/* PPROMPT -- Print prompt as first two chars of prompt string plus "> ", i.e., + * "pk> ". If null prompt string (NOCLOSURE), print the continuation prompt + * ">>> ". Also print, before the prompt, all ltasks in current package + * if menus() are enabled and a new package has been invoked. + */ +void +pprompt ( + register char *string +) +{ + static struct package *lastpack = NULL; + + if (menus() && curpack != lastpack) { + listhelp (curpack, NO); + lastpack = curpack; + } + + printf ("%2.2s", string); + printf ("> "); + fflush (stdout); +} + + +/* PUT_LOGFILE -- Put a command into the logfile, if logging is enabled. + * Otherwise check if the logfile is open and close it, in case user has + * just turned logging off. If the "share_logfile" switch is set the logfile + * is opened and closed each time a record is appended to the file, allowing + * other processes to access the same file. + */ +void +put_logfile (char *command) +{ + FILE *fp; + + if (keeplog()) { + if (logfp == NULL) + if (open_logfile (logfile()) == ERR) + /* Do not abort by calling cl_error(). We could be a + * background job accessing a shared logfile. Also, we + * want to avoid error recursion when logging an error. + */ + return; + + if (share_logfile) { + if ((fp = fopen (logfile(), "a"))) { + print_command (fp, command, "", ""); + fclose (fp); + } + } else + print_command (logfp, command, "", ""); + + } else if (logfp != NULL) + close_logfile (logfile()); +} + + +/* OPEN_LOGFILE -- Open the named command logging file for appending, + * timestamp new session. The logfile grows without bounds unless the + * user deletes it or starts a new one. + */ +int +open_logfile (char *fname) +{ + if (logfp != NULL) + close_logfile (fname); + + if ((logfp = fopen (fname, "a")) == NULL) { + eprintf ("cannot open logfile\n"); + return (ERR); + } + + if (!(firstask->t_flags & T_BATCH)) + fprintf (logfp, "\n# LOGIN %s\n", today()); + + if (share_logfile) + fclose (logfp); + + return (OK); +} + + +/* CLOSE_LOGFILE -- Print termination message and close logfile. + */ +void +close_logfile (char *fname) +{ + register FILE *fp; + + if (logfp != NULL) { + if (share_logfile) { + if ((fp = fopen (fname, "a")) == NULL) { + eprintf ("cannot open logfile\n"); + return; + } + } else + fp = logfp; + + if (!(firstask->t_flags & T_BATCH)) + fprintf (fp, "# Logout %s\n", today()); + + fclose (fp); + logfp = NULL; + } +} + + +/* RESET_LOGFILE -- The name of the logfile has been reset by the user. + * Close and reopen the logfile, but only if share_logfile option is off. + */ +void +reset_logfile (void) +{ + if (!share_logfile) { + close_logfile (""); + open_logfile (logfile()); + } +} + + +/* PRINT_COMMAND -- Print a (possibly multiline) command to the same left + * margin as when it was entered. + */ +void +print_command ( + register FILE *fp, + char *command, + char *marg1, /* margin strings of first and subseq. cmds */ + char *marg2 +) +{ + register char *ip; + + fprintf (fp, marg1); + for (ip=command; *ip != EOS; ip++) { + fputc (*ip, fp); + if (*ip == '\n' && *(ip+1) != EOS) + fprintf (fp, marg2); + } +} + + +/* TODAY -- Get todays date as a string, for datestamping the logfile. + */ +char * +today (void) +{ + static char datebuf[64]; + + c_cnvtime (c_clktime(0L), datebuf, 64); + return (datebuf); +} + + +/* WHAT_RECORD -- Return the record number of the last edited history + */ +int +what_record (void) +{ + return (history_number); +} + + +/* PUTLOG -- Format and write a message to the logfile. This is called by + * the putlog builtin (clputlog() in builtin.c) and in some places in the + * CL (e.g., exec.c). + */ +void +putlog ( + struct task *tp, /* pointer to task or NULL */ + char *usermsg +) +{ + register char *ip, *op, *otop; + register int n; + char msg[SZ_LOGBUF], job[5]; + char *pkg, *tname, *today(); + extern int bkgno; /* job number if bkg job */ + + if (!keeplog()) + return; + + /* If background job, format job number, but only if background + * logging is enabled. + */ + if (firstask->t_flags & T_BATCH) { + if (log_background()) + sprintf (job, "[%d] ", bkgno); + else + return; + } else + job[0] = EOS; + + /* If a valid task pointer is given, get the package and task name. + * Otherwise, assume it's an internal (cl) logging message. + */ + if (tp) { + pkg = tp->t_ltp->lt_pkp->pk_name; + tname = tp->t_ltp->lt_lname; + } else { + pkg = "cl"; + tname = ""; + } + + /* Format the message. Only use time, no day and date. Break long + * messages into several lines. + */ + sprintf (msg, "# %8.8s %s%s%s %s- ", + (today() + 4), pkg, (tp ? "." : ""), tname, job); + otop = &msg[SZ_LOGBUF]; + for (op=msg, n=0; *op && op < otop; op++) + n++; + for (ip=usermsg; (*op++ = *ip++) && op < otop; n++) + if (n + 2 >= MAXCOL) { + *op++ = '\\'; + *op++ = '\n'; + n = 0; + } + *(op-1) = '\n'; + *op = EOS; + + put_logfile (msg); +} -- cgit