| /* |
| Copyright (C) Andrew Tridgell 2002 |
| |
| 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. |
| */ |
| |
| #include "ccache.h" |
| |
| #ifdef _WIN32 |
| char *argvtos(char **argv) |
| { |
| int i, len; |
| char *ptr, *str; |
| |
| for (i = 0, len = 0; argv[i]; i++) { |
| len += strlen(argv[i]) + 3; |
| } |
| |
| str = ptr = (char *)malloc(len + 1); |
| if (str == NULL) |
| return NULL; |
| |
| for (i = 0; argv[i]; i++) { |
| len = strlen(argv[i]); |
| *ptr++ = '"'; |
| memcpy(ptr, argv[i], len); |
| ptr += len; |
| *ptr++ = '"'; |
| *ptr++ = ' '; |
| } |
| *ptr = 0; |
| |
| return str; |
| } |
| #endif |
| |
| /* |
| 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, |
| const char *path_stdout, |
| const char *path_stderr) |
| { |
| #ifdef _WIN32 |
| |
| #if 1 |
| PROCESS_INFORMATION pinfo; |
| STARTUPINFO sinfo; |
| BOOL ret; |
| DWORD exitcode; |
| char *args; |
| HANDLE fd_out, fd_err; |
| SECURITY_ATTRIBUTES sa = {sizeof(SECURITY_ATTRIBUTES), NULL, TRUE}; |
| |
| /* TODO: needs moving after possible exit() below, but before stdout is redirected */ |
| if (ccache_verbose) { |
| display_execute_args(argv); |
| } |
| |
| fd_out = CreateFile(path_stdout, GENERIC_WRITE, 0, &sa, CREATE_ALWAYS, |
| FILE_ATTRIBUTE_NORMAL, NULL); |
| if (fd_out == INVALID_HANDLE_VALUE) { |
| return STATUS_NOCACHE; |
| } |
| |
| fd_err = CreateFile(path_stderr, GENERIC_WRITE, 0, &sa, CREATE_ALWAYS, |
| FILE_ATTRIBUTE_NORMAL, NULL); |
| if (fd_err == INVALID_HANDLE_VALUE) { |
| return STATUS_NOCACHE; |
| } |
| |
| ZeroMemory(&pinfo, sizeof(PROCESS_INFORMATION)); |
| ZeroMemory(&sinfo, sizeof(STARTUPINFO)); |
| |
| sinfo.cb = sizeof(STARTUPINFO); |
| sinfo.hStdError = fd_err; |
| sinfo.hStdOutput = fd_out; |
| sinfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE); |
| sinfo.dwFlags |= STARTF_USESTDHANDLES; |
| |
| args = argvtos(argv); |
| |
| ret = CreateProcessA(argv[0], args, NULL, NULL, TRUE, 0, NULL, NULL, |
| &sinfo, &pinfo); |
| |
| free(args); |
| CloseHandle(fd_out); |
| CloseHandle(fd_err); |
| |
| if (ret == 0) |
| return -1; |
| |
| WaitForSingleObject(pinfo.hProcess, INFINITE); |
| GetExitCodeProcess(pinfo.hProcess, &exitcode); |
| CloseHandle(pinfo.hProcess); |
| CloseHandle(pinfo.hThread); |
| |
| return exitcode; |
| #else /* possibly slightly faster */ |
| /* needs fixing to quote commandline options to handle spaces in CCACHE_DIR etc */ |
| int status = -2; |
| int fd, std_od = -1, std_ed = -1; |
| |
| /* TODO: needs moving after possible exit() below, but before stdout is redirected */ |
| if (ccache_verbose) { |
| display_execute_args(argv); |
| } |
| |
| unlink(path_stdout); |
| std_od = _dup(1); |
| fd = _open(path_stdout, O_WRONLY|O_CREAT|O_TRUNC|O_EXCL|O_BINARY, 0666); |
| if (fd == -1) { |
| exit(STATUS_NOCACHE); |
| } |
| _dup2(fd, 1); |
| _close(fd); |
| |
| unlink(path_stderr); |
| fd = _open(path_stderr, O_WRONLY|O_CREAT|O_TRUNC|O_EXCL|O_BINARY, 0666); |
| std_ed = _dup(2); |
| if (fd == -1) { |
| exit(STATUS_NOCACHE); |
| } |
| _dup2(fd, 2); |
| _close(fd); |
| |
| /* Spawn process (_exec* family doesn't return) */ |
| status = _spawnv(_P_WAIT, argv[0], (const char **)argv); |
| |
| /* Restore descriptors */ |
| if (std_od != -1) _dup2(std_od, 1); |
| if (std_ed != -1) _dup2(std_ed, 2); |
| _flushall(); |
| |
| return (status>0); |
| |
| #endif |
| |
| #else |
| pid_t pid; |
| int status; |
| |
| pid = fork(); |
| if (pid == -1) fatal("Failed to fork"); |
| |
| if (pid == 0) { |
| int fd; |
| |
| /* TODO: needs moving after possible exit() below, but before stdout is redirected */ |
| if (ccache_verbose) { |
| display_execute_args(argv); |
| } |
| |
| unlink(path_stdout); |
| fd = open(path_stdout, O_WRONLY|O_CREAT|O_TRUNC|O_EXCL|O_BINARY, 0666); |
| if (fd == -1) { |
| exit(STATUS_NOCACHE); |
| } |
| dup2(fd, 1); |
| close(fd); |
| |
| unlink(path_stderr); |
| fd = open(path_stderr, O_WRONLY|O_CREAT|O_TRUNC|O_EXCL|O_BINARY, 0666); |
| if (fd == -1) { |
| exit(STATUS_NOCACHE); |
| } |
| dup2(fd, 2); |
| close(fd); |
| |
| exit(execv(argv[0], argv)); |
| } |
| |
| if (waitpid(pid, &status, 0) != pid) { |
| fatal("waitpid failed"); |
| } |
| |
| 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 _WIN32 |
| (void)exclude_name; |
| DWORD ret; |
| char namebuf[MAX_PATH]; |
| |
| ret = SearchPathA(getenv("CCACHE_PATH"), name, ".exe", |
| sizeof(namebuf), namebuf, NULL); |
| if (ret != 0) { |
| return x_strdup(namebuf); |
| } |
| |
| return NULL; |
| #else |
| char *path; |
| char *tok; |
| struct stat st1, st2; |
| |
| if (*name == '/') { |
| return x_strdup(name); |
| } |
| |
| path = getenv("CCACHE_PATH"); |
| if (!path) { |
| path = getenv("PATH"); |
| } |
| if (!path) { |
| cc_log("no PATH variable!?\n"); |
| stats_update(STATS_ENVIRONMMENT); |
| return NULL; |
| } |
| |
| path = x_strdup(path); |
| |
| /* search the path looking for the first compiler of the right name |
| that isn't us */ |
| for (tok=strtok(path,":"); tok; tok = strtok(NULL, ":")) { |
| char *fname; |
| x_asprintf(&fname, "%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 its a symlink then ensure it doesn't |
| point at something called exclude_name */ |
| if (S_ISLNK(st1.st_mode)) { |
| char *buf = x_realpath(fname); |
| if (buf) { |
| char *p = str_basename(buf); |
| if (strcmp(p, exclude_name) == 0) { |
| /* its a link to "ccache" ! */ |
| free(p); |
| free(buf); |
| continue; |
| } |
| free(buf); |
| free(p); |
| } |
| } |
| |
| /* found it! */ |
| free(path); |
| return fname; |
| } |
| free(fname); |
| } |
| free(path); |
| |
| return NULL; |
| #endif |
| } |
| |
| void display_execute_args(char **argv) |
| { |
| if (argv) { |
| printf("ccache executing: "); |
| while (*argv) { |
| printf("%s ", *argv); |
| ++argv; |
| } |
| printf("\n"); |
| fflush(stdout); |
| } |
| } |