home *** CD-ROM | disk | FTP | other *** search
/ ftp.freefriends.org / ftp.freefriends.org.tar / ftp.freefriends.org / arnold / Source / mush.rstevens.tar.gz / mush.tar / loop.c < prev    next >
C/C++ Source or Header  |  1994-07-09  |  36KB  |  1,323 lines

  1. /* loop.c     (c) copyright 1986 (Dan Heller) */
  2.  
  3. /*
  4.  * Here is where the main loop for text mode exists. Also, all the
  5.  * history is kept here and all the command parsing and execution
  6.  * and alias expansion in or out of text/graphics mode is done here.
  7.  */
  8.  
  9. #include "mush.h"
  10. #include "version.h"
  11.  
  12. #ifdef BSD
  13. #include <sys/wait.h>
  14. #else
  15. #ifndef SYSV
  16. #include <wait.h>
  17. #endif /* SYSV */
  18. #endif /* BSD */
  19.  
  20. #define ever (;;)
  21. #define MAXARGS        100
  22. #define isdelimeter(c)    (index(" \t;|", c))
  23.  
  24. char *alias_expand(), *hist_expand(), *reference_hist(), *hist_from_str();
  25. char *calloc();
  26.  
  27. struct history {
  28.     int histno;
  29.     char **argv;
  30.     struct history *prev;
  31.     struct history *next;
  32. };
  33. static struct history *hist_head, *hist_tail;
  34. #define malloc(n)    (struct history *)calloc((unsigned)1,(unsigned)(n))
  35. #define NULL_HIST    (struct history *)0
  36.  
  37. static char *last_aliased;
  38. static int hist_size, print_only;
  39.  
  40. do_loop()
  41. {
  42.     register char *p, **argv;
  43.     char      **last_argv = DUBL_NULL, line[256];
  44.     int         argc, c = (iscurses - 1);
  45. #ifdef CURSES
  46.     int          save_echo_flg = FALSE;
  47. #endif /* CURSES */
  48.  
  49.     /* catch the right signals -- see main.c for other signal catching */
  50.     (void) signal(SIGINT, catch);
  51.     (void) signal(SIGQUIT, catch);
  52.     (void) signal(SIGHUP, catch);
  53.     (void) signal(SIGTERM, catch);
  54.     (void) signal(SIGCHLD,
  55. #ifndef SYSV
  56.                sigchldcatcher
  57. #else /* SYSV */
  58.                SIG_DFL
  59. #endif /* SYSV */
  60.                );
  61.  
  62.     turnoff(glob_flags, IGN_SIGS);
  63.     if (hist_size == 0) /* if user didn't set history in .rc file */
  64.     hist_size = 1;
  65.  
  66.     for ever {
  67.     if (setjmp(jmpbuf)) {
  68.         Debug("jumped back to main loop (%s: %d)\n", __FILE__,__LINE__);
  69. #ifdef CURSES
  70.         if (c > 0) { /* don't pass last command back to curses_command() */
  71.         iscurses = TRUE;
  72.         c = hit_return();
  73.         }
  74. #endif /* CURSES */
  75.     }
  76.     /* If we got back to here, we shouldn't be holding any file locks */
  77.     droplocks();
  78. #ifdef CURSES
  79.     if (iscurses || c > -1) {
  80.         /* if !iscurses, we know that we returned from a curses-based
  81.          * call and we really ARE still in curses. Reset tty modes!
  82.          */
  83.         if (ison(glob_flags, ECHO_FLAG)) {
  84.         turnoff(glob_flags, ECHO_FLAG);
  85.         echo_off();
  86.         save_echo_flg = TRUE;
  87.         }
  88.         if (!iscurses) {
  89.         iscurses = TRUE;
  90.         c = hit_return();
  91.         }
  92.         if (c < 0)
  93.         c = 0;
  94.         if ((c = curses_command(c)) == -1 && save_echo_flg) {
  95.         echo_on();
  96.         turnon(glob_flags, ECHO_FLAG);
  97.         save_echo_flg = FALSE;
  98.         }
  99.         continue;
  100.     }
  101. #endif /* CURSES */
  102.     clear_msg_list(msg_list);
  103.     (void) check_new_mail();
  104.  
  105.     /* print a prompt according to printf like format:
  106.      * (current message, deleted, unread, etc) are found in mail_status.
  107.      */
  108.     mail_status(1);
  109.     if (Getstr(line, sizeof(line), 0) > -1)
  110.         p = line;
  111.     else {
  112.         if (isatty(0) && (p = do_set(set_options, "ignoreeof"))) {
  113.         if (!*p)
  114.             continue;
  115.         else
  116.             p = strcpy(line, p); /* so processing won't destroy var */
  117.         } else {
  118.         putchar('\n');
  119.         (void) mush_quit(0, DUBL_NULL);
  120.         continue; /* quit may return if new mail arrives */
  121.         }
  122.     }
  123.  
  124.     skipspaces(0);
  125.     if (!*p && !(p = do_set(set_options, "newline"))) {
  126.         (void) readmsg(0, DUBL_NULL, msg_list);
  127.         continue;
  128.     }
  129.     if (!*p) /* if newline is set, but no value, then continue */
  130.         continue;
  131.  
  132.     /* upon error, argc = -1 -- still save in history so user can
  133.      * modify syntax error. if !argv, error is too severe.  We pass
  134.      * the last command typed in last_argv for history reference, and
  135.      * get back the current command _as typed_ (unexpanded by aliases
  136.      * or history) in last_argv.
  137.      */
  138.     if (!(argv = make_command(p, &last_argv, &argc)))
  139.         continue;
  140.     /* now save the old argv in a newly created history structure */
  141.     (void) add_history(0, last_argv); /* argc is currently ignored */
  142.  
  143.     if (print_only) {
  144.         print_only = 0;
  145.         free_vec(argv);
  146.     } else if (argc > -1)
  147.         (void) do_command(argc, argv, msg_list);
  148.     }
  149. }
  150.  
  151. /* Add a command to the history list
  152.  */
  153. /*ARGSUSED*/
  154. add_history(un_used, argv)
  155. char **argv;
  156. {
  157.     struct history *new;
  158.  
  159.     if (!(new = malloc(sizeof (struct history))))
  160.     error("can't increment history");
  161.     else {
  162.     new->histno = ++hist_no;
  163.     new->argv = argv;    /* this is the command _as typed_ */
  164.     new->next = NULL_HIST;
  165.     new->prev = hist_head;
  166.     /* if first command, the tail of the list is "new" because
  167.      * nothing is in the list.  If not the first command, the
  168.      * head of the list's "next" pointer points to the new command.
  169.      */
  170.     if (hist_head)
  171.         hist_head->next = new;
  172.     else
  173.         hist_tail = new;
  174.     hist_head = new;
  175.     }
  176.     /*
  177.      * truncate the history list to the size of the history.
  178.      * Free the outdated command (argv) and move the tail closer to front.
  179.      * use a while loop in case the last command reset histsize to "small"
  180.      */
  181.     while (hist_head->histno - hist_tail->histno >= hist_size) {
  182.     hist_tail = hist_tail->next;
  183.     free_vec(hist_tail->prev->argv);
  184.     xfree((char *) (hist_tail->prev));
  185.     hist_tail->prev = NULL_HIST;
  186.     }
  187. }
  188.  
  189. /* make a command from "buf".
  190.  * first, expand history references. make an argv from that and save
  191.  * in last_argv (to be passed back and stored in history). After that,
  192.  * THEN expand aliases. return that argv to be executed as a command.
  193.  */
  194. char **
  195. make_command(start, last_argv, argc)
  196. register char *start, ***last_argv;
  197. int *argc;
  198. {
  199.     register char *p, **tmp;
  200.     char buf[BUFSIZ];
  201.  
  202.     if (!last_argv)
  203.     tmp = DUBL_NULL;
  204.     else
  205.     tmp = *last_argv;
  206.     /* first expand history -- (here's where argc gets set)
  207.      * pass the buffer, the history list to reference if \!* (or whatever)
  208.      * result in static buffer (pointed to by p) -- even if history parsing is
  209.      * ignored, do this to remove \'s behind !'s and verifying matching quotes
  210.      */
  211.     if (!(p = hist_expand(start, tmp, argc)) || Strcpy(buf, p) > sizeof buf)
  212.     return DUBL_NULL;
  213.     /* if history was referenced in the command, echo new command */
  214.     if (*argc)
  215.     puts(buf);
  216.  
  217.     /* argc may == -1; ignore this error for now but catch it later */
  218.     if (!(tmp = mk_argv(buf, argc, 0)))
  219.     return DUBL_NULL;
  220.  
  221.     /* save this as the command typed */
  222.     if (last_argv)
  223.     *last_argv = tmp;
  224.  
  225.     /* expand all aliases (recursively)
  226.      * pass _this_ command (as typed and without aliases) to let aliases
  227.      * with "!*" be able to reference the command line just typed.
  228.      */
  229.     if (alias_stuff(buf, *argc, tmp) == -1)
  230.     return DUBL_NULL;
  231.  
  232.     if (!last_argv)
  233.     free_vec(tmp);
  234.  
  235.     /* with everything expanded, build final argv from new buffer
  236.      * Note that backslashes and quotes still exist. Those are removed
  237.      * because argument final is 1.
  238.      */
  239.     tmp = mk_argv(buf, argc, 1);
  240.     return tmp;
  241. }
  242.  
  243. /* Return values from commands, see check_internal() */
  244. static int last_status;            /* Changes after every command */
  245. static char last_output[MAXMSGS];    /* Changes after SUCCESSFUL command */
  246.  
  247. /*
  248.  * do the command specified by the argument vector, argv.
  249.  * First check to see if argc < 0. If so, someone called this
  250.  * command and they should not have! make_command() will return
  251.  * an argv but it will set argc to -1 if there's a syntax error.
  252.  */
  253. do_command(argc, argv, list)
  254. char **argv, list[];
  255. {
  256.     register char *p;
  257.     char **tmp = argv, *next_cmd = NULL;
  258.     int i, status = 0;
  259.     long do_pipe = ison(glob_flags, DO_PIPE);
  260.  
  261.     if (argc <= 0) {
  262.     turnoff(glob_flags, DO_PIPE);
  263.     return -1;
  264.     }
  265.  
  266.     clear_msg_list(list);
  267.  
  268.     for (i = 0; do_pipe >= 0 && argc; argc--) {
  269.     p = argv[i];
  270.     /* mk_argv inserts a boolean in argv[i][2] for separators */
  271.     if ((!strcmp(p, "|") || !strcmp(p, ";")) && p[2]) {
  272.         if (do_pipe = (*p == '|'))
  273.         turnon(glob_flags, DO_PIPE);
  274.         else if (next_cmd = argv[i+1])
  275.         argv[i+1] = NULL, argc--;
  276.         argv[i] = NULL;
  277.         if ((status = exec_argv(i, argv, list)) <= -1)
  278.         mac_flush();
  279.         else
  280.         list_to_str(list, last_output);
  281.         turnon(glob_flags, IGN_SIGS); /* prevent longjmp */
  282.         /* if piping, then don't call next command if this one failed. */
  283.         if (status <= -1 && do_pipe) {
  284.         print("Broken pipe.\n");
  285.         do_pipe = -1, turnoff(glob_flags, DO_PIPE);
  286.         }
  287.         last_status = status;
  288.         /* if command failed and piping, or command worked and not piping */
  289.         if (do_pipe <= 0)
  290.         status = 0, clear_msg_list(list);
  291.         /* else command worked and piping: set is_pipe */
  292.         else if (!status)
  293.         turnon(glob_flags, IS_PIPE), turnoff(glob_flags, DO_PIPE);
  294.         argv[i] = p;
  295.         argv += (i+1);
  296.         i = 0;
  297.         turnoff(glob_flags, IGN_SIGS);
  298.     } else
  299.         i++;
  300.     }
  301.     if (*argv && do_pipe >= 0) {
  302.     status = exec_argv(i, argv, list);
  303.     turnon(glob_flags, IGN_SIGS);
  304.     if (status < 0) {
  305.         mac_flush();
  306.     } else
  307.         list_to_str(list, last_output);
  308.     last_status = status;
  309.     }
  310.     Debug("freeing: "), print_argv(tmp);
  311.     free_vec(tmp);
  312.     turnoff(glob_flags, DO_PIPE), turnoff(glob_flags, IS_PIPE);
  313.     if (next_cmd) {
  314.     if (tmp = mk_argv(next_cmd, &argc, 1)) {
  315.         turnoff(glob_flags, IGN_SIGS);
  316.         status = do_command(argc, tmp, list);
  317.         turnon(glob_flags, IGN_SIGS);
  318.     } else
  319.         status = argc;
  320.     xfree(next_cmd);
  321.     }
  322.     turnoff(glob_flags, IGN_SIGS);
  323.     return status;
  324. }
  325.  
  326. exec_argv(argc, argv, list)
  327. register char **argv, list[];
  328. {
  329.     register int n;
  330.  
  331.     if (!argv || !*argv || argv[0][0] == '\\' && !argv[0][1]) {
  332.     if (ison(glob_flags, IS_PIPE))
  333.         print("Invalid null command.\n");
  334.     else if (ison(glob_flags, DO_PIPE)) {
  335.         set_msg_bit(list, current_msg);
  336.         return 0;
  337.     }
  338.     return -1;
  339.     } else if (argv[0][0] == '\\') {
  340.     /* Can't change *argv (breaks free_vec),
  341.      *  so shift to remove the backslash
  342.      */
  343.     for (n = 0; argv[0][n]; n++)
  344.         argv[0][n] = argv[0][n+1];
  345.     }
  346.     Debug("executing: "), print_argv(argv);
  347.  
  348.     /* if interrupted during execution of a command, return -1 */
  349.     if (isoff(glob_flags, IGN_SIGS) && setjmp(jmpbuf)) {
  350.     Debug("jumped back to exec_argv (%s: %d)\n", __FILE__, __LINE__);
  351.     return -1;
  352.     }
  353.  
  354.     /* standard commands */
  355.     for (n = 0; cmds[n].command; n++)
  356.     if (!strcmp(argv[0], cmds[n].command))
  357.         return (*cmds[n].func)(argc, argv, list);
  358.  
  359.     /* ucb-Mail compatible commands */
  360.     for (n = 0; ucb_cmds[n].command; n++)
  361.     if (!strcmp(argv[0], ucb_cmds[n].command))
  362.         return (*ucb_cmds[n].func)(argc, argv, list);
  363.  
  364.     /* for hidden, undocumented commands */
  365.     for (n = 0; hidden_cmds[n].command; n++)
  366.     if (!strcmp(argv[0], hidden_cmds[n].command))
  367.         return (*hidden_cmds[n].func)(argc, argv, list);
  368.  
  369.     n = -1; /* default to failure */
  370.     if ((isdigit(**argv) || index("^.*$-`{}", **argv))
  371.             && (n = get_msg_list(argv, list)) != 0) {
  372.     if (n < 0)
  373.         return -1;
  374.     else if (isoff(glob_flags, DO_PIPE))
  375.         for (n = 0; n < msg_cnt; n++)
  376.         if (msg_bit(list, n)) {
  377.             display_msg((current_msg = n), (long)0);
  378.             unset_msg_bit(list, n);
  379.         }
  380.     return 0;
  381.     } else {
  382.     /* get_msg_list will set the current message bit if nothing parsed */
  383.     if (n == 0)
  384.         unset_msg_bit(list, current_msg);
  385.     if (strlen(*argv) == 1 && index("$^.", **argv)) {
  386.         if (!msg_cnt) {
  387.         print("No messages.");
  388.         return -1;
  389.         } else {
  390.         if (**argv != '.')
  391.             current_msg = (**argv == '$') ? msg_cnt-1 : 0;
  392.         set_msg_bit(list, current_msg);
  393.         display_msg(current_msg, (long)0);
  394.         }
  395.         return 0;
  396.     }
  397.     }
  398.  
  399.     if (!istool && do_set(set_options, "unix")) {
  400.     if (ison(glob_flags, IS_PIPE)) {
  401.         return pipe_msg(argc, argv, list);
  402.     } else
  403.         execute(argv);  /* try to execute a unix command */
  404.     return -1; /* doesn't affect messages! */
  405.     }
  406.  
  407.     print("%s: command not found.\n", *argv);
  408.     if (!istool)
  409.     print("type '?' for valid commands, or type `help'\n");
  410.     return -1;
  411. }
  412.  
  413. /* recursively look for aliases on a command line.  aliases may
  414.  * reference other aliases.
  415.  */
  416. alias_stuff(b, argc, Argv)
  417. register char     *b, **Argv;
  418. {
  419.     register char     *p, **argv = DUBL_NULL;
  420.     register int     n = 0, i = 0, Argc;
  421.     static int         loops;
  422.     int         dummy;
  423.  
  424.     if (++loops == 20) {
  425.     print("Alias loop.\n");
  426.     return -1;
  427.     }
  428.     for (Argc = 0; Argc < argc; Argc++) {
  429.     register char *h = Argv[n + ++i];
  430.     register char *p2 = "";
  431.     int sep;
  432.  
  433.     /* we've hit a command separator or the end of the line */
  434.     if (h && strcmp(h, ";") && strcmp(h, "|"))
  435.         continue;
  436.  
  437.     /* create a new argv containing this (possible subset) of argv */
  438.     if (!(argv = (char **)calloc((unsigned)(i+1), sizeof (char *))))
  439.         continue;
  440.     sep = n + i;
  441.     while (i--)
  442.         strdup(argv[i], Argv[n+i]);
  443.  
  444.     if ((!last_aliased || strcmp(last_aliased, argv[0]))
  445.             && (p = alias_expand(argv[0]))) {
  446.         /* if history was referenced, ignore the rest of argv
  447.          * else copy all of argv onto the end of the buffer.
  448.          */
  449.         if (!(p2 = hist_expand(p, argv, &dummy)))
  450.         break;
  451.         if (!dummy)
  452.         (void) argv_to_string(p2+strlen(p2), argv+1);
  453.         if (Strcpy(b, p2) > BUFSIZ) {
  454.         print("Not enough buffer space.\n");
  455.         break;
  456.         }
  457.         /* release old argv and build a new one based on new string */
  458.         free_vec(argv);
  459.         if (!(argv = mk_argv(b, &dummy, 0)))
  460.         break;
  461.         if (alias_stuff(b, dummy, argv) == -1)
  462.         break;
  463.     } else
  464.         b = argv_to_string(b, argv);
  465.     xfree(last_aliased), last_aliased = NULL;
  466.     free_vec(argv);
  467.     b += strlen(b);
  468.     if (h) {
  469.         b += strlen(sprintf(b, " %s ", h));
  470.         while (++Argc < argc && (h = Argv[Argc]))
  471.         if (Argc > sep && strcmp(h, ";"))
  472.             break;
  473.         n = Argc--;
  474.     }
  475.     i = 0;
  476.     }
  477.     xfree(last_aliased), last_aliased = NULL;
  478.     --loops;
  479.     if (Argc < argc) {
  480.     free_vec(argv);
  481.     return -1;
  482.     }
  483.     return 0;
  484. }
  485.  
  486. char *
  487. alias_expand(cmd)
  488. register char *cmd;
  489. {
  490.     register char *p;
  491.     register int x;
  492.  
  493.     if (!(p = do_set(functions, cmd)))
  494.     return NULL;
  495.     last_aliased = savestr(cmd); /* to be freed elsewhere; don't strdup! */
  496.     if (isoff(glob_flags, WARNING))
  497.     return p;
  498.     for (x = 0; cmds[x].command; x++)
  499.     if (!strcmp(cmd, cmds[x].command)) {
  500.         wprint("(real command: \"%s\" aliased to \"%s\")\n", cmd, p);
  501.         return p;
  502.     }
  503.     for (x = 0; ucb_cmds[x].command; x++)
  504.     if (!strcmp(cmd, ucb_cmds[x].command)) {
  505.         wprint("(ucb-command: \"%s\" aliased to \"%s\")\n", cmd, p);
  506.         return p;
  507.     }
  508.     return p;
  509. }
  510.  
  511. static int nonobang;
  512.  
  513. /* expand history references and separate message lists from other tokens */
  514. char *
  515. hist_expand(str, argv, hist_was_referenced)
  516. register char *str, **argv;
  517. register int *hist_was_referenced;
  518. {
  519.     static char   buf[BUFSIZ];
  520.     register int  b = 0, inquotes = 0;
  521.     int       first_space = 0, ignore_bang;
  522.  
  523.     ignore_bang = (ison(glob_flags, IGN_BANG) ||
  524.            do_set(set_options, "ignore_bang"));
  525.     nonobang = !!do_set(set_options, "nonobang");
  526.  
  527.     if (hist_was_referenced)
  528.     *hist_was_referenced = 0;
  529.     while (*str) {
  530.     while (!inquotes && isspace(*str))
  531.         str++;
  532.     do  {
  533.         if (!*str)
  534.         break;
  535.         if (b >= sizeof(buf)-1) {
  536.         print("argument list too long.\n");
  537.         return NULL;
  538.         }
  539.         if ((buf[b] = *str++) == '\'') {
  540.         /* make sure there's a match! */
  541.         inquotes = !inquotes;
  542.         }
  543.         if (!first_space && !inquotes && index("0123456789{}*$^.", buf[b])
  544.                  && b && !index("0123456789{}-^. \t", buf[b-1])) {
  545.         buf[b+1] = buf[b];
  546.         buf[b++] = ' ';
  547.         while ((buf[++b] = *str++) && index("0123456789-,${}.", buf[b]))
  548.             ;
  549.         if (!buf[b])
  550.             str--;
  551.         first_space++;
  552.         }
  553.         /* check for (;) (|) or any other delimiter and separate it from
  554.          * other tokens.
  555.          */
  556.         if (!inquotes && buf[b] != '\0' && isdelimeter(buf[b]) &&
  557.             (b < 0 || buf[b-1] != '\\')) {
  558.         if (!isspace(buf[b]))
  559.             first_space = -1; /* resume msg-list separation */
  560.         if (b && !isspace(buf[b-1]))
  561.             buf[b+1] = buf[b], buf[b++] = ' ';
  562.         b++;
  563.         break;
  564.         }
  565.         /*
  566.          * If double-quotes, just copy byte by byte, char by char,
  567.          *  but do remove backslashes from in front of !s
  568.          */
  569.         if (!inquotes && buf[b] == '"') {
  570.         int B = b;
  571.         while ((buf[++B] = *str++) && buf[B] != '"')
  572.             if (*str == '!' && buf[B] == '\\')
  573.             buf[B] = '!', str++;
  574.         if (buf[B])
  575.             b = B;
  576.         else
  577.             str--;
  578.         b++;
  579.         continue;
  580.         }
  581.         if (buf[b] == '\\') {
  582.         first_space = 1;    /* don't split escaped words */
  583.         if ((buf[++b] = *str) == '!')
  584.             buf[--b] = '!';
  585.         ++str;
  586.         } else if (buf[b] == '!' && *str && *str != '\\' && !isspace(*str)
  587.                && !ignore_bang) {
  588.         char word[BUFSIZ], *s;
  589.         if (!(s = reference_hist(str, word, argv))) {
  590.             if (!nonobang)
  591.             return NULL;
  592.         } else {
  593.             str = s;
  594.             if (hist_was_referenced)
  595.             *hist_was_referenced = 1;
  596.             if (strlen(word) + b >= sizeof buf) {
  597.             print("argument list too long.\n");
  598.             return NULL;
  599.             }
  600.             b += Strcpy(&buf[b], word) - 1;
  601.         }
  602.         }
  603.         b++;
  604.     } while (*str && (!isdelimeter(*str) || str[-1] == '\\'));
  605.     if (!inquotes)
  606.         first_space++, buf[b++] = ' ';
  607.     }
  608.     buf[b] = 0;
  609.     return buf;
  610. }
  611.  
  612. /*
  613.  * expand references to internal variables.  This allows such things
  614.  * as $iscurses, $hdrs_only, etc. to work correctly.
  615.  */
  616. char *
  617. check_internal(str)
  618. register char *str;
  619. {
  620.     int ret_val = -1;
  621.     static char version[80], get_status[4];
  622.  
  623.     if (!strcmp(str, "iscurses"))
  624.     ret_val = (iscurses || ison(glob_flags, PRE_CURSES));
  625.     else if (!strcmp(str, "istool"))
  626.     ret_val = istool;
  627.     else if (!strcmp(str, "hdrs_only"))
  628.     ret_val = (hdrs_only && *hdrs_only);
  629.     else if (!strcmp(str, "is_shell"))
  630.     ret_val = is_shell;
  631.     else if (!strcmp(str, "is_sending"))
  632.     ret_val = (ison(glob_flags, IS_SENDING) != 0);
  633.     else if (!strcmp(str, "redirect"))
  634.     ret_val = (isatty(0) != 0);
  635.     else if (!strcmp(str, "thisfolder"))
  636.     return (mailfile && *mailfile) ? mailfile : NULL;
  637.     else if (!strcmp(str, "status"))
  638.     return sprintf(get_status, "%d", last_status);
  639.     else if (!strcmp(str, "output"))
  640.     return last_output;
  641.     else if (!strcmp(str, "version")) {
  642.     /* Create the version string ONCE, then re-use it. */
  643.     if (!*version)
  644.         (void) sprintf(version, "%s (%d.%s.%d %s)",
  645.               MUSHNAME, RELEASE, REVISION, PATCHLEVEL, RELEASE_DATE);
  646.     return version;
  647.     }
  648.  
  649.     return ret_val > 0 ? "1" : ret_val == 0? "0" : NULL;
  650. }
  651.  
  652. /*
  653.  * Parse and expand a single variable reference.  Variable references
  654.  * begin with a '$' and thereafter look like any of:
  655.  *    $    $$ is the pid of the current process
  656.  *    [%x]    $[%x] expands %x as a hdr_format character ($%x is same)
  657.  *    (%x)    $(%x) expands %x as a prompt format character
  658.  *    name    Value of variable "name" (error if not set)
  659.  *    v:x    Modified expansion; v is any of above, x is any of
  660.  *            h    head of a file pathname
  661.  *            t    tail of a file pathname
  662.  *            l    value converted to lowercase
  663.  *            u    value converted to uppercase
  664.  *             q    quote against further expansion (not yet)
  665.  *              <num>    select the <num>th space-separated field
  666.  *    ?name    Set/unset truth value of "name"
  667.  *    {v}    Separate v (any of above) from surrounding text
  668.  * A variable name may include alphabetics, numbers, or underscores but
  669.  * must begin with an alphabetic or underscore.
  670.  */
  671. varexp(ref)
  672. struct expand *ref;
  673. {
  674.     char *str = ref->orig, c, *p, *var, *end = NULL, *op = NULL;
  675.     int do_bool, do_fmt = 0, expanded = 0;
  676.  
  677.     if (*str == '$') {
  678.     /* Allow a $ all by itself to stand */
  679.     if (!*++str || isspace(*str)) {
  680.         ref->exp = savestr("$");
  681.         ref->rest = str;
  682.         return 1;
  683.     }
  684.     /* Handle $?{name} for backwards compatibility */
  685.     if (do_bool = (*str == '?'))
  686.         str++;
  687.     if (*str == '{')
  688.         if (p = index(str + 1, '}')) {
  689.         var = str + 1;
  690.         end = p;
  691.         } else
  692.         goto bad_var;
  693.     else
  694.         var = str;
  695.     /* Handle $?name and ${?name} (normal cases) */
  696.     if (*var == '?') {
  697.         if (do_bool) /* backwards compatibility clash */
  698.         goto bad_var;
  699.         ++var, do_bool = 1;
  700.     }
  701.     switch (*var) {
  702.         case '$':
  703.         if (str[0] == '{' && str[2] != '}')
  704.             goto bad_var;
  705.         else {
  706.             char buf[16];
  707.             (void) sprintf(buf, "%d", getpid());
  708.             ref->exp = savestr(buf);
  709.             ref->rest = (end ? end : var) + 1;
  710.             return 1;
  711.         }
  712.         when '%':
  713.         for (p = var + 1; *p && !index(" \t\n;|\"'$", *p); p++)
  714.             if (*p == ':') {
  715.             if (!do_bool && !op) {
  716.                 op = p;
  717.                 do_fmt = p - var;
  718.             } else
  719.                 break;
  720.             }
  721.         if (!do_fmt)
  722.             do_fmt = p - var;
  723.         end = p;
  724.         when '[': case '(':  /*)*/
  725.         p = any(var, *var == '(' ? ") \t\n" : "] \t\n");
  726.         if (!p || isspace(*p))
  727.             goto bad_var;
  728.         if (end && p > end)
  729.             goto bad_var;
  730.         else {
  731.             var++;
  732.             do_fmt = p - var;
  733.             if (*++p == ':')
  734.             op = p;
  735.             else
  736.             end = p;
  737.         }
  738.         /* fall through */
  739.         default:
  740.         if (!do_fmt && !isalpha(*var) && *var != '_')
  741.             goto bad_var;
  742.         if (!end)
  743.             end = var + strlen(var);
  744.         for (p = (op ? op : var + do_fmt) + 1; p < end; p++)
  745.             if (!do_bool && !op && *p == ':') {
  746.             op = p;
  747.             } else if (!isalnum(*p) && *p != '_') {
  748.             if (*str == '{') /*}*/
  749.                 goto bad_var;
  750.             end = p;
  751.             break;
  752.             }
  753.         if (op && op > end)
  754.             op = NULL;
  755.     }
  756.     /* replace the end of "var" (end) with a nul,
  757.      * and save char in `c'.  Similarly chop at op.
  758.      */
  759.     c = *end, *end = 0;
  760.     if (op)
  761.         *op++ = 0;
  762.  
  763.     if (!do_fmt && debug > 3)
  764.         printf("expanding (%s) ", var);
  765.  
  766.     /* get the value of the variable. */
  767.     if (do_fmt) {
  768.         char c1 = var[do_fmt];
  769.         var[do_fmt] = 0;
  770.         if (debug > 3)
  771.         printf("expanding (%s) ", var);
  772.         if (/*(*/ ')' == c1)
  773.         p = format_prompt(current_msg, var);
  774.         else
  775.         p = format_hdr(current_msg, var, FALSE) + 9;
  776.         var[do_fmt] = c1;
  777.     } else if (!(p = check_internal(var)))
  778.         p = do_set(set_options, var);
  779.     if (do_bool) {
  780.         ref->exp = savestr((p && (*p || !do_fmt)) ? "1" : "0");
  781.         expanded = 1;
  782.         if (debug > 3)
  783.         printf("--> (%s)\n", p);
  784.     } else if (p) {
  785.         if (debug > 3)
  786.         printf("--> (%s)", p);
  787.         if (op && isdigit(*op)) {
  788.         int varc, ix = atoi(op) - 1;
  789.         char **varv = mk_argv(p, &varc, FALSE);
  790.         /* Ignore non-fatal errors like unmatched quotes */
  791.         if (varv && varc < 0)
  792.             for (varc = 0; varv[varc]; varc++)
  793.             ;
  794.         if (ix < 0 || varc <= ix || !varv)
  795.             ref->exp = savestr("");
  796.         else
  797.             ref->exp = savestr(varv[ix]);
  798.         expanded = 1;
  799.         free_vec(varv);
  800.         } else if (op) {
  801.         char *p2 = rindex(p, '/');
  802.         expanded = (*op == 'h' || *op == 't');
  803.         if (*op == 't' && p2)
  804.             p = p2 + 1;
  805.         else if (*op == 'h' && p2)
  806.             *p2 = 0;
  807.         ref->exp = savestr(p);
  808.         if (*op == 'h' && p2)
  809.             *p2 = '/';
  810.         else if (*op == 'l' || *op == 'u') {
  811.             expanded = 1;
  812.             for (p = ref->exp; *p; p++)
  813.             if (*op == 'u')
  814.                 Upper(*p);
  815.             else
  816.                 Lower(*p);
  817.         }
  818.         if (!expanded) {
  819.             print("Unknown colon modifier :%c.\n", *op);
  820.             xfree(ref->exp);
  821.         } else
  822.             if (debug > 3)
  823.             printf("--> (%s)\n", p);
  824.         } else {
  825.         ref->exp = savestr(p);
  826.         expanded = 1;
  827.         if (debug > 3)
  828.             printf("\n");
  829.         }
  830.     } else {
  831.         print("%s: undefined variable\n", var);
  832.         expanded = 0;
  833.     }
  834.     *end = c; /* replace the null with the old character */
  835.     if (op)
  836.         *--op = ':'; /* Put back the colon */
  837.     ref->rest = end + (*str == '{'); /* } */
  838.     }
  839.     return expanded;
  840. bad_var:
  841.     print("Illegal variable name.\n");
  842.     return 0;
  843. }
  844.  
  845. /*
  846.  * find mush variable references and expand them to their values.
  847.  * variables are preceded by a '$' and cannot be within single
  848.  * quotes.  Only if expansion has been made do we copy buf back into str.
  849.  * We expand only as far as the first unprotected `;' separator in str,
  850.  * to get the right behavior when multiple commands are on one line.
  851.  * RETURN 0 on failure, 1 on success.
  852.  */
  853. variable_expand(str)
  854. register char *str;
  855. {
  856.     register int     b = 0, inquotes = 0;
  857.     char             buf[BUFSIZ], *start = str;
  858.     int             expanded = 0;
  859.  
  860.     while (*str && b < sizeof buf - 1) {
  861.     if (*str == '~' && (str == start || isspace(*(str-1)))) {
  862.         register char *p, *tmp;
  863.         int x = 1;
  864.         /* Is it ever possible to have a user name start with tilde?
  865.          * On the assumption it isn't, recur in case of ~$foo or ~/$foo
  866.          */
  867.         if (str[1] != '~' && variable_expand(&str[1]) == 0)
  868.         return 0;
  869.         if (p = any(str, " \t"))
  870.         *p = 0;
  871.         tmp = getpath(str, &x);
  872.         /* if error, print message and return 0 */
  873.         if (x == -1) {
  874.         wprint("%s: %s\n", str, tmp);
  875.         return 0;
  876.         }
  877.         /* Use strncat instead of strncpy to get \0 terminator */
  878.         buf[b] = 0; /* Just in case */
  879.         b += strlen(strncat(buf + b, tmp, sizeof buf - 1 - b));
  880.         if (p && b < sizeof buf - 1) {
  881.         *p = ' ';
  882.         b += strlen(strncat(buf + b, p, sizeof buf - 1 - b));
  883.         }
  884.         expanded = 1;
  885.         break;
  886.     }
  887.     /* if single-quotes, just copy byte by byte, char by char ... */
  888.     if ((buf[b] = *str++) == '\'' && !inquotes) {
  889.         while ((buf[++b] = *str++) && buf[b] != '\'')
  890.         ;
  891.         if (!buf[b])
  892.         str--;
  893.     } else if (!inquotes && buf[b] == '\\' && *str) {
  894.         buf[++b] = *str++;
  895.         b++;
  896.         continue;
  897.     } else if (buf[b] == '"')
  898.         inquotes = !inquotes;
  899.     /* If $ is eol, continue.  Variables must start with a `$'
  900.      * and continue with {, _, a-z, A-Z or it is not a variable.      }
  901.      */
  902.     if (buf[b] == '$' && *str) {
  903.         struct expand expansion;
  904.         expansion.orig = str - 1;
  905.         if (varexp(&expansion)) {
  906.         b += Strcpy(&buf[b],
  907.                 quoteit(expansion.exp, inquotes? '"' : 0, FALSE));
  908.         xfree(expansion.exp);
  909.         str = expansion.rest;
  910.         expanded = 1;
  911.         } else
  912.         return 0;
  913.     } else if (!inquotes && (buf[b] == ';' || buf[b] == '|')) {
  914.         while ((buf[++b] = *str++) && b < sizeof buf - 2)
  915.         ;
  916.         b++;
  917.         break;
  918.     } else
  919.         b++;
  920.     }
  921.     buf[b] = 0;
  922.     if (expanded) /* if any expansions were done, copy back into orig buf */
  923.     (void) strcpy(start, buf);
  924.     if (debug > 3)
  925.     printf("expanded to: %s\n", start);
  926.     return 1;
  927. }
  928.  
  929. /* make an argv of space delimited character strings out of string "str".
  930.  * place in "argc" the number of args made.  If final is true, then expand
  931.  * variables and file names and remove quotes and backslants according to
  932.  * standard.
  933.  */
  934. char **
  935. mk_argv(str, argc, final)
  936. register char *str;
  937. int *argc;
  938. {
  939.     register char    *s = NULL, *p,*str1;
  940.     register int    tmp, err = 0, unq_sep = 0;
  941.     char        *newargv[MAXARGS], **argv, *p2, c, buf[BUFSIZ];
  942.  
  943.     if((str1=savestr(str)) == NULL)
  944.       return DUBL_NULL;;
  945.     if (debug > 3)
  946.     (void) printf("Working on: %s\n",str1);
  947.     /* If final is true, do variable expansions first */
  948.     if (final) {
  949.     (void) strcpy(buf, str1);
  950.     free(str1);
  951.     str1 = buf;
  952.     if (!variable_expand(str1))
  953.         return DUBL_NULL;
  954.     }
  955.     *argc = 0;
  956.     while (*str1 && *argc < MAXARGS) {
  957.     while (isspace(*str1))
  958.         ++str1;
  959.     /* When we have hit an unquoted `;', final must be true,
  960.      * so we're finished.  Stuff the rest of the string at the
  961.      * end of the argv -- do_command will pass it back later,
  962.      * for further processing -- and break out of the loop.
  963.      * NOTE: *s is not yet valid the first time through this
  964.      * loop, so unq_sep should always be initialized to 0.
  965.      */
  966.     if (unq_sep && s && *s == ';') {
  967.         if (*str1) { /* Don't bother saving a null string */
  968.         newargv[*argc] = savestr(str);
  969.         (*argc)++;
  970.         }
  971.         break;
  972.     }
  973.     if (*str1) {        /* found beginning of a word */
  974.         unq_sep = 0;    /* innocent until proven guilty */
  975.         s = p = str1;
  976.         do  {
  977.         if (p - s >= sizeof buf-1) {
  978.             print("argument list too long.\n");
  979.             free(str1);
  980.             return DUBL_NULL;
  981.         }
  982.         if (*str1 == ';' || *str1 == '|')
  983.             unq_sep = final; /* Mark an unquoted separator */
  984.         if ((*p = *str1++) == '\\') {
  985.             if (final && (*str1 == ';' || *str1 == '|'))
  986.             --p; /* Back up to overwrite the backslash */
  987.             if (*++p = *str1) /* assign and compare to NUL */
  988.             str1++;
  989.             continue;
  990.         }
  991.         if (p2 = index("\"'", *p)) {
  992.             register char c2 = *p2;
  993.             /* you can't escape quotes inside quotes of the same type */
  994.             if (!(p2 = index(str1, c2))) {
  995.             if (final)
  996.                 print("Unmatched %c.\n", c2);
  997.             err++;
  998.             p2 = str1;
  999.             }
  1000.             /* This is the intent of the following loop:
  1001.              *  tmp = (int)(p2 - str1) + 1;
  1002.              *  (void) strncpy(p + !final, str1, tmp);
  1003.              * The strncpy() can't be used directly because
  1004.              * it may be overlapping, which fails sometimes.
  1005.              */
  1006.             /* copy up to and including quote */
  1007.             for (tmp = 0; tmp < (int)(p2 - str1) + 1; tmp++)
  1008.             p[tmp+!final] = str1[tmp];
  1009.             p += tmp - 2 * !!final; /* change final to a boolean */
  1010.             if (*(str1 = p2))
  1011.             str1++;
  1012.         }
  1013.         } while (++p, *str1 && (!isdelimeter(*str1) || str1[-1] == '\\'));
  1014.         if (c = *str1) /* set c = *str, check for null */
  1015.         str1++;
  1016.         *p = 0;
  1017.         if (*s) {
  1018.         /* To differentiate real separators from quoted or
  1019.          * escaped ones, always store 3 chars:
  1020.          *  1) The separator character
  1021.          *  2) A nul (string terminator)
  1022.          *  3) An additional boolean (0 or 1)
  1023.          * The boolean is checked by do_command.  Note that this
  1024.          * applies only to "solitary" separators, i.e. those not
  1025.          * part of a larger word.
  1026.          */
  1027.         if (final && (!strcmp(s, ";") || !strcmp(s, "|"))) {
  1028.             char *sep = savestr("xx"); /* get 3 char slots */
  1029.             sep[0] = *s, sep[1] = '\0', sep[2] = unq_sep;
  1030.             newargv[*argc] = sep;
  1031.         } else
  1032.             newargv[*argc] = savestr(s);
  1033.         (*argc)++;
  1034.         }
  1035.         *p = c;
  1036.     }
  1037.     }
  1038.     if (!*argc){
  1039.         free(str1);
  1040.     return DUBL_NULL;
  1041.       }
  1042.     /* newargv[*argc] = NULL; */
  1043.     if (!(argv = (char **)calloc((unsigned)((*argc)+1), sizeof(char *)))) {
  1044.     perror("mk_argv: calloc");
  1045.     free(str1);
  1046.     return DUBL_NULL;
  1047.     }
  1048.     for (tmp = 0; tmp < *argc; tmp++)
  1049.     argv[tmp] = newargv[tmp];
  1050.     if (err)
  1051.     *argc = -1;
  1052.     else if (debug > 3)
  1053.     (void) printf("Made argv: "), print_argv(argv);
  1054.     free(str1);
  1055.     return argv;
  1056. }
  1057.  
  1058. /*
  1059.  * Report a history parsing error.
  1060.  * Suppress the message if nonobang is true.
  1061.  */
  1062. #define hist_error    if (nonobang) {;} else print
  1063.  
  1064. /*
  1065.  * reference previous history from syntax of str and place result into buf
  1066.  * We know we've got a history reference -- we're passed the string starting
  1067.  * the first char AFTER the '!' (which indicates history reference)
  1068.  */
  1069. char *
  1070. reference_hist(str, buf, hist_ref)
  1071. register char *str, **hist_ref;
  1072. char buf[];
  1073. {
  1074.     int        relative; /* relative from current hist_no */
  1075.     int        old_hist, argstart = 0, lastarg, argend = 0, n = 0;
  1076.     register char  *p, *rb = NULL, **argv = hist_ref;
  1077.     struct history *hist;
  1078.  
  1079.     buf[0] = 0;
  1080.     if (*str == '{')
  1081.     if (!(rb = index(str, '}'))) {   /* { */
  1082.         hist_error("Unmatched '}'");
  1083.         return NULL;
  1084.     } else
  1085.         *rb = 0, ++str;
  1086.     relative = *str == '-';
  1087.     if (index("!:$*", *str)) {
  1088.     old_hist = hist_no;
  1089.     if (*str == '!')
  1090.         str++;
  1091.     } else if (isdigit(*(str + relative)))
  1092.     str = my_atoi(str + relative, &old_hist);
  1093.     else if (!(p = hist_from_str(str, &old_hist))) {
  1094.     if (rb) /* { */
  1095.         *rb = '}';
  1096.     return NULL;
  1097.     } else
  1098.     str = p;
  1099.     if (relative)
  1100.     old_hist = (hist_no - old_hist) + 1;
  1101.     if (old_hist == hist_no) {
  1102.     if (!(argv = hist_ref))
  1103.         hist_error("You haven't done anything yet!\n");
  1104.     } else {
  1105.     if (old_hist <= hist_no-hist_size || old_hist > hist_no ||
  1106.         old_hist <= 0) {
  1107.         if (old_hist <= 0)
  1108.         hist_error("You haven't done that many commands, yet.\n");
  1109.         else
  1110.         hist_error("Event %d %s.\n", old_hist,
  1111.             (old_hist > hist_no)? "hasn't happened yet": "expired");
  1112.         if (rb) /* { */
  1113.         *rb = '}';
  1114.         return NULL;
  1115.     }
  1116.     hist = hist_head;
  1117.     while (hist && hist->histno != old_hist)
  1118.         hist = hist->prev;
  1119.     if (hist)
  1120.         argv = hist->argv;
  1121.     }
  1122.     if (!argv) {
  1123.     if (rb) /* { */
  1124.         *rb = '}';
  1125.     return NULL;
  1126.     }
  1127.     while (argv[argend+1])
  1128.     argend++;
  1129.     lastarg = argend;
  1130.     if (*str && index(":$*-", *str)) {
  1131.     int isrange;
  1132.     if (*str == ':' && isdigit(*++str))
  1133.         str = my_atoi(str, &argstart);
  1134.     if (isrange = (*str == '-'))
  1135.         str++;
  1136.     if (!isdigit(*str)) {
  1137.         if (*str == 'p')
  1138.         str++, print_only = 1;
  1139.         else if (*str == '*') {
  1140.         str++;
  1141.         if (!isrange) {
  1142.             if (argv[0]) {
  1143.             if (argv[1])
  1144.                 argstart = 1;
  1145.             else {
  1146.                 if (rb) /* { */
  1147.                 *rb = '}';
  1148.                 return (rb ? rb + 1 : str);
  1149.             }
  1150.             } else
  1151.             argstart = 0;
  1152.         }
  1153.         } else if (*str == '$') {
  1154.         if (!isrange)
  1155.             argstart = argend;
  1156.         str++;
  1157.         } else if (isrange && argend > argstart)
  1158.         argend--; /* unspecified end of range implies last-1 arg */
  1159.         else 
  1160.         argend = argstart; /* no range specified; use arg given */
  1161.     } else
  1162.         str = my_atoi(str, &argend);
  1163.     }
  1164.     if (argstart > lastarg || argend > lastarg || argstart > argend) {
  1165.     hist_error("Bad argument selector.\n");
  1166.     if (rb) /* { */
  1167.         *rb = '}';
  1168.     return (nonobang ? rb ? rb + 1 : str : NULL);
  1169.     }
  1170.     if (debug > 3)
  1171.     print("history expanding from "), print_argv(argv);
  1172.     while (argstart <= argend) {
  1173.     n += Strcpy(&buf[n], argv[argstart++]);
  1174.     buf[n++] = ' ';
  1175.     }
  1176.     buf[--n] = 0;
  1177.     if (rb) /* { */
  1178.     *rb = '}';
  1179.     return (rb ? rb + 1 : str);
  1180. }
  1181.  
  1182. /* find a history command that contains the string "str"
  1183.  * place that history number in "hist" and return the end of the string
  1184.  * parsed: !?foo (find command with "foo" in it) !?foo?bar (same, but add "bar")
  1185.  * in the second example, return the pointer to "bar"
  1186.  */
  1187. char *
  1188. hist_from_str(str, hist_number)
  1189. register char *str;
  1190. register int *hist_number;
  1191. {
  1192.     register char *p = NULL, c = 0;
  1193.     int       full_search = 0, len, found;
  1194.     char       buf[BUFSIZ];
  1195.     struct history *hist;
  1196. #ifndef REGCMP
  1197.     extern char   *re_comp();
  1198. #else
  1199.     char *rex = NULL;
  1200.     extern char   *regcmp();
  1201. #endif /* REGCMP */
  1202.  
  1203.     /* For !{something}, the {} are stripped in reference_hist() */
  1204.     if (*str == '?') {
  1205.     if (p = index(++str, '?'))
  1206.         c = *p, *p = 0;
  1207.     else
  1208.         p = str + strlen(str);
  1209.     full_search = 1;
  1210.     } else {
  1211.     p = str;
  1212.     while (*p && *p != ':' && !isspace(*p))
  1213.         p++;
  1214.     c = *p, *p = 0;
  1215.     }
  1216.     if (*str) {
  1217. #ifndef REGCMP
  1218.     if (re_comp(str))
  1219. #else
  1220.     if (!(rex = regcmp(str, NULL)))    /* Assign and test */
  1221. #endif /* REGCMP */
  1222.     {
  1223.         if (c)
  1224.         *p = c;
  1225.         return NULL;
  1226.     }
  1227.     } else {
  1228.     *hist_number = hist_no;
  1229.     if (c)
  1230.         *p = c;
  1231.     return (*p == '?' ? p + 1 : p);
  1232.     }
  1233.     len = strlen(str);
  1234.     /* move thru the history in reverse searching for a string match. */
  1235.     for (hist = hist_head; hist; hist = hist->prev) {
  1236.     if (full_search) {
  1237.         (void) argv_to_string(buf, hist->argv);
  1238.         Debug("Checking for (%s) in (#%d: %s)\n", str, hist->histno, buf);
  1239.     }
  1240.     if (!full_search) {
  1241.         (void) strcpy(buf, hist->argv[0]);
  1242.         Debug("Checking for (%s) in (#%d: %*s)\n",
  1243.         str, hist->histno, len, buf);
  1244.         found = !strncmp(buf, str, len);
  1245.     } else
  1246.         found =
  1247. #ifndef REGCMP
  1248.         re_exec(buf)
  1249. #else
  1250.         !!regex(rex, buf, NULL) /* convert to boolean value */
  1251. #endif /* REGCMP */
  1252.                 == 1;
  1253.     if (found) {
  1254.         *hist_number = hist->histno;
  1255.         Debug("Found it in history #%d\n", *hist_number);
  1256.         *p = c;
  1257.         return (*p == '?' ? p + 1 : p);
  1258.     }
  1259.     }
  1260.     hist_error("%s: event not found\n", str);
  1261.     *p = c;
  1262.     return NULL;
  1263. }
  1264.  
  1265. disp_hist(n, argv)  /* argc not used -- use space for the variable, "n" */
  1266. register int n;
  1267. char **argv;
  1268. {
  1269.     register int    list_num = TRUE, num_of_hists = hist_size;
  1270.     register int    reverse = FALSE;
  1271.     struct history    *hist = hist_tail;
  1272.  
  1273.     while (*++argv && *argv[0] == '-') {
  1274.     n = 1;
  1275.     do  switch(argv[0][n]) {
  1276.         case 'h': list_num = FALSE;
  1277.         when 'r': reverse = TRUE; hist = hist_head;
  1278.         otherwise: return help(0, "history", cmd_help);
  1279.         }
  1280.     while (argv[0][++n]);
  1281.     }
  1282.  
  1283.     if (!hist) {
  1284.     print("No history yet.\n");
  1285.     return -1;
  1286.     }
  1287.     if (*argv)
  1288.     if (!isdigit(**argv)) {
  1289.         print("history: badly formed number\n");
  1290.         return -1;
  1291.     } else
  1292.         num_of_hists = atoi(*argv);
  1293.  
  1294.     if (num_of_hists > hist_size || num_of_hists > hist_no)
  1295.     num_of_hists = min(hist_size, hist_no);
  1296.  
  1297.     if (!reverse)
  1298.     while (hist_no - hist->histno >= num_of_hists) {
  1299.         Debug("skipping %d\n", hist->histno);
  1300.         hist = hist->next;
  1301.     }
  1302.  
  1303.     (void) do_pager(NULL, TRUE);
  1304.     for (n = 0; n < num_of_hists && hist; n++) {
  1305.     char buf[256];
  1306.     if (list_num)
  1307.         (void) do_pager(sprintf(buf, "%4.d  ", hist->histno), FALSE);
  1308.     (void) argv_to_string(buf, hist->argv);
  1309.     (void) do_pager(buf, FALSE);
  1310.     if (do_pager("\n", FALSE) == -1)
  1311.         break;
  1312.     hist = (reverse)? hist->prev : hist->next;
  1313.     }
  1314.     (void) do_pager(NULL, FALSE);
  1315.     return 0;
  1316. }
  1317.  
  1318. init_history(newsize)
  1319. {
  1320.     if ((hist_size = newsize) < 1)
  1321.     hist_size = 1;
  1322. }
  1323.