| // Copyright (C) 2015 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "android/base/system/System.h" |
| |
| #include "android/base/EintrWrapper.h" |
| #include "android/base/files/PathUtils.h" |
| #include "android/base/memory/LazyInstance.h" |
| #include "android/base/misc/StringUtils.h" |
| #include "android/base/StringFormat.h" |
| #include "android/base/threads/Thread.h" |
| |
| #ifdef _WIN32 |
| #include "android/base/system/Win32UnicodeString.h" |
| #include "android/base/system/Win32Utils.h" |
| #endif |
| |
| #ifdef _WIN32 |
| #include <shlobj.h> |
| #include <windows.h> |
| #include <shlobj.h> |
| #endif |
| |
| #ifdef __APPLE__ |
| #import <Carbon/Carbon.h> |
| #include <mach/clock.h> |
| #include <mach/mach.h> |
| #include <spawn.h> |
| #endif // __APPLE__ |
| |
| #include <algorithm> |
| #include <array> |
| #include <chrono> |
| #include <memory> |
| #include <vector> |
| |
| #ifndef _WIN32 |
| #include <fcntl.h> |
| #include <dirent.h> |
| #include <pwd.h> |
| #include <signal.h> |
| #include <sys/times.h> |
| #include <sys/types.h> |
| #include <sys/wait.h> |
| #include <time.h> |
| #endif |
| #include <assert.h> |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <sys/time.h> |
| |
| // This variable is a pointer to a zero-terminated array of all environment |
| // variables in the current process. |
| // Posix requires this to be declared as extern at the point of use |
| // NOTE: Apple developer manual states that this variable isn't available for |
| // the shared libraries, and one has to use the _NSGetEnviron() function instead |
| #ifdef __APPLE__ |
| #include <crt_externs.h> |
| #define environ (*_NSGetEnviron()) |
| #else |
| extern "C" char** environ; |
| #endif |
| |
| namespace android { |
| namespace base { |
| |
| using std::string; |
| using std::unique_ptr; |
| using std::vector; |
| |
| namespace { |
| |
| struct TickCountImpl { |
| private: |
| System::WallDuration mStartTimeUs; |
| #ifdef _WIN32 |
| long long mFreqPerSec = 0; // 0 means 'high perf counter isn't available' |
| #endif |
| |
| public: |
| TickCountImpl() { |
| #ifdef _WIN32 |
| LARGE_INTEGER freq; |
| if (::QueryPerformanceFrequency(&freq)) { |
| mFreqPerSec = freq.QuadPart; |
| } |
| #endif |
| mStartTimeUs = getUs(); |
| } |
| |
| System::WallDuration getStartTimeUs() const { |
| return mStartTimeUs; |
| } |
| |
| System::WallDuration getUs() const { |
| #ifdef _WIN32 |
| if (!mFreqPerSec) { |
| return ::GetTickCount() * 1000; |
| } |
| LARGE_INTEGER now; |
| ::QueryPerformanceCounter(&now); |
| return (now.QuadPart * 1000000ull) / mFreqPerSec; |
| #elif defined __linux__ |
| timespec ts; |
| clock_gettime(CLOCK_MONOTONIC, &ts); |
| return ts.tv_sec * 1000000ll + ts.tv_nsec / 1000; |
| #else // MAC |
| clock_serv_t clockServ; |
| mach_timespec_t mts; |
| host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &clockServ); |
| clock_get_time(clockServ, &mts); |
| mach_port_deallocate(mach_task_self(), clockServ); |
| |
| return mts.tv_sec * 1000000ll + mts.tv_nsec / 1000; |
| #endif |
| } |
| }; |
| |
| // This is, maybe, the only static variable that may not be a LazyInstance: |
| // it holds the actual timestamp at startup, and has to be initialized as |
| // soon as possible after the application launch. |
| static const TickCountImpl kTickCount; |
| |
| } // namespace |
| |
| #ifdef _WIN32 |
| // Check if we're currently running under Wine |
| static bool isRunningUnderWine() { |
| // this is the only good way of detecting Wine: it exports a function |
| // 'wine_get_version()' from its ntdll.dll |
| // Note: the typedef and casting here are for documentation purposes: |
| // if you need to get the actual Wine version, you just already know the |
| // type, calling convention and arguments. |
| using wineGetVersionFunc = const char* __attribute__((cdecl)) (); |
| |
| // Make sure we don't call FreeLibrary() for this handle as |
| // GetModuleHandle() doesn't increment the reference count |
| const HMODULE ntDll = ::GetModuleHandleW(L"ntdll.dll"); |
| if (!ntDll) { |
| // some strange version of Windows, definitely not Wine |
| return false; |
| } |
| |
| if (const auto wineGetVersion = reinterpret_cast<wineGetVersionFunc*>( |
| ::GetProcAddress(ntDll, "wine_get_version"))) { |
| return true; |
| } |
| return false; |
| } |
| |
| static bool extractFullPath(std::string* cmd) { |
| if (PathUtils::isAbsolute(*cmd)) { |
| return true; |
| } else { |
| // try searching %PATH% and current directory for the binary |
| const Win32UnicodeString name(*cmd); |
| const Win32UnicodeString extension(PathUtils::kExeNameSuffix); |
| Win32UnicodeString buffer(MAX_PATH); |
| |
| DWORD size = ::SearchPathW(nullptr, name.c_str(), extension.c_str(), |
| buffer.size() + 1, buffer.data(), nullptr); |
| if (size > buffer.size()) { |
| // function may ask for more space |
| buffer.resize(size); |
| size = ::SearchPathW(nullptr, name.c_str(), extension.c_str(), |
| buffer.size() + 1, buffer.data(), nullptr); |
| } |
| if (size == 0) { |
| // Couldn't find anything matching the passed name |
| return false; |
| } |
| if (buffer.size() != size) { |
| buffer.resize(size); |
| } |
| *cmd = buffer.toString(); |
| } |
| return true; |
| } |
| |
| #endif |
| |
| namespace { |
| |
| class HostSystem : public System { |
| public: |
| HostSystem() : mProgramDir(), mHomeDir(), mAppDataDir() {} |
| |
| virtual ~HostSystem() {} |
| |
| virtual const std::string& getProgramDirectory() const { |
| if (mProgramDir.empty()) { |
| #if defined(__linux__) |
| char path[1024]; |
| memset(path, 0, sizeof(path)); // happy valgrind! |
| int len = readlink("/proc/self/exe", path, sizeof(path)); |
| if (len > 0 && len < (int)sizeof(path)) { |
| char* x = ::strrchr(path, '/'); |
| if (x) { |
| *x = '\0'; |
| mProgramDir.assign(path); |
| } |
| } |
| #elif defined(__APPLE__) |
| ProcessSerialNumber psn; |
| GetCurrentProcess(&psn); |
| CFDictionaryRef dict = |
| ProcessInformationCopyDictionary(&psn, 0xffffffff); |
| CFStringRef value = (CFStringRef)CFDictionaryGetValue( |
| dict, CFSTR("CFBundleExecutable")); |
| char s[PATH_MAX]; |
| CFStringGetCString(value, s, PATH_MAX - 1, kCFStringEncodingUTF8); |
| char* x = ::strrchr(s, '/'); |
| if (x) { |
| // skip all slashes - there might be more than one |
| while (x > s && x[-1] == '/') { |
| --x; |
| } |
| *x = '\0'; |
| mProgramDir.assign(s); |
| } else { |
| mProgramDir.assign("<unknown-application-dir>"); |
| } |
| #elif defined(_WIN32) |
| Win32UnicodeString appDir(PATH_MAX); |
| int len = GetModuleFileNameW(0, appDir.data(), appDir.size()); |
| mProgramDir.assign("<unknown-application-dir>"); |
| if (len > 0) { |
| if (len > (int)appDir.size()) { |
| appDir.resize(static_cast<size_t>(len)); |
| GetModuleFileNameW(0, appDir.data(), appDir.size()); |
| } |
| std::string dir = appDir.toString(); |
| char* sep = ::strrchr(&dir[0], '\\'); |
| if (sep) { |
| *sep = '\0'; |
| mProgramDir.assign(dir.c_str()); |
| } |
| } |
| #else |
| #error "Unsupported platform!" |
| #endif |
| } |
| return mProgramDir; |
| } |
| |
| virtual std::string getCurrentDirectory() const { |
| #if defined(_WIN32) |
| int currentLen = GetCurrentDirectoryW(0, nullptr); |
| if (currentLen < 0) { |
| // Could not get size of working directory. Something is really |
| // fishy here, return an empty string. |
| return std::string(); |
| } |
| wchar_t* currentDir = |
| static_cast<wchar_t*>(calloc(currentLen + 1, sizeof(wchar_t))); |
| if (!GetCurrentDirectoryW(currentLen + 1, currentDir)) { |
| // Again, some unexpected problem. Can't do much here. |
| // Make the string empty. |
| currentDir[0] = L'0'; |
| } |
| |
| std::string result = Win32UnicodeString::convertToUtf8(currentDir); |
| ::free(currentDir); |
| return result; |
| #else // !_WIN32 |
| char currentDir[PATH_MAX]; |
| if (!getcwd(currentDir, sizeof(currentDir))) { |
| return std::string(); |
| } |
| return std::string(currentDir); |
| #endif // !_WIN32 |
| } |
| |
| virtual const std::string& getLauncherDirectory() const { |
| if (mLauncherDir.empty()) { |
| std::string launcherDirEnv = envGet("ANDROID_EMULATOR_LAUNCHER_DIR"); |
| if (!launcherDirEnv.empty()) { |
| mLauncherDir = launcherDirEnv; |
| return mLauncherDir; |
| } |
| std::string programDir = getProgramDirectory(); |
| |
| std::string launcherName("emulator"); |
| #ifdef _WIN32 |
| launcherName += ".exe"; |
| #endif |
| std::vector<std::string> pathList = {programDir, launcherName}; |
| std::string launcherPath = PathUtils::recompose(pathList); |
| |
| if (pathIsFile(launcherPath)) { |
| mLauncherDir = programDir; |
| return mLauncherDir; |
| } |
| |
| // we are probably executing a qemu2 binary, which live in |
| // <launcher-dir>/qemu/<os>-<arch>/ |
| // look for the launcher in grandparent directory |
| std::vector<std::string> programDirVector = |
| PathUtils::decompose(programDir); |
| if (programDirVector.size() >= 2) { |
| programDirVector.resize(programDirVector.size() - 2); |
| std::string grandparentDir = PathUtils::recompose(programDirVector); |
| programDirVector.push_back(launcherName); |
| std::string launcherPath = PathUtils::recompose(programDirVector); |
| if (pathIsFile(launcherPath)) { |
| mLauncherDir = grandparentDir; |
| return mLauncherDir; |
| } |
| } |
| |
| mLauncherDir.assign("<unknown-launcher-dir>"); |
| } |
| return mLauncherDir; |
| } |
| |
| virtual const std::string& getHomeDirectory() const { |
| if (mHomeDir.empty()) { |
| #if defined(_WIN32) |
| // NOTE: SHGetFolderPathW always takes a buffer of MAX_PATH size, |
| // so don't use a Win32UnicodeString here to avoid un-necessary |
| // dynamic allocation. |
| wchar_t path[MAX_PATH] = {0}; |
| // Query Windows shell for known folder paths. |
| // SHGetFolderPath acts as a wrapper to KnownFolders; |
| // this is preferred for simplicity and XP compatibility. |
| if (SUCCEEDED( |
| SHGetFolderPathW(NULL, CSIDL_PROFILE, NULL, 0, path))) { |
| mHomeDir = Win32UnicodeString::convertToUtf8(path); |
| } else { |
| // Fallback to windows-equivalent of HOME env var |
| std::string homedrive = envGet("HOMEDRIVE"); |
| std::string homepath = envGet("HOMEPATH"); |
| if (!homedrive.empty() && !homepath.empty()) { |
| mHomeDir.assign(homedrive); |
| mHomeDir.append(homepath); |
| } |
| } |
| #elif defined(__linux__) || (__APPLE__) |
| // Try getting HOME from env first |
| const char* home = getenv("HOME"); |
| if (home != NULL) { |
| mHomeDir.assign(home); |
| } else { |
| // If env HOME appears empty for some reason, |
| // try getting HOME by querying system password database |
| const struct passwd *pw = getpwuid(getuid()); |
| if (pw != NULL && pw->pw_dir != NULL) { |
| mHomeDir.assign(pw->pw_dir); |
| } |
| } |
| #else |
| #error "Unsupported platform!" |
| #endif |
| } |
| return mHomeDir; |
| } |
| |
| virtual const std::string& getAppDataDirectory() const { |
| #if defined(_WIN32) |
| if (mAppDataDir.empty()) { |
| // NOTE: See comment in getHomeDirectory(). |
| wchar_t path[MAX_PATH] = {0}; |
| if (SUCCEEDED( |
| SHGetFolderPathW(NULL, CSIDL_APPDATA, NULL, 0, path))) { |
| mAppDataDir = Win32UnicodeString::convertToUtf8(path); |
| } else { |
| const wchar_t* appdata = _wgetenv(L"APPDATA"); |
| if(appdata != NULL) { |
| mAppDataDir = Win32UnicodeString::convertToUtf8(appdata); |
| } |
| } |
| } |
| #elif defined (__APPLE__) |
| if (mAppDataDir.empty()) { |
| // The equivalent of AppData directory in MacOS X is |
| // under ~/Library/Preferences. Apple does not offer |
| // a C/C++ API to query this location (in ObjC cocoa |
| // applications NSSearchPathForDirectoriesInDomains |
| // can be used), so we apply the common practice of |
| // hard coding it |
| mAppDataDir.assign(getHomeDirectory()); |
| mAppDataDir.append("/Library/Preferences"); |
| } |
| #elif defined(__linux__) |
| ; // not applicable |
| #else |
| #error "Unsupported platform!" |
| #endif |
| return mAppDataDir; |
| } |
| |
| |
| virtual int getHostBitness() const { |
| #ifdef _WIN32 |
| |
| // Retrieves the path of the WOW64 system directory, which doesn't |
| // exist on 32-bit systems. |
| // NB: we don't really need the directory, we just want to see if |
| // Windows has it - so let's not even try to pass a buffer that |
| // is long enough; return value is the required buffer length. |
| std::array<wchar_t, 1> directory; |
| const unsigned len = GetSystemWow64DirectoryW( |
| directory.data(), |
| static_cast<unsigned>(directory.size())); |
| if (len == 0) { |
| return 32; |
| } else { |
| return 64; |
| } |
| |
| #else // !_WIN32 |
| /* |
| This function returns 64 if host is running 64-bit OS, or 32 otherwise. |
| |
| It uses the same technique in ndk/build/core/ndk-common.sh. |
| Here are comments from there: |
| |
| ## On Linux or Darwin, a 64-bit kernel (*) doesn't mean that the |
| ## user-land is always 32-bit, so use "file" to determine the bitness |
| ## of the shell that invoked us. The -L option is used to de-reference |
| ## symlinks. |
| ## |
| ## Note that on Darwin, a single executable can contain both x86 and |
| ## x86_64 machine code, so just look for x86_64 (darwin) or x86-64 |
| ## (Linux) in the output. |
| |
| (*) ie. The following code doesn't always work: |
| struct utsname u; |
| int host_runs_64bit_OS = (uname(&u) == 0 && |
| strcmp(u.machine, "x86_64") == 0); |
| */ |
| if (system("file -L \"$SHELL\" | grep -q \"x86[_-]64\"") == 0) { |
| return 64; |
| } else if (system("file -L \"$SHELL\" > /dev/null")) { |
| fprintf(stderr, "WARNING: Cannot decide host bitness because " |
| "$SHELL is not properly defined; 32 bits assumed.\n"); |
| } |
| return 32; |
| |
| #endif // !_WIN32 |
| } |
| |
| virtual OsType getOsType() const override { |
| #ifdef _WIN32 |
| return OsType::Windows; |
| #elif defined(__APPLE__) |
| return OsType::Mac; |
| #elif defined(__linux__) |
| return OsType::Linux; |
| #else |
| #error getOsType(): unsupported OS; |
| #endif |
| } |
| |
| virtual bool isRunningUnderWine() const override { |
| #ifndef _WIN32 |
| return false; |
| #else |
| static const bool isUnderWine = android::base::isRunningUnderWine(); |
| return isUnderWine; |
| #endif |
| } |
| |
| System::Pid getCurrentProcessId() const override |
| { |
| #ifdef _WIN32 |
| return ::GetCurrentProcessId(); |
| #else |
| return getpid(); |
| #endif |
| } |
| |
| virtual std::vector<std::string> scanDirEntries( |
| StringView dirPath, |
| bool fullPath = false) const { |
| std::vector<std::string> result = scanDirInternal(dirPath); |
| if (fullPath) { |
| // Pre-pend |dirPath| to each entry. |
| std::string prefix = PathUtils::addTrailingDirSeparator(dirPath); |
| for (size_t n = 0; n < result.size(); ++n) { |
| std::string path = prefix; |
| path.append(result[n]); |
| result[n] = path; |
| } |
| } |
| return result; |
| } |
| |
| virtual std::string envGet(StringView varname) const { |
| #ifdef _WIN32 |
| Win32UnicodeString varname_unicode(varname); |
| const wchar_t* value = _wgetenv(varname_unicode.c_str()); |
| if (!value) { |
| return std::string(); |
| } else { |
| return Win32UnicodeString::convertToUtf8(value); |
| } |
| #else |
| const char* value = getenv(varname.c_str()); |
| if (!value) { |
| value = ""; |
| } |
| return std::string(value); |
| #endif |
| } |
| |
| virtual void envSet(StringView varname, StringView varvalue) { |
| #ifdef _WIN32 |
| std::string envStr = |
| StringFormat("%s=%s", varname, varvalue); |
| // Note: this leaks the result of release(). |
| _wputenv(Win32UnicodeString(envStr).release()); |
| #else |
| if (varvalue.empty()) { |
| unsetenv(varname.c_str()); |
| } else { |
| setenv(varname.c_str(), varvalue.c_str(), 1); |
| } |
| #endif |
| } |
| |
| virtual bool envTest(StringView varname) const { |
| #ifdef _WIN32 |
| Win32UnicodeString varname_unicode(varname); |
| const wchar_t* value = _wgetenv(varname_unicode.c_str()); |
| return value && value[0] != L'\0'; |
| #else |
| const char* value = getenv(varname.c_str()); |
| return value && value[0] != '\0'; |
| #endif |
| } |
| |
| virtual std::vector<std::string> envGetAll() const override { |
| std::vector<std::string> res; |
| for (auto env = environ; env && *env; ++env) { |
| res.push_back(*env); |
| } |
| return res; |
| } |
| |
| virtual bool isRemoteSession(std::string* sessionType) const { |
| if (envTest("NX_TEMP")) { |
| if (sessionType) { |
| *sessionType = "NX"; |
| } |
| return true; |
| } |
| if (envTest("CHROME_REMOTE_DESKTOP_SESSION")) { |
| if (sessionType) { |
| *sessionType = "Chrome Remote Desktop"; |
| } |
| return true; |
| } |
| #ifdef _WIN32 |
| if (GetSystemMetrics(SM_REMOTESESSION)) { |
| if (sessionType) { |
| *sessionType = "Windows Remote Desktop"; |
| } |
| return true; |
| } |
| #endif // _WIN32 |
| return false; |
| } |
| |
| virtual bool pathExists(StringView path) const { |
| return pathExistsInternal(path); |
| } |
| |
| virtual bool pathIsFile(StringView path) const { |
| return pathIsFileInternal(path); |
| } |
| |
| virtual bool pathIsDir(StringView path) const { |
| return pathIsDirInternal(path); |
| } |
| |
| virtual bool pathCanRead(StringView path) const override { |
| return pathCanReadInternal(path); |
| } |
| |
| virtual bool pathCanWrite(StringView path) const override { |
| return pathCanWriteInternal(path); |
| } |
| |
| virtual bool pathCanExec(StringView path) const override { |
| return pathCanExecInternal(path); |
| } |
| |
| virtual bool deleteFile(StringView path) const override { |
| return deleteFileInternal(path); |
| } |
| |
| virtual bool pathFileSize(StringView path, |
| FileSize* outFileSize) const override { |
| return pathFileSizeInternal(path, outFileSize); |
| } |
| |
| Times getProcessTimes() const override { |
| Times res = {}; |
| |
| #ifdef _WIN32 |
| FILETIME creationTime = {}; |
| FILETIME exitTime = {}; |
| FILETIME kernelTime = {}; |
| FILETIME userTime = {}; |
| ::GetProcessTimes(::GetCurrentProcess(), &creationTime, &exitTime, |
| &kernelTime, &userTime); |
| |
| // convert 100-ns intervals from a struct to int64_t milliseconds |
| ULARGE_INTEGER kernelInt64; |
| kernelInt64.LowPart = kernelTime.dwLowDateTime; |
| kernelInt64.HighPart = kernelTime.dwHighDateTime; |
| res.systemMs = static_cast<Duration>(kernelInt64.QuadPart / 10000); |
| |
| ULARGE_INTEGER userInt64; |
| userInt64.LowPart = userTime.dwLowDateTime; |
| userInt64.HighPart = userTime.dwHighDateTime; |
| res.userMs = static_cast<Duration>(userInt64.QuadPart / 10000); |
| #else |
| tms times = {}; |
| ::times(×); |
| // convert to milliseconds |
| const long int ticksPerSec = ::sysconf(_SC_CLK_TCK); |
| res.systemMs = (times.tms_stime * 1000ll) / ticksPerSec; |
| res.userMs = (times.tms_utime * 1000ll) / ticksPerSec; |
| #endif |
| res.wallClockMs = |
| (kTickCount.getUs() - kTickCount.getStartTimeUs()) / 1000; |
| |
| return res; |
| } |
| |
| time_t getUnixTime() const override { |
| return time(NULL); |
| } |
| |
| Duration getUnixTimeUs() const override { |
| timeval tv; |
| gettimeofday(&tv, nullptr); |
| return tv.tv_sec * 1000000LL + tv.tv_usec; |
| } |
| |
| WallDuration getHighResTimeUs() const override { |
| return kTickCount.getUs(); |
| } |
| |
| void sleepMs(unsigned n) const override { |
| Thread::sleepMs(n); |
| } |
| |
| void yield() const override { |
| Thread::yield(); |
| } |
| |
| bool runCommand(const std::vector<std::string>& commandLine, |
| RunOptions options, |
| System::Duration timeoutMs, |
| System::ProcessExitCode* outExitCode, |
| System::Pid* outChildPid, |
| const std::string& outputFile) override { |
| // Sanity check. |
| if (commandLine.empty()) { |
| return false; |
| } |
| |
| #ifdef _WIN32 |
| std::vector<std::string> commandLineCopy = commandLine; |
| STARTUPINFOW startup = {}; |
| startup.cb = sizeof(startup); |
| |
| if (!extractFullPath(&commandLineCopy[0])) { |
| return false; |
| } |
| |
| if ((options & RunOptions::ShowOutput) == 0 || |
| ((options & RunOptions::DumpOutputToFile) != 0 && |
| !outputFile.empty())) { |
| startup.dwFlags = STARTF_USESHOWWINDOW; |
| |
| // 'Normal' way of hiding console output is passing null std handles |
| // to the CreateProcess() function and CREATE_NO_WINDOW as a flag. |
| // Sadly, in this case Cygwin runtime goes completely mad - its |
| // whole FILE* machinery just stops working. E.g., resize2fs always |
| // creates corrupted images if you try doing it in a 'normal' way. |
| // So, instead, we do the following: run the command in a cmd.exe |
| // with stdout and stderr redirected to either nul (for no output) |
| // or the specified file (for file output). |
| |
| // 1. Find the commmand-line interpreter - which hides behind the |
| // %COMSPEC% environment variable |
| std::string comspec = envGet("COMSPEC"); |
| if (comspec.empty()) { |
| comspec = "cmd.exe"; |
| } |
| |
| if (!extractFullPath(&comspec)) { |
| return false; |
| } |
| |
| // 2. Now turn the command into the proper cmd command: |
| // cmd.exe /C "command" "arguments" ... >nul 2>&1 |
| // This executes a command with arguments passed and redirects |
| // stdout to nul, stderr is attached to stdout (so it also |
| // goes to nul) |
| commandLineCopy.insert(commandLineCopy.begin(), "/C"); |
| commandLineCopy.insert(commandLineCopy.begin(), comspec); |
| |
| if ((options & RunOptions::DumpOutputToFile) != RunOptions::Empty) { |
| commandLineCopy.push_back(">"); |
| commandLineCopy.push_back(outputFile); |
| commandLineCopy.push_back("2>&1"); |
| } else if ((options & RunOptions::ShowOutput) == RunOptions::Empty) { |
| commandLineCopy.push_back(">nul"); |
| commandLineCopy.push_back("2>&1"); |
| } |
| } |
| |
| PROCESS_INFORMATION pinfo = {}; |
| |
| std::string args = commandLineCopy[0]; |
| for (size_t i = 1; i < commandLineCopy.size(); ++i) { |
| args += ' '; |
| args += android::base::Win32Utils::quoteCommandLine(commandLineCopy[i]); |
| } |
| |
| Win32UnicodeString commandUnicode(commandLineCopy[0]); |
| Win32UnicodeString argsUnicode(args); |
| |
| if (!::CreateProcessW(commandUnicode.c_str(), // program path |
| argsUnicode.data(), // command line args, has to be writable |
| nullptr, // process handle is not inheritable |
| nullptr, // thread handle is not inheritable |
| FALSE, // no, don't inherit any handles |
| 0, // default creation flags |
| nullptr, // use parent's environment block |
| nullptr, // use parent's starting directory |
| &startup, // startup info, i.e. std handles |
| &pinfo)) { |
| return false; |
| } |
| |
| CloseHandle(pinfo.hThread); |
| // make sure we close the process handle on exit |
| const android::base::Win32Utils::ScopedHandle process(pinfo.hProcess); |
| |
| if (outChildPid) { |
| *outChildPid = pinfo.dwProcessId; |
| } |
| |
| if ((options & RunOptions::WaitForCompletion) == 0) { |
| return true; |
| } |
| |
| // We were requested to wait for the process to complete. |
| DWORD ret = ::WaitForSingleObject(pinfo.hProcess, |
| timeoutMs ? timeoutMs : INFINITE); |
| if (ret == WAIT_FAILED || ret == WAIT_TIMEOUT) { |
| if ((options & RunOptions::TerminateOnTimeout) != 0) { |
| ::TerminateProcess(pinfo.hProcess, 1); |
| } |
| return false; |
| } |
| |
| DWORD exitCode; |
| auto exitCodeSuccess = ::GetExitCodeProcess(pinfo.hProcess, &exitCode); |
| assert(exitCodeSuccess); |
| (void)exitCodeSuccess; |
| if (outExitCode) { |
| *outExitCode = exitCode; |
| } |
| return true; |
| |
| #else // !_WIN32 |
| sigset_t oldset; |
| sigset_t set; |
| if (sigemptyset(&set) || sigaddset(&set, SIGCHLD) || |
| pthread_sigmask(SIG_UNBLOCK, &set, &oldset)) { |
| return false; |
| } |
| auto result = runCommandPosix(commandLine, options, timeoutMs, |
| outExitCode, outChildPid, outputFile); |
| pthread_sigmask(SIG_SETMASK, &oldset, nullptr); |
| return result; |
| #endif // !_WIN32 |
| } |
| |
| virtual std::string getTempDir() const { |
| #ifdef _WIN32 |
| Win32UnicodeString path(PATH_MAX); |
| DWORD retval = GetTempPathW(path.size(), path.data()); |
| if (retval > path.size()) { |
| path.resize(static_cast<size_t>(retval)); |
| retval = GetTempPathW(path.size(), path.data()); |
| } |
| if (!retval) { |
| // Best effort! |
| return std::string("C:\\Temp"); |
| } |
| path.resize(retval); |
| // The result of GetTempPath() is already user-dependent |
| // so don't append the username or userid to the result. |
| path.append(L"\\AndroidEmulator"); |
| ::_wmkdir(path.c_str()); |
| return path.toString(); |
| #else // !_WIN32 |
| std::string result; |
| const char* tmppath = getenv("ANDROID_TMP"); |
| if (!tmppath) { |
| const char* user = getenv("USER"); |
| if (user == NULL || user[0] == '\0') { |
| user = "unknown"; |
| } |
| result = "/tmp/android-"; |
| result += user; |
| } else { |
| result = tmppath; |
| } |
| ::mkdir(result.c_str(), 0744); |
| return result; |
| #endif // !_WIN32 |
| } |
| |
| #ifndef _WIN32 |
| bool runCommandPosix(const std::vector<std::string>& commandLine, |
| RunOptions options, |
| System::Duration timeoutMs, |
| System::ProcessExitCode* outExitCode, |
| System::Pid* outChildPid, |
| const std::string& outputFile) { |
| vector<char*> params; |
| for (const auto& item : commandLine) { |
| params.push_back(const_cast<char*>(item.c_str())); |
| } |
| params.push_back(nullptr); |
| |
| std::string cmd = ""; |
| if(LOG_IS_ON(VERBOSE)) { |
| cmd = "|"; |
| for (const auto& param : commandLine) { |
| cmd += param; |
| cmd += " "; |
| } |
| cmd += "|"; |
| } |
| |
| #if defined(__APPLE__) |
| int pid = runViaPosixSpawn(commandLine[0].c_str(), params, options, |
| outputFile); |
| #else |
| int pid = runViaForkAndExec(commandLine[0].c_str(), params, options, |
| outputFile); |
| #endif // !defined(__APPLE__) |
| |
| if (pid < 0) { |
| LOG(VERBOSE) << "Failed to fork for command " << cmd; |
| return false; |
| } |
| |
| if (outChildPid) { |
| *outChildPid = pid; |
| } |
| |
| if ((options & RunOptions::WaitForCompletion) == 0) { |
| return true; |
| } |
| |
| // We were requested to wait for the process to complete. |
| int exitCode; |
| // Do not use SIGCHLD here because we're not sure if we're |
| // running on the main thread and/or what our sigmask is. |
| if (timeoutMs == kInfinite) { |
| // Let's just wait forever and hope that the child process |
| // exits. |
| HANDLE_EINTR(waitpid(pid, &exitCode, 0)); |
| if (outExitCode) { |
| *outExitCode = WEXITSTATUS(exitCode); |
| } |
| return WIFEXITED(exitCode); |
| } |
| |
| auto startTime = std::chrono::steady_clock::now(); |
| auto elapsed = std::chrono::milliseconds::zero(); |
| while (elapsed.count() < timeoutMs) { |
| pid_t waitPid = HANDLE_EINTR(waitpid(pid, &exitCode, WNOHANG)); |
| if (waitPid < 0) { |
| auto local_errno = errno; |
| LOG(VERBOSE) << "Error running command " << cmd |
| << ". waitpid failed with |" |
| << strerror(local_errno) << "|"; |
| return false; |
| } |
| |
| if (waitPid > 0) { |
| if (outExitCode) { |
| *outExitCode = WEXITSTATUS(exitCode); |
| } |
| return WIFEXITED(exitCode); |
| } |
| |
| sleepMs(10); |
| elapsed = std::chrono::duration_cast<std::chrono::milliseconds>( |
| std::chrono::steady_clock::now() - startTime); |
| } |
| |
| // Timeout occured. |
| if ((options & RunOptions::TerminateOnTimeout) != 0) { |
| kill(pid, SIGKILL); |
| waitpid(pid, nullptr, WNOHANG); |
| } |
| LOG(VERBOSE) << "Timed out with running command " << cmd; |
| return false; |
| } |
| |
| int runViaForkAndExec(const char* command, |
| const vector<char*>& params, |
| RunOptions options, |
| const string& outputFile) { |
| // If an output file was requested, open it before forking, since |
| // creating a file in the child of a multi-threaded process is sketchy. |
| // |
| // It will be immediately closed in the parent process, and dup2'd into |
| // stdout and stderr in the child process. |
| int outputFd = 0; |
| if ((options & RunOptions::DumpOutputToFile) != RunOptions::Empty) { |
| if (outputFile.empty()) { |
| LOG(VERBOSE) << "Can not redirect output to empty file!"; |
| return -1; |
| } |
| |
| // Ensure the umask doesn't get in the way while creating the |
| // output file. |
| mode_t old = umask(0); |
| outputFd = open(outputFile.c_str(), O_WRONLY | O_CREAT | O_TRUNC, |
| 0700); |
| umask(old); |
| if (outputFd < 0) { |
| LOG(VERBOSE) << "Failed to open file to redirect stdout/stderr"; |
| return -1; |
| } |
| } |
| |
| int pid = fork(); |
| |
| if (pid != 0) { |
| if (outputFd > 0) { |
| close(outputFd); |
| } |
| // Return the child's pid / error code to parent process. |
| return pid; |
| } |
| |
| // In the child process. |
| // Do not do __anything__ except execve. That includes printing to |
| // stdout/stderr. None of it is safe in the child process forked from a |
| // parent with multiple threads. |
| if ((options & RunOptions::DumpOutputToFile) != RunOptions::Empty) { |
| dup2(outputFd, 1); |
| dup2(outputFd, 2); |
| close(outputFd); |
| } else if ((options & RunOptions::ShowOutput) == RunOptions::Empty) { |
| // We were requested to RunOptions::HideAllOutput |
| int fd = open("/dev/null", O_WRONLY); |
| if (fd > 0) { |
| dup2(fd, 1); |
| dup2(fd, 2); |
| close(fd); |
| } |
| } |
| |
| // We never want to forward our stdin to the child process. On the other |
| // hand, closing it can confuse some programs. |
| int fd = open("/dev/null", O_RDONLY); |
| if (fd > 0) { |
| dup2(fd, 0); |
| close(fd); |
| } |
| |
| if (execvp(command, params.data()) == -1) { |
| // emulator doesn't really like exit calls from a forked process |
| // (it just hangs), so let's just kill it |
| if (raise(SIGKILL) != 0) { |
| exit(RunFailed); |
| } |
| } |
| // Should not happen, but let's keep the compiler happy |
| return -1; |
| } |
| |
| #if defined(__APPLE__) |
| int runViaPosixSpawn(const char* command, |
| const vector<char*>& params, |
| RunOptions options, |
| const string& outputFile) { |
| posix_spawnattr_t attr; |
| if (posix_spawnattr_init(&attr)) { |
| LOG(VERBOSE) << "Failed to initialize spawnattr obj."; |
| return -1; |
| } |
| // Automatically destroy the successfully initialized attr. |
| auto attrDeleter = [](posix_spawnattr_t* t) { |
| posix_spawnattr_destroy(t); |
| }; |
| unique_ptr<posix_spawnattr_t, decltype(attrDeleter)> scopedAttr( |
| &attr, attrDeleter); |
| |
| if (posix_spawnattr_setflags(&attr, POSIX_SPAWN_CLOEXEC_DEFAULT)) { |
| LOG(VERBOSE) << "Failed to request CLOEXEC."; |
| return -1; |
| } |
| |
| posix_spawn_file_actions_t fileActions; |
| if (posix_spawn_file_actions_init(&fileActions)) { |
| LOG(VERBOSE) << "Failed to initialize fileactions obj."; |
| return -1; |
| } |
| // Automatically destroy the successfully initialized fileActions. |
| auto fileActionsDeleter = [](posix_spawn_file_actions_t* t) { |
| posix_spawn_file_actions_destroy(t); |
| }; |
| unique_ptr<posix_spawn_file_actions_t, decltype(fileActionsDeleter)> |
| scopedFileActions(&fileActions, fileActionsDeleter); |
| |
| if ((options & RunOptions::DumpOutputToFile) != RunOptions::Empty) { |
| if (posix_spawn_file_actions_addopen( |
| &fileActions, 1, outputFile.c_str(), |
| O_WRONLY | O_CREAT | O_TRUNC, 0700) || |
| posix_spawn_file_actions_addopen( |
| &fileActions, 2, outputFile.c_str(), |
| O_WRONLY | O_CREAT | O_TRUNC, 0700)) { |
| LOG(VERBOSE) << "Failed to redirect child output to file " |
| << outputFile; |
| return -1; |
| } |
| } else if ((options & RunOptions::ShowOutput) != RunOptions::Empty) { |
| if (posix_spawn_file_actions_addinherit_np(&fileActions, 1) || |
| posix_spawn_file_actions_addinherit_np(&fileActions, 2)) { |
| LOG(VERBOSE) << "Failed to request child stdout/stderr to be " |
| "left intact"; |
| return -1; |
| } |
| } else { |
| if (posix_spawn_file_actions_addopen(&fileActions, 1, "/dev/null", |
| O_WRONLY, 0700) || |
| posix_spawn_file_actions_addopen(&fileActions, 2, "/dev/null", |
| O_WRONLY, 0700)) { |
| LOG(VERBOSE) << "Failed to redirect child output to /dev/null"; |
| return -1; |
| } |
| } |
| |
| // We never want to forward our stdin to the child process. On the other |
| // hand, closing it can confuse some programs. |
| if (posix_spawn_file_actions_addopen(&fileActions, 0, "/dev/null", |
| O_RDONLY, 0700)) { |
| LOG(VERBOSE) << "Failed to redirect child stdin from /dev/null"; |
| return -1; |
| } |
| |
| // Posix spawn requires that argv[0] exists. |
| assert(params[0] != nullptr); |
| |
| int pid; |
| if (int error_code = posix_spawnp(&pid, command, &fileActions, &attr, |
| params.data(), environ)) { |
| LOG(VERBOSE) << "posix_spawnp failed: " << strerror(error_code); |
| return -1; |
| } |
| return pid; |
| } |
| #endif // defined(__APPLE__) |
| #endif // !_WIN32 |
| |
| private: |
| mutable std::string mProgramDir; |
| mutable std::string mLauncherDir; |
| mutable std::string mHomeDir; |
| mutable std::string mAppDataDir; |
| }; |
| |
| LazyInstance<HostSystem> sHostSystem = LAZY_INSTANCE_INIT; |
| System* sSystemForTesting = NULL; |
| |
| #ifdef _WIN32 |
| // Return |path| as a Unicode string, while discarding trailing separators. |
| Win32UnicodeString win32Path(StringView path) { |
| Win32UnicodeString wpath(path); |
| // Get rid of trailing directory separators, Windows doesn't like them. |
| size_t size = wpath.size(); |
| while (size > 0U && |
| (wpath[size - 1U] == L'\\' || wpath[size - 1U] == L'/')) { |
| size--; |
| } |
| if (size < wpath.size()) { |
| wpath.resize(size); |
| } |
| return wpath; |
| } |
| |
| using PathStat = struct _stat; |
| |
| #else // _WIN32 |
| |
| using PathStat = struct stat; |
| |
| #endif // _WIN32 |
| |
| int pathStat(StringView path, PathStat* st) { |
| #ifdef _WIN32 |
| return _wstat(win32Path(path).c_str(), st); |
| #else // !_WIN32 |
| return HANDLE_EINTR(stat(path.c_str(), st)); |
| #endif // !_WIN32 |
| } |
| |
| int pathAccess(StringView path, int mode) { |
| #ifdef _WIN32 |
| // Convert |mode| to win32 permission bits. |
| int win32mode = 0x0; |
| if ((mode & R_OK) || (mode & X_OK)) { |
| win32mode |= 0x4; |
| } |
| if (mode & W_OK) { |
| win32mode |= 0x2; |
| } |
| return _waccess(win32Path(path).c_str(), win32mode); |
| #else // !_WIN32 |
| return HANDLE_EINTR(access(path.c_str(), mode)); |
| #endif // !_WIN32 |
| } |
| |
| } // namespace |
| |
| // static |
| System* System::get() { |
| System* result = sSystemForTesting; |
| if (!result) { |
| result = sHostSystem.ptr(); |
| } |
| return result; |
| } |
| |
| #ifdef __x86_64__ |
| // static |
| const char* System::kLibSubDir = "lib64"; |
| // static |
| const char* System::kBinSubDir = "bin64"; |
| #else |
| // static |
| const char* System::kLibSubDir = "lib"; |
| // static |
| const char* System::kBinSubDir = "bin"; |
| #endif |
| |
| const char* System::kBin32SubDir = "bin"; |
| |
| // These need to be defined so one can take an address of them. |
| const int System::kProgramBitness; |
| const char System::kDirSeparator; |
| const char System::kPathSeparator; |
| |
| #ifdef _WIN32 |
| // static |
| const char* System::kLibrarySearchListEnvVarName = "PATH"; |
| #elif defined(__APPLE__) |
| const char* System::kLibrarySearchListEnvVarName = "DYLD_LIBRARY_PATH"; |
| #else |
| // static |
| const char* System::kLibrarySearchListEnvVarName = "LD_LIBRARY_PATH"; |
| #endif |
| |
| // static |
| System* System::setForTesting(System* system) { |
| System* result = sSystemForTesting; |
| sSystemForTesting = system; |
| return result; |
| } |
| |
| // static |
| std::vector<std::string> System::scanDirInternal(StringView dirPath) { |
| std::vector<std::string> result; |
| |
| if (dirPath.empty()) { |
| return result; |
| } |
| #ifdef _WIN32 |
| auto root = PathUtils::addTrailingDirSeparator(dirPath); |
| root += '*'; |
| Win32UnicodeString rootUnicode(root); |
| struct _wfinddata_t findData; |
| intptr_t findIndex = _wfindfirst(rootUnicode.c_str(), &findData); |
| if (findIndex >= 0) { |
| do { |
| const wchar_t* name = findData.name; |
| if (wcscmp(name, L".") != 0 && wcscmp(name, L"..") != 0) { |
| result.push_back(Win32UnicodeString::convertToUtf8(name)); |
| } |
| } while (_wfindnext(findIndex, &findData) >= 0); |
| _findclose(findIndex); |
| } |
| #else // !_WIN32 |
| DIR* dir = ::opendir(dirPath.c_str()); |
| if (dir) { |
| for (;;) { |
| struct dirent* entry = ::readdir(dir); |
| if (!entry) { |
| break; |
| } |
| const char* name = entry->d_name; |
| if (strcmp(name, ".") != 0 && strcmp(name, "..") != 0) { |
| result.push_back(std::string(name)); |
| } |
| } |
| ::closedir(dir); |
| } |
| #endif // !_WIN32 |
| std::sort(result.begin(), result.end()); |
| return result; |
| } |
| |
| // static |
| bool System::pathExistsInternal(StringView path) { |
| if (path.empty()) { |
| return false; |
| } |
| int ret = pathAccess(path, F_OK); |
| return (ret == 0) || (errno != ENOENT); |
| } |
| |
| // static |
| bool System::pathIsFileInternal(StringView path) { |
| if (path.empty()) { |
| return false; |
| } |
| PathStat st; |
| int ret = pathStat(path, &st); |
| if (ret < 0) { |
| return false; |
| } |
| return S_ISREG(st.st_mode); |
| } |
| |
| // static |
| bool System::pathIsDirInternal(StringView path) { |
| if (path.empty()) { |
| return false; |
| } |
| PathStat st; |
| int ret = pathStat(path, &st); |
| if (ret < 0) { |
| return false; |
| } |
| return S_ISDIR(st.st_mode); |
| } |
| |
| // static |
| bool System::pathCanReadInternal(StringView path) { |
| if (path.empty()) { |
| return false; |
| } |
| return pathAccess(path, R_OK) == 0; |
| } |
| |
| // static |
| bool System::pathCanWriteInternal(StringView path) { |
| if (path.empty()) { |
| return false; |
| } |
| return pathAccess(path, W_OK) == 0; |
| } |
| |
| // static |
| bool System::pathCanExecInternal(StringView path) { |
| if (path.empty()) { |
| return false; |
| } |
| return pathAccess(path, X_OK) == 0; |
| } |
| |
| bool System::deleteFileInternal(StringView path) { |
| if (!pathIsFileInternal(path)) { |
| return false; |
| } |
| |
| int remove_res = -1; |
| |
| #ifdef _WIN32 |
| remove_res = remove(path.c_str()); |
| if (remove_res < 0) { |
| // Windows sometimes just fails to delete a file |
| // on the first try. |
| // Sleep a little bit and try again here. |
| System::get()->sleepMs(1); |
| remove_res = remove(path.c_str()); |
| } |
| #else |
| remove_res = remove(path.c_str()); |
| #endif |
| |
| if (remove_res != 0) { |
| LOG(VERBOSE) << "Failed to delete file [" << path << "]."; |
| } |
| |
| return remove_res == 0; |
| } |
| |
| // static |
| bool System::pathFileSizeInternal(StringView path, FileSize* outFileSize) { |
| if (path.empty() || !outFileSize) { |
| return false; |
| } |
| PathStat st; |
| int ret = pathStat(path, &st); |
| if (ret < 0 || !S_ISREG(st.st_mode)) { |
| return false; |
| } |
| // This is off_t on POSIX and a 32/64 bit integral type on windows based on |
| // the host / compiler combination. We cast everything to 64 bit unsigned to |
| // play safe. |
| *outFileSize = static_cast<FileSize>(st.st_size); |
| return true; |
| } |
| |
| // static |
| void System::addLibrarySearchDir(StringView path) { |
| System* system = System::get(); |
| const char* varName = kLibrarySearchListEnvVarName; |
| |
| std::string libSearchPath = system->envGet(varName); |
| if (libSearchPath.size()) { |
| libSearchPath = |
| StringFormat("%s%c%s", path, kPathSeparator, libSearchPath); |
| } else { |
| libSearchPath = path; |
| } |
| system->envSet(varName, libSearchPath); |
| } |
| |
| // static |
| std::string System::findBundledExecutable(StringView programName) { |
| System* const system = System::get(); |
| const std::string executableName = PathUtils::toExecutableName(programName); |
| |
| // first, try the root launcher directory |
| std::vector<std::string> pathList = {system->getLauncherDirectory(), |
| executableName}; |
| std::string executablePath = PathUtils::recompose(pathList); |
| if (system->pathIsFile(executablePath)) { |
| return executablePath; |
| } |
| |
| // it's not there - let's try the 'bin/' subdirectory |
| assert(pathList.size() == 2); |
| assert(pathList[1] == executableName.c_str()); |
| pathList[1] = kBinSubDir; |
| pathList.push_back(executableName); |
| executablePath = PathUtils::recompose(pathList); |
| if (system->pathIsFile(executablePath)) { |
| return executablePath; |
| } |
| |
| #if defined(_WIN32) && defined(__x86_64) |
| // On Windows we don't have a x64 version e2fsprogs, so let's try |
| // 32-bit directory if 64-bit lookup failed |
| assert(pathList[1] == kBinSubDir); |
| pathList[1] = kBin32SubDir; |
| executablePath = PathUtils::recompose(pathList); |
| if (system->pathIsFile(executablePath)) { |
| return executablePath; |
| } |
| #endif |
| |
| return std::string(); |
| } |
| |
| std::string toString(OsType osType) { |
| switch (osType) { |
| case OsType::Windows: |
| return "Windows"; |
| case OsType::Linux: |
| return "Linux"; |
| case OsType::Mac: |
| return "Mac"; |
| default: |
| return "Unknown"; |
| } |
| } |
| |
| } // namespace base |
| } // namespace android |