// // Copyright 2015 The ANGLE Project Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // // test_utils_posix.cpp: Implementation of OS-specific functions for Posix systems #include "util/test_utils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common/debug.h" #include "common/platform.h" #include "common/system_utils.h" #if !defined(ANGLE_PLATFORM_FUCHSIA) # include #endif #if defined(ANGLE_PLATFORM_MACOS) # include #endif namespace angle { namespace { #if defined(ANGLE_PLATFORM_MACOS) // Argument to skip the file hooking step. Might be automatically added by InitMetalFileAPIHooking() constexpr char kSkipFileHookingArg[] = "--skip-file-hooking"; #endif struct ScopedPipe { ~ScopedPipe() { closeEndPoint(0); closeEndPoint(1); } void closeEndPoint(int index) { if (fds[index] >= 0) { close(fds[index]); fds[index] = -1; } } bool valid() const { return fds[0] != -1 || fds[1] != -1; } int fds[2] = { -1, -1, }; }; enum class ReadResult { NoData, GotData, }; ReadResult ReadFromFile(int fd, std::string *out) { constexpr size_t kBufSize = 2048; char buffer[kBufSize]; ssize_t bytesRead = read(fd, buffer, sizeof(buffer)); if (bytesRead < 0 && errno == EINTR) { return ReadResult::GotData; } if (bytesRead <= 0) { return ReadResult::NoData; } out->append(buffer, bytesRead); return ReadResult::GotData; } void ReadEntireFile(int fd, std::string *out) { while (ReadFromFile(fd, out) == ReadResult::GotData) { } } class PosixProcess : public Process { public: PosixProcess(const std::vector &commandLineArgs, ProcessOutputCapture captureOutput) { if (commandLineArgs.empty()) { return; } const bool captureStdout = captureOutput != ProcessOutputCapture::Nothing; const bool captureStderr = captureOutput == ProcessOutputCapture::StdoutAndStderrInterleaved || captureOutput == ProcessOutputCapture::StdoutAndStderrSeparately; const bool pipeStderrToStdout = captureOutput == ProcessOutputCapture::StdoutAndStderrInterleaved; // Create pipes for stdout and stderr. if (captureStdout) { if (pipe(mStdoutPipe.fds) != 0) { std::cerr << "Error calling pipe: " << errno << "\n"; return; } if (fcntl(mStdoutPipe.fds[0], F_SETFL, O_NONBLOCK) == -1) { std::cerr << "Error calling fcntl: " << errno << "\n"; return; } } if (captureStderr && !pipeStderrToStdout) { if (pipe(mStderrPipe.fds) != 0) { std::cerr << "Error calling pipe: " << errno << "\n"; return; } if (fcntl(mStderrPipe.fds[0], F_SETFL, O_NONBLOCK) == -1) { std::cerr << "Error calling fcntl: " << errno << "\n"; return; } } mPID = fork(); if (mPID < 0) { return; } mStarted = true; mTimer.start(); if (mPID == 0) { // Child. Execute the application. // Redirect stdout and stderr to the pipe fds. if (captureStdout) { if (dup2(mStdoutPipe.fds[1], STDOUT_FILENO) < 0) { _exit(errno); } mStdoutPipe.closeEndPoint(1); } if (pipeStderrToStdout) { if (dup2(STDOUT_FILENO, STDERR_FILENO) < 0) { _exit(errno); } } else if (captureStderr) { if (dup2(mStderrPipe.fds[1], STDERR_FILENO) < 0) { _exit(errno); } mStderrPipe.closeEndPoint(1); } // Execute the application, which doesn't return unless failed. Note: execv takes argv // as `char * const *` for historical reasons. It is safe to const_cast it: // // http://pubs.opengroup.org/onlinepubs/9699919799/functions/exec.html // // > The statement about argv[] and envp[] being constants is included to make explicit // to future writers of language bindings that these objects are completely constant. // Due to a limitation of the ISO C standard, it is not possible to state that idea in // standard C. Specifying two levels of const- qualification for the argv[] and envp[] // parameters for the exec functions may seem to be the natural choice, given that these // functions do not modify either the array of pointers or the characters to which the // function points, but this would disallow existing correct code. Instead, only the // array of pointers is noted as constant. std::vector args; for (const char *arg : commandLineArgs) { args.push_back(const_cast(arg)); } args.push_back(nullptr); execv(commandLineArgs[0], args.data()); std::cerr << "Error calling evecv: " << errno; _exit(errno); } // Parent continues execution. mStdoutPipe.closeEndPoint(1); mStderrPipe.closeEndPoint(1); } ~PosixProcess() override {} bool started() override { return mStarted; } bool finish() override { if (!mStarted) { return false; } if (mFinished) { return true; } while (!finished()) { angle::Sleep(1); } return true; } bool finished() override { if (!mStarted) { return false; } if (mFinished) { return true; } int status = 0; pid_t returnedPID = ::waitpid(mPID, &status, WNOHANG); if (returnedPID == -1 && errno != ECHILD) { std::cerr << "Error calling waitpid: " << ::strerror(errno) << "\n"; return true; } if (returnedPID == mPID) { mFinished = true; mTimer.stop(); readPipes(); mExitCode = WEXITSTATUS(status); return true; } if (mStdoutPipe.valid()) { ReadEntireFile(mStdoutPipe.fds[0], &mStdout); } if (mStderrPipe.valid()) { ReadEntireFile(mStderrPipe.fds[0], &mStderr); } return false; } int getExitCode() override { return mExitCode; } bool kill() override { if (!mStarted) { return false; } if (finished()) { return true; } return (::kill(mPID, SIGTERM) == 0); } private: void readPipes() { // Close the write end of the pipes, so EOF can be generated when child exits. // Then read back the output of the child. if (mStdoutPipe.valid()) { ReadEntireFile(mStdoutPipe.fds[0], &mStdout); } if (mStderrPipe.valid()) { ReadEntireFile(mStderrPipe.fds[0], &mStderr); } } bool mStarted = false; bool mFinished = false; ScopedPipe mStdoutPipe; ScopedPipe mStderrPipe; int mExitCode = 0; pid_t mPID = -1; }; std::string TempFileName() { return std::string(".angle.XXXXXX"); } } // anonymous namespace void Sleep(unsigned int milliseconds) { // On Windows Sleep(0) yields while it isn't guaranteed by Posix's sleep // so we replicate Windows' behavior with an explicit yield. if (milliseconds == 0) { sched_yield(); } else { long milliseconds_long = milliseconds; timespec sleepTime = { .tv_sec = milliseconds_long / 1000, .tv_nsec = (milliseconds_long % 1000) * 1000000, }; nanosleep(&sleepTime, nullptr); } } void SetLowPriorityProcess() { #if !defined(ANGLE_PLATFORM_FUCHSIA) setpriority(PRIO_PROCESS, getpid(), 10); #endif } void WriteDebugMessage(const char *format, ...) { va_list vararg; va_start(vararg, format); vfprintf(stderr, format, vararg); va_end(vararg); } bool StabilizeCPUForBenchmarking() { #if !defined(ANGLE_PLATFORM_FUCHSIA) bool success = true; errno = 0; setpriority(PRIO_PROCESS, getpid(), -20); if (errno) { // A friendly warning in case the test was run without appropriate permission. perror( "Warning: setpriority failed in StabilizeCPUForBenchmarking. Process will retain " "default priority"); success = false; } # if ANGLE_PLATFORM_LINUX cpu_set_t affinity; CPU_SET(0, &affinity); errno = 0; if (sched_setaffinity(getpid(), sizeof(affinity), &affinity)) { perror( "Warning: sched_setaffinity failed in StabilizeCPUForBenchmarking. Process will retain " "default affinity"); success = false; } # else // TODO(jmadill): Implement for non-linux. http://anglebug.com/2923 # endif return success; #else // defined(ANGLE_PLATFORM_FUCHSIA) return false; #endif } bool GetTempDir(char *tempDirOut, uint32_t maxDirNameLen) { const char *tmp = getenv("TMPDIR"); if (tmp) { strncpy(tempDirOut, tmp, maxDirNameLen); return true; } #if defined(ANGLE_PLATFORM_ANDROID) // Not used right now in the ANGLE test runner. // return PathService::Get(DIR_CACHE, path); return false; #else strncpy(tempDirOut, "/tmp", maxDirNameLen); return true; #endif } bool CreateTemporaryFileInDir(const char *dir, char *tempFileNameOut, uint32_t maxFileNameLen) { std::string tempFile = TempFileName(); sprintf(tempFileNameOut, "%s/%s", dir, tempFile.c_str()); int fd = mkstemp(tempFileNameOut); close(fd); return fd != -1; } bool DeleteFile(const char *path) { return unlink(path) == 0; } Process *LaunchProcess(const std::vector &args, ProcessOutputCapture captureOutput) { return new PosixProcess(args, captureOutput); } int NumberOfProcessors() { // sysconf returns the number of "logical" (not "physical") processors on both // Mac and Linux. So we get the number of max available "logical" processors. // // Note that the number of "currently online" processors may be fewer than the // returned value of NumberOfProcessors(). On some platforms, the kernel may // make some processors offline intermittently, to save power when system // loading is low. // // One common use case that needs to know the processor count is to create // optimal number of threads for optimization. It should make plan according // to the number of "max available" processors instead of "currently online" // ones. The kernel should be smart enough to make all processors online when // it has sufficient number of threads waiting to run. long res = sysconf(_SC_NPROCESSORS_CONF); if (res == -1) { return 1; } return static_cast(res); } const char *GetNativeEGLLibraryNameWithExtension() { #if defined(ANGLE_PLATFORM_ANDROID) return "libEGL.so"; #elif defined(ANGLE_PLATFORM_LINUX) return "libEGL.so.1"; #else return "unknown_libegl"; #endif } #if defined(ANGLE_PLATFORM_MACOS) void InitMetalFileAPIHooking(int argc, char **argv) { if (argc < 1) { return; } for (int i = 0; i < argc; ++i) { if (strncmp(argv[i], kSkipFileHookingArg, strlen(kSkipFileHookingArg)) == 0) { return; } } constexpr char kInjectLibVarName[] = "DYLD_INSERT_LIBRARIES"; constexpr size_t kInjectLibVarNameLen = sizeof(kInjectLibVarName) - 1; std::string exeDir = GetExecutableDirectory(); if (!exeDir.empty() && exeDir.back() != '/') { exeDir += "/"; } // Intercept Metal shader cache access and return as if the cache doesn't exist. // This is to avoid slow shader cache mechanism that caused the test timeout in the past. // In order to do that, we need to hook the file API functions by making sure // libmetal_shader_cache_file_hooking.dylib library is loaded first before any other libraries. std::string injectLibsVar = std::string(kInjectLibVarName) + "=" + exeDir + "libmetal_shader_cache_file_hooking.dylib"; char skipHookOption[sizeof(kSkipFileHookingArg)]; memcpy(skipHookOption, kSkipFileHookingArg, sizeof(kSkipFileHookingArg)); // Construct environment variables std::vector newEnv; char **environ = *_NSGetEnviron(); for (int i = 0; environ[i]; ++i) { if (strncmp(environ[i], kInjectLibVarName, kInjectLibVarNameLen) == 0) { injectLibsVar += ':'; injectLibsVar += environ[i] + kInjectLibVarNameLen + 1; } else { newEnv.push_back(environ[i]); } } newEnv.push_back(strdup(injectLibsVar.data())); newEnv.push_back(nullptr); // Construct arguments with kSkipFileHookingArg flag to skip the hooking after re-launching. std::vector newArgs; newArgs.push_back(argv[0]); newArgs.push_back(skipHookOption); for (int i = 1; i < argc; ++i) { newArgs.push_back(argv[i]); } newArgs.push_back(nullptr); // Re-launch the app with file API hooked. ASSERT(-1 != execve(argv[0], newArgs.data(), newEnv.data())); } #endif } // namespace angle