/* * This program looks at its command line arguments and decides * whether to execute the command directly or to run cmd.exe to * execute them. * * In addition, it implements some key shell commands in order to work * around a problem with the Windows cmd.exe, which has an absurdly * short command line length limit. * * It should be a suitable executable to use as a JAMSHELL for the * Jam/MR make system. */ # define WIN32_LEAN_AND_MEAN #include <windows.h> // do the ugly deed #include <process.h> #include <winbase.h> #include <winuser.h> #include <stdlib.h> #include <stdio.h> #include <string.h> static int debug = 0; static char* version = "jamshell 0.1"; /* builtin functions */ static void builtin_erase(const char* command); static void builtin_echo(const char* command); /* These are commands that NT's cmd.exe implements that we do not, so * if a command begins with one of them, we pass it to cmd.exe. */ const char* cmd_exe_strings[] = { "echo ", "color ", "cd ", "chdir ", "md ", "del ", "erase ", "mkdir ", "prompt", "pushd ", "popd ", "set ", "setlocal ", "endlocal ", "if ", "for ", "call ", "shift ", "goto ", "start ", "assoc ", "ftype " }; /* These are the commands that we implement internally. */ struct { const char* name; void (*function)(const char* args); } builtins[] = { { "jamshell-erase", builtin_erase }, { "jamshell-echo", builtin_echo } }; static void usage(void) { printf( "Usage: jamshell command_file\n" "\nWhere command_file is a file containing command(s) to run. If\n" "command_file contains a single line, then it will be executed directly,\n" "avoiding command line length limitations of cmd.exe. Otherwise it will\n" "use cmd.exe to execute the set of commands.\n\nThis is %s.\n", version); exit(3); } /* end of usage */ /* Returns 1 if this command is something that needs cmd.exe. It * possibly truncates the string if there is a spurious newline at * the end. */ static int cmd_needs_cmd_exe(char* cmd) { int i; char buffer[16]; int quoted = 0; char* newline = NULL; char* curr = NULL; char* prev = NULL; curr = cmd; while (*curr) { if (!quoted) { /* detect unquoted >, <, & and | */ if (*curr == '>' || *curr == '<' || *curr == '&' || *curr == '|') { if (debug) { printf("jamshell: unquoted '%c', must use cmd.exe\n", *curr); } return 1; } } /* detect entering/leaving quoted text */ if (*curr == '"') { quoted = !quoted; } if (*curr == '\n' && !newline) { newline = curr; } /* if we have a newline with non-whitespace following, fail */ if (!isspace(*curr) && newline) { if (debug) { printf("jamshell: non-whitespace follows newline, " "must use cmd.exe\n"); } return 1; } prev = curr; curr++; } /* if we have a newline with only whitespace following, truncate * the newline at the whitespace */ if (newline) { *newline = '\0'; } strncpy(buffer, cmd, sizeof(buffer)); buffer[sizeof(buffer) - 1] = '\0'; strlwr(buffer); for (i = 0; i < sizeof(cmd_exe_strings)/sizeof(cmd_exe_strings[0]); i++) { const char* s = cmd_exe_strings[i]; if (!strncmp(buffer, s, strlen(s))) { /* bummer, cmd is something cmd.exe must execute so we * can't do it ourselves. */ if (debug) { printf("jamshell: cmd is %s, must use cmd.exe\n", cmd_exe_strings[i]); } return 1; } } return 0; } /* end of cmd_needs_cmd_exe */ /* Returns a command string read from 'filename'. The string is * allocated in the heap. */ static char* read_cmd(const char* filename) { FILE* f; char* cmd = NULL; size_t cmd_size = 0; size_t cmd_index = 0; size_t bread; f = fopen(filename, "rt"); if (!f) { perror("fopen"); fprintf(stderr, "jamshell: can not open \"%s\"", filename); exit(3); } for (;;) { char temp[1024]; size_t i; bread = fread(temp, 1, sizeof(temp), f); if (bread <= 0) { break; } if (cmd_index + bread + 1 > cmd_size) { cmd_size = cmd_size ? cmd_size * 2 : (bread + 1); cmd = realloc(cmd, cmd_size); } for (i = 0; i < bread; i++) { cmd[cmd_index++] = temp[i]; } cmd[cmd_index] = '\0'; } if (!cmd) { fprintf(stderr, "jamshell: no commands in \"%s\"", filename); exit(3); } return cmd; } /* end of read_cmd */ /* * Read a word from a string. Returned value is allocated in the * heap. STRING argument is incemented to the next parsing point. */ char* read_word(const char** string) { int in_quotes = 0; int whitespace_only = 1; int ch; const char* get = *string; char* word = malloc(1); *word = 0; while ( (ch = *get++) ) { if (ch == '"') { in_quotes = !in_quotes; continue; } else if (whitespace_only && isspace(ch)) { continue; } whitespace_only = 0; if (!in_quotes && isspace(ch)) { *string = get; return word; } word = realloc(word, strlen(word) + 2); word[strlen(word) + 1] = 0; word[strlen(word)] = (char)ch; } if (ch == 0) *string = NULL; else *string = get; if (whitespace_only) { free(word); return NULL; } return word; } /* * Try to execute a command internally. */ int execute_internally(const char* cmd) { char* command = read_word(&cmd); int i; if (debug) { printf("jamshell: command is <%s>\n", command ? command : "<null>"); } if (!command) return 1; for (i = 0; i < sizeof(builtins) / sizeof(builtins[0]); i++) { if (!stricmp(command, builtins[i].name)) { free(command); builtins[i].function(cmd); return 1; } } free(command); return 0; } /* * Delete all arguments */ void builtin_erase(const char* args) { if (debug) { printf("jamshell: builtin_erase\n"); } while (args) { char* file = read_word(&args); if (file) { if (unlink(file) < 0) { perror("unlink"); printf("jamshell: could not erase <%s>\n", file); } } else { return; } } } /* * Echo all arguments to a file */ void builtin_echo(const char* args) { int parsing_args = 1; int append = 0; const char* separator = ""; FILE* file = stdout; if (debug) { printf("jamshell: builtin_echo\n"); } while (args) { char* word = read_word(&args); if (!word) break; if (parsing_args && word[0] == '-') { switch (word[1]) { case '-': args = 0; break; case 'a': append = 1; break; case 'f': { char* filename = read_word(&args); if (!filename) { fprintf(stderr, "jamshell: builtin_echo: no " "filename follows -f\n"); exit(3); } if (file != stdout) { fclose(file); } file = fopen(filename, append ? "a" : "w"); if (!file) { perror("fopen"); fprintf(stderr, "jamshell: builtin_echo: can not open " "\"%s\"\n", filename); } free(filename); break; } default: fprintf(stderr, "jamshell: builtin_echo: unknown arg %s\n", word); exit(3); } } else { parsing_args = 0; fprintf(file, "%s%s", separator, word); separator = " "; } free(word); } fprintf(file, "\n"); if (file != stdout) { fclose(file); } } int main(int argc, char** argv) { char* cmd = NULL; const char* cmd_file = NULL; HANDLE currentProc; STARTUPINFO startInfo; PROCESS_INFORMATION procInfo; int i; for (i = 1; i < argc; i++) { if (!strcmp("-d", argv[i])) { debug++; } else if (i == argc - 1) { cmd_file = argv[i]; } else { usage(); } } if (debug) { printf("jamshell: this is %s\n", version); } if (cmd_file == NULL) { fprintf(stderr, "jamshell: must pass command file\n"); usage(); } cmd = read_cmd(cmd_file); if (cmd_needs_cmd_exe(cmd)) { /* command is something complex, so pass it off to cmd.exe * and hope that it works */ static const char* cmd_exe = "cmd.exe /Q/C "; if (debug) { printf("jamshell: using cmd.exe.\n"); } cmd = malloc(strlen(cmd_exe) + strlen(cmd_file) + 1); strcpy(cmd, cmd_exe); strcat(cmd, cmd_file); } else if (execute_internally(cmd)) { exit(0); } memset(&startInfo, 0, sizeof(STARTUPINFO)); startInfo.cb = sizeof(STARTUPINFO); startInfo.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; startInfo.wShowWindow = SW_HIDE; currentProc = GetCurrentProcess(); DuplicateHandle(currentProc, GetStdHandle(STD_INPUT_HANDLE), currentProc, &startInfo.hStdInput, 0, TRUE, DUPLICATE_SAME_ACCESS); DuplicateHandle(currentProc, GetStdHandle(STD_OUTPUT_HANDLE), currentProc, &startInfo.hStdOutput, 0, TRUE, DUPLICATE_SAME_ACCESS); DuplicateHandle(currentProc, GetStdHandle(STD_ERROR_HANDLE), currentProc, &startInfo.hStdError, 0, TRUE, DUPLICATE_SAME_ACCESS); if (debug) { printf("jamshell: command length %d.\n", strlen(cmd)); printf("jamshell: command is <%s>.\n", cmd); } if (!CreateProcess(NULL, (char*)cmd, NULL, NULL, TRUE, 0, NULL, NULL, &startInfo, &procInfo)) { char* start = cmd; char* end; char* command; size_t length; while (*start && isspace(*start)) { start++; } end = start; while (*end && !isspace(*end)) { end++; } length = end - start; command = malloc(length + 1); memcpy(command, start, length); command[length] = '\0'; fprintf(stderr, "jamshell: command not found: %s\n", command); fprintf(stderr, "jamshell: \"%s\" is probably not in your PATH.\n", command); fprintf(stderr, "jamshell: the current PATH is \"%s\".\n", getenv("PATH")); free(command); exit(3); } WaitForSingleObject(currentProc, 50); WaitForInputIdle(procInfo.hProcess, 5000); CloseHandle(procInfo.hThread); CloseHandle(startInfo.hStdInput); CloseHandle(startInfo.hStdOutput); CloseHandle(startInfo.hStdError); for (;;) { DWORD exitcode; if (WAIT_FAILED == WaitForSingleObject(procInfo.hProcess, 500)) { continue; } if (GetExitCodeProcess(procInfo.hProcess, &exitcode)) { if (exitcode != STILL_ACTIVE) { CloseHandle(procInfo.hProcess); return exitcode; } } } } /* end of main() */
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#1 | 1182 | Matt Armstrong |
This is a shell suitable for use as a JAMSHELL on Windows NT. If you increace MAXLINES in jam.h to 10000 for Windows NT, you'll find that CMD.EXE no longer serves as a useful default shell because its built in commands such as del and echo can't handle such long command lines. This shell does two things: Implements jamshell-echo and jamshell-erase commands that do the job of CMD.EXE's echo and del commands. This way, very long lists of files can be deleted in one go. Checks if the command is a single line that contains no shell meta characters, and if so it runs the command directly. By bypassing CMD.EXE, very long command lines can be executed. Otherwise, it shells out to CMD.EXE to run the command. |