blob: fa263c166dff00480c2bb8e3d130213cc632e60e [file] [log] [blame]
// 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(&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