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