blob: c8cb68a921c1afdd6f15bc15755dfcd6cf55c1ff [file] [log] [blame]
//
// Copyright 2018 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.
//
// system_utils_posix.cpp: Implementation of POSIX OS-specific functions.
#include "system_utils.h"
#include <array>
#include <dlfcn.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
namespace angle
{
namespace
{
struct ScopedPipe
{
~ScopedPipe()
{
closeEndPoint(0);
closeEndPoint(1);
}
void closeEndPoint(int index)
{
if (fds[index] >= 0)
{
close(fds[index]);
fds[index] = -1;
}
}
int fds[2] = {
-1,
-1,
};
};
void ReadEntireFile(int fd, std::string *out)
{
out->clear();
while (true)
{
char buffer[256];
ssize_t bytesRead = read(fd, buffer, sizeof(buffer));
// If interrupted, retry.
if (bytesRead < 0 && errno == EINTR)
{
continue;
}
// If failed, or nothing to read, we are done.
if (bytesRead <= 0)
{
break;
}
out->append(buffer, bytesRead);
}
}
} // anonymous namespace
Optional<std::string> GetCWD()
{
std::array<char, 4096> pathBuf;
char *result = getcwd(pathBuf.data(), pathBuf.size());
if (result == nullptr)
{
return Optional<std::string>::Invalid();
}
return std::string(pathBuf.data());
}
bool SetCWD(const char *dirName)
{
return (chdir(dirName) == 0);
}
bool UnsetEnvironmentVar(const char *variableName)
{
return (unsetenv(variableName) == 0);
}
bool SetEnvironmentVar(const char *variableName, const char *value)
{
return (setenv(variableName, value, 1) == 0);
}
std::string GetEnvironmentVar(const char *variableName)
{
const char *value = getenv(variableName);
return (value == nullptr ? std::string() : std::string(value));
}
const char *GetPathSeparator()
{
return ":";
}
bool RunApp(const std::vector<const char *> &args,
std::string *stdoutOut,
std::string *stderrOut,
int *exitCodeOut)
{
if (args.size() == 0 || args.back() != nullptr)
{
return false;
}
ScopedPipe stdoutPipe;
ScopedPipe stderrPipe;
// Create pipes for stdout and stderr.
if (stdoutOut && pipe(stdoutPipe.fds) != 0)
{
return false;
}
if (stderrOut && pipe(stderrPipe.fds) != 0)
{
return false;
}
pid_t pid = fork();
if (pid < 0)
{
return false;
}
if (pid == 0)
{
// Child. Execute the application.
// Redirect stdout and stderr to the pipe fds.
if (stdoutOut)
{
if (dup2(stdoutPipe.fds[1], STDOUT_FILENO) < 0)
{
_exit(errno);
}
}
if (stderrOut)
{
if (dup2(stderrPipe.fds[1], STDERR_FILENO) < 0)
{
_exit(errno);
}
}
// 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.
execv(args[0], const_cast<char *const *>(args.data()));
_exit(errno);
}
// Parent. Read child output from the pipes and clean it up.
// Close the write end of the pipes, so EOF can be generated when child exits.
stdoutPipe.closeEndPoint(1);
stderrPipe.closeEndPoint(1);
// Read back the output of the child.
if (stdoutOut)
{
ReadEntireFile(stdoutPipe.fds[0], stdoutOut);
}
if (stderrOut)
{
ReadEntireFile(stderrPipe.fds[0], stderrOut);
}
// Cleanup the child.
int status = 0;
do
{
pid_t changedPid = waitpid(pid, &status, 0);
if (changedPid < 0 && errno == EINTR)
{
continue;
}
if (changedPid < 0)
{
return false;
}
} while (!WIFEXITED(status) && !WIFSIGNALED(status));
// Retrieve the error code.
if (exitCodeOut)
{
*exitCodeOut = WEXITSTATUS(status);
}
return true;
}
class PosixLibrary : public Library
{
public:
PosixLibrary(const char *libraryName, SearchType searchType)
{
std::string directory;
if (searchType == SearchType::ApplicationDir)
{
static int dummySymbol = 0;
Dl_info dlInfo;
if (dladdr(&dummySymbol, &dlInfo) != 0)
{
std::string moduleName = dlInfo.dli_fname;
directory = moduleName.substr(0, moduleName.find_last_of('/') + 1);
}
}
std::string fullPath = directory + libraryName + "." + GetSharedLibraryExtension();
mModule = dlopen(fullPath.c_str(), RTLD_NOW);
}
~PosixLibrary() override
{
if (mModule)
{
dlclose(mModule);
}
}
void *getSymbol(const char *symbolName) override
{
if (!mModule)
{
return nullptr;
}
return dlsym(mModule, symbolName);
}
void *getNative() const override { return mModule; }
private:
void *mModule = nullptr;
};
Library *OpenSharedLibrary(const char *libraryName, SearchType searchType)
{
return new PosixLibrary(libraryName, searchType);
}
bool IsDirectory(const char *filename)
{
struct stat st;
int result = stat(filename, &st);
return result == 0 && ((st.st_mode & S_IFDIR) == S_IFDIR);
}
bool IsDebuggerAttached()
{
// This could have a fuller implementation.
// See https://cs.chromium.org/chromium/src/base/debug/debugger_posix.cc
return false;
}
void BreakDebugger()
{
// This could have a fuller implementation.
// See https://cs.chromium.org/chromium/src/base/debug/debugger_posix.cc
abort();
}
} // namespace angle