summaryrefslogtreecommitdiff
path: root/execute.c
diff options
context:
space:
mode:
Diffstat (limited to 'execute.c')
-rw-r--r--execute.c348
1 files changed, 348 insertions, 0 deletions
diff --git a/execute.c b/execute.c
new file mode 100644
index 0000000..566ef9e
--- /dev/null
+++ b/execute.c
@@ -0,0 +1,348 @@
+// Copyright (C) 2002 Andrew Tridgell
+// Copyright (C) 2011-2016 Joel Rosdahl
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+// more details.
+//
+// You should have received a copy of the GNU General Public License along with
+// this program; if not, write to the Free Software Foundation, Inc., 51
+// Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+#include "ccache.h"
+
+extern struct conf *conf;
+
+static char *
+find_executable_in_path(const char *name, const char *exclude_name, char *path);
+
+#ifdef _WIN32
+// Re-create a win32 command line string based on **argv.
+// http://msdn.microsoft.com/en-us/library/17w5ykft.aspx
+char *
+win32argvtos(char *prefix, char **argv)
+{
+ int i = 0;
+ int k = 0;
+ char *arg = prefix ? prefix : argv[i++];
+ do {
+ int bs = 0;
+ for (int j = 0; arg[j]; j++) {
+ switch (arg[j]) {
+ case '\\':
+ bs++;
+ break;
+ case '"':
+ bs = (bs << 1) + 1;
+ default:
+ k += bs + 1;
+ bs = 0;
+ }
+ }
+ k += (bs << 1) + 3;
+ } while ((arg = argv[i++]));
+
+ char *ptr = malloc(k + 1);
+ char *str = ptr;
+ if (!str) {
+ return NULL;
+ }
+
+ i = 0;
+ arg = prefix ? prefix : argv[i++];
+ do {
+ int bs = 0;
+ *ptr++ = '"';
+ for (int j = 0; arg[j]; j++) {
+ switch (arg[j]) {
+ case '\\':
+ bs++;
+ break;
+ case '"':
+ bs = (bs << 1) + 1;
+ default:
+ while (bs && bs--) {
+ *ptr++ = '\\';
+ }
+ *ptr++ = arg[j];
+ }
+ }
+ bs <<= 1;
+ while (bs && bs--) {
+ *ptr++ = '\\';
+ }
+ *ptr++ = '"';
+ *ptr++ = ' ';
+ // cppcheck-suppress unreadVariable
+ } while ((arg = argv[i++]));
+ ptr[-1] = '\0';
+
+ return str;
+}
+
+char *
+win32getshell(char *path)
+{
+ char *path_env;
+ char *sh = NULL;
+ const char *ext = get_extension(path);
+ if (ext && strcasecmp(ext, ".sh") == 0 && (path_env = getenv("PATH"))) {
+ sh = find_executable_in_path("sh.exe", NULL, path_env);
+ }
+ if (!sh && getenv("CCACHE_DETECT_SHEBANG")) {
+ // Detect shebang.
+ FILE *fp = fopen(path, "r");
+ if (fp) {
+ char buf[10];
+ fgets(buf, sizeof(buf), fp);
+ buf[9] = 0;
+ if (str_eq(buf, "#!/bin/sh") && (path_env = getenv("PATH"))) {
+ sh = find_executable_in_path("sh.exe", NULL, path_env);
+ }
+ fclose(fp);
+ }
+ }
+
+ return sh;
+}
+
+void add_exe_ext_if_no_to_fullpath(char *full_path_win_ext, size_t max_size,
+ const char *ext, const char *path) {
+ if (!ext || (!str_eq(".exe", ext)
+ && !str_eq(".bat", ext)
+ && !str_eq(".EXE", ext)
+ && !str_eq(".BAT", ext))) {
+ snprintf(full_path_win_ext, max_size, "%s.exe", path);
+ } else {
+ snprintf(full_path_win_ext, max_size, "%s", path);
+ }
+}
+
+int
+win32execute(char *path, char **argv, int doreturn,
+ int fd_stdout, int fd_stderr)
+{
+ PROCESS_INFORMATION pi;
+ memset(&pi, 0x00, sizeof(pi));
+
+ STARTUPINFO si;
+ memset(&si, 0x00, sizeof(si));
+
+ char *sh = win32getshell(path);
+ if (sh) {
+ path = sh;
+ }
+
+ si.cb = sizeof(STARTUPINFO);
+ if (fd_stdout != -1) {
+ si.hStdOutput = (HANDLE)_get_osfhandle(fd_stdout);
+ si.hStdError = (HANDLE)_get_osfhandle(fd_stderr);
+ si.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
+ si.dwFlags = STARTF_USESTDHANDLES;
+ if (si.hStdOutput == INVALID_HANDLE_VALUE
+ || si.hStdError == INVALID_HANDLE_VALUE) {
+ return -1;
+ }
+ } else {
+ // Redirect subprocess stdout, stderr into current process.
+ si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
+ si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
+ si.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
+ si.dwFlags = STARTF_USESTDHANDLES;
+ if (si.hStdOutput == INVALID_HANDLE_VALUE
+ || si.hStdError == INVALID_HANDLE_VALUE) {
+ return -1;
+ }
+ }
+
+ char *args = win32argvtos(sh, argv);
+ const char *ext = strrchr(path, '.');
+ char full_path_win_ext[MAX_PATH] = {0};
+ add_exe_ext_if_no_to_fullpath(full_path_win_ext, MAX_PATH, ext, path);
+ BOOL ret =
+ CreateProcess(full_path_win_ext, args, NULL, NULL, 1, 0, NULL, NULL,
+ &si, &pi);
+ if (fd_stdout != -1) {
+ close(fd_stdout);
+ close(fd_stderr);
+ }
+ free(args);
+ if (ret == 0) {
+ LPVOID lpMsgBuf;
+ DWORD dw = GetLastError();
+ FormatMessage(
+ FORMAT_MESSAGE_ALLOCATE_BUFFER |
+ FORMAT_MESSAGE_FROM_SYSTEM |
+ FORMAT_MESSAGE_IGNORE_INSERTS,
+ NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpMsgBuf,
+ 0, NULL);
+
+ LPVOID lpDisplayBuf =
+ (LPVOID) LocalAlloc(LMEM_ZEROINIT,
+ (lstrlen((LPCTSTR) lpMsgBuf)
+ + lstrlen((LPCTSTR) __FILE__) + 200)
+ * sizeof(TCHAR));
+ _snprintf((LPTSTR) lpDisplayBuf,
+ LocalSize(lpDisplayBuf) / sizeof(TCHAR),
+ TEXT("%s failed with error %d: %s"), __FILE__, dw, lpMsgBuf);
+
+ cc_log("can't execute %s; OS returned error: %s",
+ full_path_win_ext, (char *)lpDisplayBuf);
+
+ LocalFree(lpMsgBuf);
+ LocalFree(lpDisplayBuf);
+
+ return -1;
+ }
+ WaitForSingleObject(pi.hProcess, INFINITE);
+
+ DWORD exitcode;
+ GetExitCodeProcess(pi.hProcess, &exitcode);
+ CloseHandle(pi.hProcess);
+ CloseHandle(pi.hThread);
+ if (!doreturn) {
+ x_exit(exitcode);
+ }
+ return exitcode;
+}
+
+#else
+
+// Execute a compiler backend, capturing all output to the given paths the full
+// path to the compiler to run is in argv[0].
+int
+execute(char **argv, int fd_out, int fd_err, pid_t *pid)
+{
+ cc_log_argv("Executing ", argv);
+
+ block_signals();
+ *pid = fork();
+ unblock_signals();
+
+ if (*pid == -1) {
+ fatal("Failed to fork: %s", strerror(errno));
+ }
+
+ if (*pid == 0) {
+ // Child.
+ dup2(fd_out, 1);
+ close(fd_out);
+ dup2(fd_err, 2);
+ close(fd_err);
+ x_exit(execv(argv[0], argv));
+ }
+
+ close(fd_out);
+ close(fd_err);
+
+ int status;
+ if (waitpid(*pid, &status, 0) != *pid) {
+ fatal("waitpid failed: %s", strerror(errno));
+ }
+
+ block_signals();
+ *pid = 0;
+ unblock_signals();
+
+ if (WEXITSTATUS(status) == 0 && WIFSIGNALED(status)) {
+ return -1;
+ }
+
+ return WEXITSTATUS(status);
+}
+#endif
+
+// Find an executable by name in $PATH. Exclude any that are links to
+// exclude_name.
+char *
+find_executable(const char *name, const char *exclude_name)
+{
+ if (is_absolute_path(name)) {
+ return x_strdup(name);
+ }
+
+ char *path = conf->path;
+ if (str_eq(path, "")) {
+ path = getenv("PATH");
+ }
+ if (!path) {
+ cc_log("No PATH variable");
+ return NULL;
+ }
+
+ return find_executable_in_path(name, exclude_name, path);
+}
+
+static char *
+find_executable_in_path(const char *name, const char *exclude_name, char *path)
+{
+ path = x_strdup(path);
+
+ // Search the path looking for the first compiler of the right name that
+ // isn't us.
+ char *saveptr = NULL;
+ for (char *tok = strtok_r(path, PATH_DELIM, &saveptr);
+ tok;
+ tok = strtok_r(NULL, PATH_DELIM, &saveptr)) {
+#ifdef _WIN32
+ char namebuf[MAX_PATH];
+ int ret = SearchPath(tok, name, NULL, sizeof(namebuf), namebuf, NULL);
+ if (!ret) {
+ char *exename = format("%s.exe", name);
+ ret = SearchPath(tok, exename, NULL, sizeof(namebuf), namebuf, NULL);
+ free(exename);
+ }
+ (void) exclude_name;
+ if (ret) {
+ free(path);
+ return x_strdup(namebuf);
+ }
+#else
+ struct stat st1, st2;
+ char *fname = format("%s/%s", tok, name);
+ // Look for a normal executable file.
+ if (access(fname, X_OK) == 0 &&
+ lstat(fname, &st1) == 0 &&
+ stat(fname, &st2) == 0 &&
+ S_ISREG(st2.st_mode)) {
+ if (S_ISLNK(st1.st_mode)) {
+ char *buf = x_realpath(fname);
+ if (buf) {
+ char *p = basename(buf);
+ if (str_eq(p, exclude_name)) {
+ // It's a link to "ccache"!
+ free(p);
+ free(buf);
+ continue;
+ }
+ free(buf);
+ free(p);
+ }
+ }
+
+ // Found it!
+ free(path);
+ return fname;
+ }
+ free(fname);
+#endif
+ }
+
+ free(path);
+ return NULL;
+}
+
+void
+print_command(FILE *fp, char **argv)
+{
+ for (int i = 0; argv[i]; i++) {
+ fprintf(fp, "%s%s", (i == 0) ? "" : " ", argv[i]);
+ }
+ fprintf(fp, "\n");
+}