#include #include #include #include #include #include #include #include #include #include #ifdef _WIN32 #ifdef _WIN64 #define PRI_SIZET PRIu64 #else #define PRI_SIZET PRIu32 #endif #else #define PRI_SIZET "zu" #endif #if defined(_WIN32) || defined(_WIN64) #include #include #include static HANDLE my_pipein[2], my_pipeout[2], my_pipeerr[2]; #if !defined(_WINDOWS_) #define _WINDOWS_ #endif #if !defined(PATH_MAX) #define PATH_MAX MAX_PATH #endif #elif defined(__LINUX__) || defined(__linux__) #include #include #include enum {FALSE=0, TRUE=1}; typedef int BOOL; #define _read read #if !defined(_LINUX_) #define _LINUX_ #endif #endif #define DQUOTE 0x22 int is_space_or_tab(char c) { return c == ' ' || c == '\t'; } char *skip_spaces(const char *cp) { while (cp[0] != '\0' && *cp == ' ') cp++; return (char *)cp; } /************************************************************************ Return the number of tokens if MAGIC, Return position of 2nd token if MAGIC0, otherwise, length of found token. ************************************************************************/ const int MAGIC_NEXTTOKEN = -1, MAGIC_NUMTOKENS = -2; int GetFileToken(const char *cp, char *token, int n) { int count, preserve_quotes = FALSE; char *tok = token; const char *cp0 = cp; *token = '\0'; // just in case we return early if ((n != MAGIC_NUMTOKENS && n != MAGIC_NEXTTOKEN) && (n & 0x8000)) { n &= ~0x8000; preserve_quotes = TRUE; } if ((n == 0 || n < MAGIC_NUMTOKENS) || cp[0] == '\0') return 0; for (count = 0; count != n;) { int ignore_spaces; while (is_space_or_tab(*cp)) cp++; if (*cp == '\0' || (count == 1 && n == MAGIC_NEXTTOKEN)) break; count++; for (ignore_spaces = 0; *cp; cp++) { if (*cp == DQUOTE) { ignore_spaces ^= 1; if (count == n && preserve_quotes) { *tok++ = *cp; } } else if (!ignore_spaces && (is_space_or_tab(*cp))) { cp++; break; } else if (count == n) *tok++ = *cp; } } *tok = '\0'; if (n == MAGIC_NEXTTOKEN) { return cp - cp0; } return n == MAGIC_NUMTOKENS ? count : (int)strlen(token); } #if defined(_LINUX_) void my_exec(const char *command, const char *cmdtail) { int i; char *argv[128] = {0}; char work[1000], *argp; argv[0] = strdup(command); /* -c "source \"a space/cmd\"" should end up with: -c source "a space/cmd" loop while c is a space, skip it if c is a " then copy to arg until " if \, skip it, and take the next character no matter what it is stop on \0 else copy until a space or quote if \, skip it, and take the next character no matter what it is endif next arg end */ // argp - copy to // s -- copy from if (cmdtail != NULL) { for (i = 1; ; ++i) { // skip leading spaces while (*cmdtail == ' ') ++cmdtail; if (*cmdtail == '\0') break; argp = work; if (*cmdtail == '"') { // copy until '"' or end-of-string // skip over initial quote cmdtail++; for (;;) { if (*cmdtail == '\\') { ++cmdtail; if (*cmdtail != '\0') { *argp++ = *cmdtail++; } } if (*cmdtail == '"') { // skip over ending quote cmdtail++; break; } if (*cmdtail == '\0') { break; } *argp++ = *cmdtail++; } } else { // copy until space, '"', or end-of-string // only cases at first: escape, end-of-string, or non-quote/non-space for (;;) { if (*cmdtail == '\\') { ++cmdtail; if (*cmdtail != '\0') { *argp++ = *cmdtail++; } } if (*cmdtail == ' ' || *cmdtail == '"') { break; } if (*cmdtail == '\0') { break; } *argp++ = *cmdtail++; } } *argp = '\0'; argv[i] = strdup(work); } } argv[i] = NULL; #if defined(__WATCOMC__) execvp(command, (const char **)argv); #else execvp(command, (char * const*)argv); #endif // if we get to here, execvp failed if (errno == 0) errno = 1; _exit(127); } /*------------------------------------------------------------------------ flags: 0 = everything 1 = stdout only 2 = stderr only 3 = everything Returns a handle to the output of command. ------------------------------------------------------------------------*/ int my_popen(const char *command, const char *cmdtail, int flags, void **process) { int pipefd[2]; pid_t pid; printf("my_popen begin command %s cmdtail %s\n", command, cmdtail); if (command == NULL) return 0; if (flags == 0) flags = 3; if (pipe(pipefd) < 0) return 0; if ((pid = fork()) < 0) { close(pipefd[0]); close(pipefd[1]); return 0; } if (pid == 0) { // child int fd; if (flags != 3) fd = open("/dev/null", O_WRONLY); close(pipefd[0]); // close reading end in the child if (flags & STDOUT_FILENO) dup2(pipefd[1], 1); // send stdout to the pipe else dup2(fd, 1); // else send stdout to null if (flags & STDERR_FILENO) dup2(pipefd[1], 2); // send stderr to the pipe else dup2(fd, 2); // else send stderr to null close(pipefd[1]); // this descriptor is no longer needed if (flags != 3) close(fd); my_exec(command, cmdtail); // never returns } // parent close(pipefd[1]); // close the write end of the pipe in the parent *process = (void *)(size_t)pid; printf("my_popen end pid %d pipefd[0] %d\n", pid, pipefd[0]); return pipefd[0]; } /*------------------------------------------------------------------------ When exec* fails, convention is to _exit(127). If rc is -1 or 127, return FALSE, else TRUE. ------------------------------------------------------------------------*/ BOOL my_pclose(int fh, void *g_pid, int flags) { int rc; pid_t pid; int status; printf("my_pclose begin g_pid %ld\n", (long)g_pid); close(fh); /* wait for process to end */ do { pid = waitpid((long)g_pid, &status, 0); } while (pid == -1 && errno == EINTR); if (WIFEXITED(status)) rc = WEXITSTATUS(status); else rc = -1; printf("my_pclose end rc %d\n", rc); errno = rc; return rc == -1 || rc == 127 ? FALSE : TRUE; } #elif defined(_WINDOWS_) int wait_for_process(HANDLE hProcess) { DWORD exit_code = 0; int i; #if defined(GUI) char wtitle[MAX_SCREEN_COLUMNS]; int num_objects = 1; HANDLE wait_objects_tab[1]; wait_objects_tab[0] = hProcess; for (;;) { int result; if (!GetExitCodeProcess(hProcess, &exit_code)) break; result = MsgWaitForMultipleObjects(num_objects, wait_objects_tab, FALSE, INFINITE, QS_ALLINPUT); if (result == (WAIT_OBJECT_0 + num_objects)) { MSG msg; while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { handleQuitMessage(msg); if (IN_RANGE(msg.message, WM_KEYFIRST, WM_KEYLAST)) { switch (msg.message) { case WM_KEYDOWN: case WM_SYSKEYDOWN: switch (msg.wParam) { case VK_SHIFT: case VK_CONTROL: case VK_MENU: break; default: break; } } continue; } if (IN_RANGE(msg.message, WM_MOUSEFIRST, WM_MOUSELAST)) continue; DispatchMessage(&msg); } } else { GetExitCodeProcess(hProcess, &exit_code); break; } } end_of_shell: if (give_us_focus && !IsIconic(g->hwnd)) { SetForegroundWindow(g->hwnd); SetActiveWindow(g->hwnd); } #else for (i = 0; ;++i) { int rc; if (!GetExitCodeProcess(hProcess, &exit_code)) break; rc = GetLastError(); //if (i < 5) { // printf("GetExitCodeProcess i %d exit_code %ld rc %d\n", i, exit_code, rc); //} // Error code 5 stands for "Access is Denied" // Error 109 is a system error code in Win32 that means "the pipe has been ended". It can also mean "The request was in a format that the server did not expect". if (i > 0 && (rc == 5 || rc == 109)) { //printf("breaking i %d exit_code %ld because rc is %d\n", i, exit_code, rc); break; } if (WaitForSingleObject(hProcess, 5000) == WAIT_OBJECT_0) { GetExitCodeProcess(hProcess, &exit_code); break; } } #endif return exit_code; } void close_some_handles(void) { if (my_pipein[0] != INVALID_HANDLE_VALUE) { CloseHandle(my_pipein[0]); my_pipein[0] = INVALID_HANDLE_VALUE; } if (my_pipein[1] != INVALID_HANDLE_VALUE) { CloseHandle(my_pipein[1]); my_pipein[1] = INVALID_HANDLE_VALUE; } if (my_pipeout[0] != INVALID_HANDLE_VALUE) { CloseHandle(my_pipeout[0]); my_pipeout[0] = INVALID_HANDLE_VALUE; } if (my_pipeout[1] != INVALID_HANDLE_VALUE) { CloseHandle(my_pipeout[1]); my_pipeout[1] = INVALID_HANDLE_VALUE; } if (my_pipeerr[0] != INVALID_HANDLE_VALUE) { CloseHandle(my_pipeerr[0]); my_pipeerr[0] = INVALID_HANDLE_VALUE; } if (my_pipeerr[1] != INVALID_HANDLE_VALUE) { CloseHandle(my_pipeerr[1]); my_pipeerr[1] = INVALID_HANDLE_VALUE; } } int my_pipe(HANDLE *readwrite) { SECURITY_ATTRIBUTES sa; sa.nLength = sizeof(sa); sa.bInheritHandle = TRUE; sa.lpSecurityDescriptor = NULL; if (!CreatePipe(&readwrite[0],&readwrite[1],&sa, 1 << 13)) { errno = EMFILE; return -1; } return 0; } #define STDIN_FILENO 0 #define STDOUT_FILENO 1 #define STDERR_FILENO 2 int my_popen(const char *command, const char *cmdtail, int flags, void **process) { PROCESS_INFORMATION pi; STARTUPINFO si = {0}; int success, fh = 0, error; char *cmdln = alloca(strlen(command) + strlen(cmdtail) + 2); printf("my_popen begin command %s cmdtail %s\n", command, cmdtail); if (command == NULL) return 0; if (cmdtail == NULL || cmdtail[0] == '\0') strcpy(cmdln, command); else sprintf(cmdln, "%s %s", command, cmdtail); if (flags == 0) flags = STDOUT_FILENO | STDERR_FILENO; my_pipein[0] = my_pipein[1] = my_pipeout[0] = my_pipeout[1] = my_pipeerr[0] = my_pipeerr[1] = INVALID_HANDLE_VALUE; /* * Should the stderr be redirected to stdout ? */ //redirect_stderr = strstr(cmd, "2>&1") != NULL; //redirect_stderr = 1; /* * Create the Pipes... */ if (my_pipe(my_pipein) == -1) goto finish_up; if (my_pipe(my_pipeout) == -1) goto finish_up; if ((flags & STDERR_FILENO) && my_pipe(my_pipeerr) == -1) goto finish_up; /* * Now create the child process */ si.cb = sizeof(STARTUPINFO); si.hStdInput = my_pipein[0]; si.hStdOutput = my_pipeout[1]; si.hStdError = my_pipeout[1]; if (flags == STDOUT_FILENO || flags == STDERR_FILENO) si.hStdError = my_pipeerr[1]; si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; si.wShowWindow = SW_HIDE; //CREATE_NO_WINDOW //0x08000000 errno = 0; success = CreateProcess( NULL, // application name (LPTSTR)cmdln, // command line NULL, // process security attributes NULL, // primary thread security attributes TRUE, // handles are inherited CREATE_DEFAULT_ERROR_MODE|CREATE_NO_WINDOW, // creation flags NULL, // use parent's environment NULL, // use parent's current directory &si, // STARTUPINFO pointer &pi); // receives PROCESS_INFORMATION error = 0; if (!success) { if (errno != 0) error = errno; else error = 1; } CloseHandle(pi.hThread); *process = pi.hProcess; if (!success) goto finish_up; /* * These handles belong to the child process */ CloseHandle(my_pipein[0]); my_pipein[0] = INVALID_HANDLE_VALUE; CloseHandle(my_pipeout[1]); my_pipeout[1] = INVALID_HANDLE_VALUE; CloseHandle(my_pipeerr[1]); my_pipeerr[1] = INVALID_HANDLE_VALUE; if (flags == STDERR_FILENO) fh = _open_osfhandle((intptr_t)my_pipeerr[0], _O_BINARY); else fh = _open_osfhandle((intptr_t)my_pipeout[0], _O_BINARY); finish_up: if (!fh) { close_some_handles(); } errno = error; printf("my_popen end hProcess %p pipefd %d\n", *process, fh); return fh; } BOOL my_pclose(int fh, void *process, int flags) { int error; printf("my_pclose begin process %p\n", process); _close(fh); if (flags == STDERR_FILENO) my_pipeerr[0] = INVALID_HANDLE_VALUE; else my_pipeout[0] = INVALID_HANDLE_VALUE; close_some_handles(); error = wait_for_process((HANDLE)process); errno = error; printf("my_pclose end rc %d\n", error); return error; } #endif char *read_file(int fh) { char *buf; int tot_size, size, n, to_read, m; printf("read_file begin fh %d\n", fh); size = 2 << 15; // 5000000 seems to work tot_size = 0; buf = malloc(size); to_read = size; for (;;) { char *buf0; readit: #if defined(_WINDOWS_) m = _msize(buf); #endif n = _read(fh, &buf[tot_size], to_read); #if defined(_WINDOWS_) if ((int)_msize(buf) != m) { printf("Oops! m %d _msize %d\n", m, (int)_msize(buf)); } printf("read %d bytes tot %d msize %d\n", n, tot_size, m); #endif if (n > 0) { tot_size += n; if (n < size) { to_read -= n; if (to_read > 0) goto readit; } } else if (n == 0) { #if defined(_WINDOWS_) int ok; DWORD total_bytes_avail; HANDLE h = (HANDLE)_get_osfhandle(fh); ok = PeekNamedPipe(h, NULL, 0, NULL, &total_bytes_avail, NULL); if (!ok) { break; } #elif defined(_LINUX_) #if 0 if (fcntl(fh, F_GETFL) == -1) { printf("fcntl returned -1\n"); break; } if (errno == EBADF) { printf("errno returned EBADF\n"); break; } { int fd_dup = dup(fh); if (fd_dup == -1) { printf("f_dup returned -1\n"); if (errno == EBADF) { printf("f_dup returned EBADF, breaking\n"); break; } } else close(fd_dup); } #if 0 - constant not found { int pipesize = fcntl(fh, F_GETPIPE_SZ); if (pipesize == -1) { printf("pipesize returned < 0, breaking\n"); break; } } #endif #if defined(__GNUC__) { #include struct pollfd pfd = {.fd = fh, .events = POLLERR}; if (poll(&pfd, 1, 100) < 0) { printf("poll returned < 0, breaking\n"); break; } if (pfd.revents & POLLERR) { printf("pfd found POLLERR, breaking\n"); break; } } #endif #endif printf("breaking on 0\n"); break; #endif goto readit; } else { printf("breaking on read < 0\n"); break; } buf0 = realloc(buf, tot_size + size); //if (buf != buf0) // free(buf); buf = buf0; to_read = size; } if (tot_size == 0) { free(buf); return NULL; } buf = realloc(buf, tot_size + 1); buf[tot_size] = '\0'; //printf("total size %d\n", tot_size); printf("read_file end tot_size %d\n", tot_size); return buf; } /*------------------------------------------------------------------------ Capture output of command. Run command directly. my_popen never (rarely?) fails on Linux - however it can on Windows. my_pclose will fail if command could not be found. Returns 0 on failure, capture buffer on success. ------------------------------------------------------------------------*/ char *cmlCapture(const char *command, const char *cmdtail, int flags) { int fh, error, error2; void *process; char *buf; if (command == NULL) return NULL; printf("cmlCapture begin command %s cmdtail %s\n", command, cmdtail); if (flags == 0) flags = 3; fh = my_popen(command, cmdtail, flags, &process); if (!fh) { if (errno == 0) errno = 1; return NULL; } error = errno; buf = read_file(fh); errno = 0; error2 = my_pclose(fh, process, flags); if (error && errno == 0) errno = error; if (error2 && errno == 0) errno = error2; //printf("cmlCapture end process %" PRI_SIZET "\n", (size_t)process); printf("cmlCapture end process %p\n", process); return buf; } #if defined(_LINUX_) // bash builtins: const char *built_ins[] = { "alias", "bg", "bind", "break", "builtin", "case", "cd", "command", "compgen", "complete", "continue", "declare", "dirs", "disown", "echo", "enable", "eval", "exec", "exit", "export", "fc", "fg", "getopts", "hash", "help", "history", "if", "jobs", "kill", "let", "local", "logout", "popd", "printf", "pushd", "pwd", "read", "readonly", "return", "set", "shift", "shopt", "source", "suspend", "test", "times", "trap", "type", "typeset", "ulimit", "umask", "unalias", "unset", "until", "wait", "while", NULL, }; #elif defined(_WINDOWS_) // cmd.exe builtins: const char *built_ins[] = { "assoc", "break", "call", "cd", "chdir", "cls", "color", "copy", "date", "del", "dir", "dpath", "echo", "endlocal", "erase", "exit", "for", "ftype", "goto", "if", "keys", "md", "mkdir", "mklink", "move", "path", "pause", "popd", "prompt", "pushd", "rd", "rem", "ren", "rename", "rmdir", "set", "setlocal", "shift", "start", "time", "title", "type", "ver", "verify", "vol", NULL, }; #endif int compare(const void *a, const void *b) { return strcmp(*(const char **)a, *(const char **)b); } int is_builtin(const char *needle, const char *haystack[], int n) { char **result; result = (char **)bsearch(&needle, haystack, n, sizeof(const char *), compare); return result != NULL; } /*------------------------------------------------------------------------ Capture output of cmd. Always run through system shell. ------------------------------------------------------------------------*/ char *cmShell(const char *cmd, int flags) { char shell[PATH_MAX + 1]; char command[PATH_MAX + 1]; #if defined(_LINUX_) char *p = "SHELL"; char *c; strcpy(shell, "/bin/bash"); // default in case getenv returns null #elif defined(_WINDOWS_) char *p = "COMSPEC"; strcpy(shell, "c:\\windows\\system32\\cmd.exe"); // default in case getenv returns null #endif if (cmd == NULL) return NULL; p = getenv(p); if (p) strcpy(shell, p); #if defined(_LINUX_) c = strcpy(command, "-c ") + 3; if (cmd[0] == '"') { strcat(command, cmd); } else { // copy cmd to command, quotting it, and escaping '"' *c++ = '"'; while (*cmd) { if (*cmd == '"') { *c++ = '\\'; } *c++ = *cmd++; } *c++ = '"'; *c++ = '\0'; } #elif defined(_WINDOWS_) sprintf(command, "/c %s", cmd); #endif return cmlCapture(shell, command, flags); } /*------------------------------------------------------------------------ Capture the output of cmd. If cmd is a built-in, run it via the shell. Otherwise, try to run it directly. ------------------------------------------------------------------------*/ // returns buffer_id or 0 on error. char *cmCapture(const char *cmd, int flags) { char command[PATH_MAX + 1]; int len, n = sizeof(built_ins) / sizeof(built_ins[0]); if (cmd == NULL) return NULL; #if defined(_WINDOWS_) if (cmd[0] != '\0' && cmd[1] == ':') return cmlCapture(cmd, "", flags); #endif // ' or " or \ or / if (strchr("'\"\\/", cmd[0]) != NULL) { #if defined(_WINDOWS_) return cmlCapture(cmd, "", flags); #elif defined(_LINUX_) char *c = command; char quote = cmd[0]; // if cmd starts with a slash, put first part in command, rest in 'tail' if (cmd[0] == '\\' || cmd[0] == '/') { // copy until a space or null while (*cmd) { if (*cmd == ' ') break; *c++ = *cmd++; } *c = '\0'; cmd = skip_spaces(cmd); return cmlCapture(command, cmd, flags); } // cmd[0] is a quote // put quoted string in command, pass everything else in tail // copy until quote or null while (*cmd) { if (*cmd == quote) { *c++ = *cmd++; break; } *c++ = *cmd++; } *c = '\0'; cmd = skip_spaces(cmd); return cmlCapture(command, cmd, flags); #endif } // cmd does not start with a quote or a dir separator. See if it is a built-in len = GetFileToken(cmd, command, 1); if (!is_builtin(command, built_ins, n)) { cmd = skip_spaces(cmd + len); return cmlCapture(command, cmd, flags); } return cmShell(cmd, flags); } int main(int argc, char *argv[]) { char *buf, *out; int i, first = TRUE; if (argc < 2) { printf("Capture command arg1 arg2 ... argn\n"); return 0; } buf = calloc(4000, 1); for (i = 1; i < argc; i++) { if (first) { first = FALSE; } else { strcat(buf, " "); } strcat(buf, argv[i]); } buf = realloc(buf, strlen(buf) + 1); out = cmCapture(buf, 3); free(buf); if (out) { puts(out); free(out); } return 0; }