home *** CD-ROM | disk | FTP | other *** search
- /* Copyright (C) 1996 DJ Delorie, see COPYING.DJ for details */
- /* Copyright (C) 1995 DJ Delorie, see COPYING.DJ for details */
- #include <libc/stubs.h>
- #include <sys/system.h>
- #include <fcntl.h>
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <unistd.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <ctype.h>
- #include <errno.h>
- #include <process.h>
- #include <libc/dosexec.h>
- #include <libc/unconst.h>
- #include <libc/file.h> /* for fileno() */
-
- extern char **environ;
- #define alloca __builtin_alloca
-
- typedef enum {
- REDIR_INPUT,
- REDIR_OUTPUT,
- REDIR_APPEND,
- PIPE,
- SEMICOLON,
- EOL,
- WORD,
- UNMATCHED_QUOTE
- } cmd_sym_t;
-
- static cmd_sym_t get_sym (char *s, char **beg, char **end);
- static char * __unquote (char *to, const char *beg, const char *end);
-
- int __system_flags = __system_redirect
- | __system_handle_null_commands
- | __system_use_shell
- | __system_allow_long_cmds;
-
- static int sys_flags;
-
- static inline int
- emiterror (const char *s, int err)
- {
- write (2, s, strlen (s));
- if (err > 0)
- {
- char *msg = strerror (err);
- size_t msg_len = strlen (msg);
-
- write (2, ": ", 2);
- write (2, msg, msg_len);
- }
- write (2, "\r\n", 2); /* '\r' in case they put it into binary mode */
- fsync (2); /* in case they've redirected stderr */
- return -1;
- }
-
- static char command_com[] = "c:\\command.com";
-
- /* Call the system shell $(SHELL) or $(COMSPEC) with the
- command line "PROG CMDLINE". */
- int
- _shell_command (const char *prog, const char *cmdline)
- {
- char *comspec = getenv ("COMSPEC");
- char *shell = 0;
-
- if (!prog)
- prog = "";
- if (!cmdline)
- cmdline = "";
-
- if (sys_flags & __system_use_shell)
- shell = getenv ("SHELL");
- if (!shell)
- shell = comspec;
- /* Is it worth the hassle to get the boot drive (Int 21h/AX=3305h)
- and look for COMMAND.COM there if COMSPEC fails? */
- if (!shell)
- shell = command_com;
-
- if (!*prog && !*cmdline)
- {
- /* Special case: zero or empty command line means just invoke
- the command interpreter and let the user type ``exit''. */
- return _dos_exec (shell, "", environ);
- }
- else if (_is_dos_shell (shell))
- {
- char *cmd_tail = (char *)alloca (3 + strlen (prog) + 1
- + strlen (cmdline) + 1);
- const char *s = prog;
- char *d = cmd_tail + 3;
-
- strcpy (cmd_tail, "/c ");
- while ((*d = *s++) != 0)
- {
- if (*d == '/')
- *d = '\\';
- d++;
- }
-
- if (*cmdline)
- {
- if (*prog)
- *d++ = ' ';
-
- strcpy (d, cmdline);
- }
-
- /* [4N]DOS.COM can support upto 255 chars per command line.
- They lose that feature here, because there's no simple
- way to pass long command lines to DOS function 4Bh (Exec)
- which `_dos_exec' summons. */
- if (strlen (cmd_tail) > 126)
- {
- errno = E2BIG;
- return emiterror ("Command line too long.", 0);
- }
- else
- return _dos_exec (shell, cmd_tail, environ);
- }
- else
- {
- /* Assume this is a `sh' work-alike which can be invoked like
- this:
- sh file
-
- where `file' holds the entire command line.
-
- (Another possibility is a response file, but that breaks the
- ``here document'' feature of the shell.) */
- FILE *respf;
- int e = errno;
- char *atfile = (char *) alloca (L_tmpnam);
- char *cmd_tail = (char *)alloca (L_tmpnam + 1);
-
- errno = 0;
- respf = fopen (tmpnam (atfile), "wb");
-
- if (respf)
- {
- int retval;
-
- errno = e;
- if (*prog)
- {
- fputs (prog, respf);
- fputc (' ', respf);
- }
- fputs (cmdline, respf);
- fputc ('\n', respf);
- fclose (respf);
- strcpy (cmd_tail, " ");
- strcat (cmd_tail, atfile);
- retval = _dos_exec (shell, cmd_tail, environ);
- remove (atfile);
- return retval;
- }
- else
- return emiterror ("Cannot open script file for $SHELL", errno);
- }
- }
-
- /* If this function finds PROG on the PATH, it breaks the command
- tail into words and calls `spawnve' to invoke PROG with the list
- of words as arguments.
- Otherwise, it calls the shell, on the assumption that it's a
- built-in command, or alias, or something.
-
- (We cannot just pass the command tail as a single argument,
- because this will look to the child as if the entire command line
- was a single quoted argument. In particular, it will effectively
- disable file wildcards expansion by the child.) */
-
- static int
- plainsystem(const char *prog, char *args)
- {
- char found_at[FILENAME_MAX];
- int e = errno;
-
- if (__dosexec_find_on_path (prog, environ, found_at))
- {
- char **pargv, **this_arg;
- int pargc = 2; /* PROG is one, terminating 0 is another */
- char *pcmd = args;
- char *b, *e2;
-
- if (! (sys_flags & __system_allow_long_cmds))
- {
- if (strlen (args) > 126)
- {
- errno = E2BIG;
- return emiterror ("Command line too long.", 0);
- }
-
- return _dos_exec (found_at, args, environ);
- }
-
- /* Pass 1: how many arguments do we have? */
- while (*pcmd)
- {
- /* Only words and the terminating 0 are legal at this point. */
- if (get_sym (pcmd, &b, &e2) == WORD)
- pargc++;
- else if (*b)
- {
- errno = EINVAL;
- return emiterror ("Syntax error.", 0);
- }
-
- pcmd = e2;
- }
-
- /* Pass 2: put program, the arguments and the terminating 0. */
- this_arg = pargv = (char **)alloca (pargc * sizeof (char *));
- *this_arg++ = strcpy ((char *)alloca (strlen (prog) + 1), prog);
- --pargc;
-
- for (pcmd = args; --pargc; pcmd = e2, this_arg++)
- {
- get_sym (pcmd, &b, &e2);
- *this_arg = (char *)alloca (e2 - b + 1);
- strncpy (*this_arg, b, e2 - b);
- (*this_arg)[e2 - b] = '\0';
- }
- *this_arg = 0;
-
- return __spawnve(P_WAIT, found_at, pargv, (char * const *)environ);
- }
- else
- {
- /* PROG is nowhere on the PATH. If it got explicit ".exe",
- ".bat", ".btm" or ".com" extension, return an error. */
- const char *endcmd = 0;
- int i;
-
- for (i = 0; prog[i]; i++)
- {
- if (prog[i] == '.')
- endcmd = prog+i+1;
- if (prog[i] == '\\' || prog[i] == '/')
- endcmd = 0;
- }
- if (endcmd
- && (stricmp (endcmd, "exe") == 0
- || stricmp (endcmd, "com") == 0
- || stricmp (endcmd, "bat") == 0
- || stricmp (endcmd, "btm") == 0))
- {
- errno = ENOENT;
- return -1;
- }
- else
- {
- errno = e; /* don't return errno from failed $PATH search */
- /* Last resort: try the shell. */
- return _shell_command (prog, args);
- }
- }
- }
-
-
- /* ------------------------------------------------------------------------ */
- /* Now follows the overlay that handles redirection. There should be no */
- /* fixed limits in this code. */
-
- /* Check if two strings compare equal upto given length. */
-
- #define CHECK_FOR(s, c) check_for(s, c, sizeof(c) - 1)
-
- static int
- check_for(const char *s, const char *cmd, int cmdl)
- {
- while (cmdl)
- {
- if (*s++ != *cmd++)
- return 0;
- cmdl--;
- }
-
- if (isgraph (*s))
- return 0;
- return 1;
- }
-
- /* This function handles certain dummy or simple commands and
- passes the rest to plainsystem. */
- static int
- system1 (const char *cmd, char *cmdline)
- {
- /* There are certain commands that are no-ops only with arguments,
- or only without them, or both. There are other commands which
- we prefer to emulate (when the emulation is better than the
- original), but only if we're allowed to.
-
- Note that this function does the wrong thing if the command
- line is intended for a *real* shell; the assumption is that
- for these you just set `__system_call_cmdproc' and
- `__system_use_shell' in `__system_flags' and $SHELL in the
- environment, and let the shell do the job. This function is
- meant to improve on COMMAND.COM and its ilk.
-
- The first character in CMDLINE cannot be a whitespace (it was
- removed by `system'), testing for non-empty arguments relies
- on this here. */
- if ((sys_flags & __system_handle_null_commands)
- && (CHECK_FOR (cmd, "rem")
- || CHECK_FOR (cmd, "exit")
- || CHECK_FOR (cmd, "goto")
- || CHECK_FOR (cmd, "shift")
- || ((CHECK_FOR (cmd, "set")
- || CHECK_FOR (cmd, "path")
- || CHECK_FOR (cmd, "prompt")) && *cmdline)))
- return 0;
- else if (CHECK_FOR (cmd, "chdir") || CHECK_FOR (cmd, "cd"))
- {
- if (*cmdline && (sys_flags & __system_ignore_chdir))
- return 0;
- else if (sys_flags & __system_emulate_chdir)
- {
- /* We can `chdir' better, because we know about forward
- slashes, and also change the drive. */
- if (*cmdline)
- return __chdir (cmdline);
- else
- {
- /* COMMAND.COM prints the current directory if given
- no argument to CD. */
- char wd[FILENAME_MAX];
-
- printf ("%s\n", __getcwd(wd, FILENAME_MAX-1)
- ? wd : "Current drive is no longer valid");
- fflush (stdout); /* make sure it's delivered */
- fsync (fileno (stdout));
- return 0;
- }
- }
- }
- #if 0
- else if ((sys_flags & __system_emulate_echo)
- && CHECK_FOR (cmd, "echo"))
- return do_echo (cmdline);
- #endif
- else if (*cmd == 0)
- return emiterror ("Invalid null command.", 0);
-
- return plainsystem (cmd, cmdline);
- }
-
- /* Return a copy of a word between BEG and (excluding) END with all
- quoting characters removed from it. */
-
- static char *
- __unquote (char *to, const char *beg, const char *end)
- {
- const char *s = beg;
- char *d = to;
- int quote = 0;
-
- while (s < end)
- {
- switch (*s)
- {
- case '"':
- case '\'':
- if (!quote)
- quote = *s;
- else if (quote == *s)
- quote = 0;
- s++;
- break;
- case '\\':
- if (s[1] == '"' || s[1] == '\''
- || (s[1] == ';'
- && (__system_flags & __system_allow_multiple_cmds)))
- s++;
- /* Fall-through. */
- default:
- *d++ = *s++;
- break;
- }
- }
-
- *d = 0;
- return to;
- }
-
- /* A poor-man's lexical analyzer for simplified command processing.
-
- It only knows about these:
-
- redirection and pipe symbols
- semi-colon `;' (that possibly ends a command)
- argument quoting rules with quotes and `\'
- whitespace delimiters of words (except in quoted args)
-
- Returns the type of next symbol and pointers to its first and (one
- after) the last characters.
-
- Only `get_sym' and `unquote' should know about quoting rules. */
-
- static cmd_sym_t
- get_sym (char *s, char **beg, char **end)
- {
- int in_a_word = 0;
-
- while (isspace (*s))
- s++;
-
- *beg = s;
-
- do {
- *end = s + 1;
-
- if (in_a_word
- && (!*s || strchr ("<>| \t\n", *s)
- || ((sys_flags & __system_allow_multiple_cmds) && *s == ';')))
- {
- --*end;
- return WORD;
- }
-
- switch (*s)
- {
- case '<':
- return REDIR_INPUT;
- case '>':
- if (**end == '>')
- {
- ++*end;
- return REDIR_APPEND;
- }
- return REDIR_OUTPUT;
- case '|':
- return PIPE;
- case ';':
- if (sys_flags & __system_allow_multiple_cmds)
- return SEMICOLON;
- else
- in_a_word = 1;
- break;
- case '\0':
- --*end;
- return EOL;
- case '\\':
- if (s[1] == '"' || s[1] == '\''
- || (s[1] == ';' && (sys_flags & __system_allow_multiple_cmds)))
- s++;
- in_a_word = 1;
- break;
- case '\'':
- case '"':
- {
- char quote = *s++;
-
- while (*s && *s != quote)
- {
- if (*s++ == '\\' && (*s == '"' || *s == '\''))
- s++;
- }
- *end = s;
- if (!*s)
- return UNMATCHED_QUOTE;
- in_a_word = 1;
- break;
- }
- default:
- in_a_word = 1;
- break;
- }
-
- s++;
-
- } while (1);
- }
-
- /* This function handles redirection and passes the rest to system1
- or to the shell. */
- int
- system (const char *cmdline)
- {
- /* Set the feature bits for this run: either from the
- environment or from global variable. */
- const char *envflags = getenv ("DJSYSFLAGS");
- const char *comspec = getenv ("COMSPEC");
- const char *shell = 0;
-
- sys_flags = __system_flags;
- if (envflags && *envflags)
- {
- char *stop;
- long flags = strtol (envflags, &stop, 0);
-
- if (*stop == '\0')
- sys_flags = flags;
- }
-
- if (sys_flags & __system_use_shell)
- shell = getenv ("SHELL");
- if (!shell)
- shell = comspec;
-
- /* Special case: NULL means just exec the command interpreter. */
- if (cmdline == 0)
- cmdline = "";
-
- /* Strip initial spaces (so that if the command is empty, we
- know it right here). */
- while (isspace(*cmdline))
- cmdline++;
-
- /* Call the shell if:
-
- the command line is empty
- or
- they want to always do it via command processor
- or
- $SHELL or $COMSPEC point to a unixy shell */
- if (!*cmdline
- || (sys_flags & __system_call_cmdproc)
- || (!(sys_flags & __system_emulate_command) && _is_unixy_shell (shell)))
- return _shell_command ("", cmdline);
- else
- {
- char *f_in = 0, *f_out = 0;
- int rm_in = 0, rm_out = 0;
- /* Assigned to silence -Wall */
- int h_in = 0, h_inbak = 0, h_out = 0, h_outbak = 0;
- char *s, *t, *u, *v, *tmp, *cmdstart;
- int result = 0, done = 0;
- int append_out = 0;
- cmd_sym_t token;
- char *prog;
-
- tmp = alloca(L_tmpnam);
- prog = alloca (L_tmpnam);
-
- s = strcpy (alloca (strlen (cmdline) + 1), cmdline);
- while (!done && result >= 0)
- {
- char **fp = &f_in;
- /* Assignements pacify -Wall */
- int hin_err = 0, hbak_err = 0, hout_err = 0;
- int needcmd = 1;
- int again;
-
- if (rm_in)
- remove (f_in);
- f_in = f_out; /* Piping. */
- rm_in = rm_out;
- f_out = 0;
- rm_out = 0;
- append_out = 0;
-
- cmdstart = s;
-
- do {
- again = 0;
- token = get_sym (s, &t, &u); /* get next symbol */
-
- /* Weed out extra whitespace, leaving only a single
- whitespace character between any two tokens.
- This way, if we eventually pass the command to a
- shell, it won't fail due to length > 126 chars
- unless it really *is* that long. */
-
- if (s == cmdstart)
- v = s; /* don't need blank at beginning of cmdline */
- else
- v = s + 1;
- if (t > v)
- {
- strcpy (v, t);
- u -= t - v;
- t = v;
- }
-
- switch (token)
- {
- case WORD:
- /* First word we see is the program to run. */
- if (needcmd)
- {
- __unquote (prog, t, u); /* unquote and copy to prog */
- /* We can't grok commands in parentheses, so assume they
- use a shell that knows about these, like 4DOS or `sh'.
-
- FIXME: if the parenthesized group is NOT the first command
- in a pipe, the commands that preceed it will be run twice. */
- if (prog[0] == '(')
- return _shell_command ("", cmdline);
- strcpy (s, u); /* remove program name from cmdline */
- needcmd = 0;
- }
- else
- s = u;
- again = 1;
- break;
- case REDIR_INPUT:
- case REDIR_OUTPUT:
- case REDIR_APPEND:
- if (!(sys_flags & __system_redirect))
- return _shell_command ("", cmdline);
- if (token == REDIR_INPUT)
- {
- if (f_in)
- {
- result = emiterror ("Ambiguous input redirect.", 0);
- errno = EINVAL;
- goto leave;
- }
- fp = &f_in;
- }
- else if (token == REDIR_OUTPUT || token == REDIR_APPEND)
- {
- if (f_out)
- {
- result = emiterror ("Ambiguous output redirect.", 0);
- errno = EINVAL;
- goto leave;
- }
- fp = &f_out;
- if (token == REDIR_APPEND)
- append_out = 1;
- }
- if (get_sym (u, &u, &v) != WORD)
- {
- result = emiterror ("Target of redirect is not a filename.", 0);
- errno = EINVAL;
- goto leave;
- }
- *fp = memcpy ((char *)alloca (v - u + 1), u, v - u);
- (*fp)[v - u] = 0;
- strcpy (t, v);
- again = 1;
- break;
- case PIPE:
- if (!(sys_flags & __system_redirect))
- return _shell_command ("", cmdline);
- if (f_out)
- {
- result = emiterror ("Ambiguous output redirect.", 0);
- errno = EINVAL;
- goto leave;
- }
-
- /* tmpnam guarantees unique names */
- tmpnam(tmp);
- f_out = strcpy (alloca (L_tmpnam), tmp);
- rm_out = 1;
- /* Fall through. */
- case SEMICOLON:
- case EOL:
- if (needcmd)
- {
- result = emiterror ("No command name seen.", 0);
- errno = EINVAL;
- goto leave;
- }
-
- /* Remove extra whitespace at end of command. */
- while (s > cmdstart && isspace (s[-1])) s--;
- while (t > s && isspace (t[-1])) t--;
- *t = 0;
-
- #ifdef TEST
- fprintf (stderr, "Input from: %s\nOutput%s to: %s\n",
- f_in ? f_in : "<stdin>",
- append_out ? " appended" : "",
- f_out ? f_out : "<stdout>");
- fflush (stderr);
- #endif
- if (f_in)
- {
- int e = errno;
-
- errno = 0;
- h_in = open (f_in, O_RDONLY | O_BINARY);
- hin_err = errno;
- errno = 0;
- h_inbak = dup (0);
- hbak_err = errno;
- dup2 (h_in, 0);
- errno = e;
- }
- if (f_out)
- {
- int e = errno;
-
- errno = 0;
- h_out = open (f_out,
- O_WRONLY | O_BINARY | O_CREAT
- | (append_out ? O_APPEND: O_TRUNC),
- S_IREAD | S_IWRITE);
- hout_err = errno;
- errno = 0;
- h_outbak = dup (1);
- hbak_err = errno;
- fflush(stdout); /* so any buffered chars will be written out */
- dup2 (h_out, 1);
- errno = e;
- }
- if (f_in && h_in < 0)
- result = emiterror ("Cannot redirect input", hin_err);
- else if ((f_in && h_inbak < 0) || (f_out && h_outbak < 0))
- result = emiterror ("Out of file handles in redirect", hbak_err);
- else if (f_out && h_out < 0)
- result = emiterror ("Cannot redirect output", hout_err);
-
- if (!result)
- {
- #ifdef TEST
- fprintf (stderr, "system1 (\"%s\", \"%s\")\n", prog, cmdstart);
- fflush (stderr);
- #endif
- /* Tell `__spawnve' it was invoked by `system', so it would
- know how to deal with command-line arguments' quoting. */
- __dosexec_in_system = 1;
- result = system1 (prog, cmdstart);
- __dosexec_in_system = 0;
- }
- if (f_in)
- {
- dup2 (h_inbak, 0);
- close (h_in);
- close (h_inbak);
- }
- if (f_out)
- {
- dup2 (h_outbak, 1);
- close (h_out);
- close (h_outbak);
- }
-
- if (token == EOL)
- done = 1;
- else
- {
- if (token == SEMICOLON)
- f_in = f_out = 0;
- s = u;
- }
- break;
- case UNMATCHED_QUOTE:
- result = emiterror ("Unmatched quote character.", 0);
- errno = EINVAL;
- goto leave;
- default:
- result = emiterror ("I cannot grok this.", 0);
- errno = EINVAL;
- goto leave;
- }
- } while (again);
- }
- leave:
- if (rm_in)
- remove (f_in);
- if (rm_out)
- remove (f_out);
-
- return result;
- }
- }
-
- #ifdef TEST
-
- #include <signal.h>
-
- int main (int argc, char *argv[])
- {
- __system_flags |= (__system_allow_multiple_cmds | __system_emulate_chdir);
- signal (SIGINT, SIG_IGN);
- if (argc > 1)
- {
- int i;
- printf ("system (\"%s\") gives this:\n\n", argv[1]);
- printf ("\n\nsystem() returned %d", (errno = 0, i = system (argv[1])));
- fflush (stdout);
- if (errno)
- perror ("");
- else
- puts ("");
- return 0;
- }
- return 1;
- }
-
- #endif
-