blob: b6f5bf3ae52f1e6434507e2c8e678a6457ae8f77 [file] [log] [blame]
// Copyright 2011 The Goma Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "mypath.h"
#include <limits.h>
#ifndef _WIN32
#include <pwd.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/utsname.h>
#ifdef __MACH__
# include <mach-o/dyld.h>
#endif
#ifdef __FreeBSD__
# include <sys/types.h>
# include <sys/sysctl.h>
#endif
#endif
#include <vector>
#include "glog/logging.h"
#include "basictypes.h"
#ifdef _WIN32
# include "config_win.h"
# include <psapi.h>
# pragma comment(lib, "psapi.lib")
# include <lmcons.h> // for UNLEN
#endif
#include "env_flags.h"
#include "file_dir.h"
#include "filesystem.h"
#include "file_stat.h"
#include "mypath_helper.h"
#include "path.h"
#include "path_resolver.h"
#include "path_util.h"
#include "util.h"
GOMA_DECLARE_string(CACHE_DIR);
GOMA_DECLARE_string(TMP_DIR);
namespace {
#ifndef _WIN32
const char kGomaTmpDirPrefix[] = "goma_";
#else
const char kGomaTmpDir[] = "goma";
#endif
const char kGomaCrashDumpDir[] = "goma_crash";
const char kGomaCacheDir[] = "goma_cache";
template<typename UnaryFunction>
static string GetEnvMatchedCondition(
const std::vector<const char*>& candidates,
UnaryFunction condition,
const char* default_value) {
for (const auto* candidate : candidates) {
const string value = devtools_goma::GetEnv(candidate);
if (!value.empty() && condition(value)) {
return value;
}
}
return default_value;
}
static string GetTempDirectoryEnv() {
static const char* kTmpdirEnvs[] = {
"TEST_TMPDIR",
"TMPDIR",
"TMP",
};
return GetEnvMatchedCondition(
std::vector<const char*>(&kTmpdirEnvs[0],
&kTmpdirEnvs[arraysize(kTmpdirEnvs)]),
[](const string& tmpdir) {
return file::IsDirectory(tmpdir, file::Defaults()).ok();
},
"/tmp");
}
} // anonymous namespace
namespace devtools_goma {
string GetUsernameEnv() {
static const char* kRoot = "root";
static const char* kUserEnvs[] = {
"SUDO_USER",
"USERNAME",
"USER",
"LOGNAME",
};
return GetEnvMatchedCondition(
std::vector<const char*>(&kUserEnvs[0],
&kUserEnvs[arraysize(kUserEnvs)]),
[](const string& user) {
return user != kRoot;
},
"");
}
string GetUsernameNoEnv() {
#ifndef _WIN32
uid_t uid = getuid();
struct passwd* pw = getpwuid(uid);
if (uid == 0 || pw == nullptr || pw->pw_name == nullptr ||
*pw->pw_name == '\0') {
return "";
}
return pw->pw_name;
#else
char buf[UNLEN + 1] = {0};
DWORD len = UNLEN;
::GetUserNameA(buf, &len);
return buf;
#endif
}
string GetUsername() {
string username = GetUsernameEnv();
if (!username.empty()) {
return username;
}
username = GetUsernameNoEnv();
if (!username.empty()) {
SetEnv("USER", username);
return username;
}
return "unknown";
}
string GetNodename() {
#ifndef _WIN32
// Gets nodename, which is a good enough approximation to a
// hostname, for debugging purposes, for now.
struct utsname u;
if (uname(&u) == 0) {
return u.nodename;
}
PLOG(ERROR) << "uname failed";
#else
// Get NetBIOS name for now to avoid network queries.
char buffer[MAX_COMPUTERNAME_LENGTH + 1] = {0};
DWORD len = MAX_COMPUTERNAME_LENGTH + 1;
if (GetComputerNameA(buffer, &len) && len) {
string nodename(buffer, len);
return nodename;
}
LOG(ERROR) << "GetComputerName " << GetLastError();
#endif
return "localhost";
}
string GetMyPathname() {
string myself_fullpath;
#ifdef _WIN32
char path[PATH_MAX] = {0};
HANDLE process = GetCurrentProcess();
PCHECK(GetModuleFileNameExA(process, nullptr, path, PATH_MAX));
myself_fullpath = path;
#elif defined(__MACH__)
myself_fullpath = _dyld_get_image_name(0);
#elif defined(__FreeBSD__)
char buf[PATH_MAX + 1];
const int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1};
size_t length = sizeof(buf);
PCHECK(sysctl(mib, 4, buf, &length, nullptr, 0) >= 0);
CHECK_GT(length, 1U);
myself_fullpath.assign(buf, length - 1);
#else
char buf[PATH_MAX + 1];
ssize_t len;
PCHECK((len = readlink("/proc/self/exe", buf, PATH_MAX)) >= 0);
CHECK_LT(static_cast<size_t>(len), sizeof buf);
buf[len] = '\0';
myself_fullpath = buf;
#endif
// Mac's _dyld_get_image_name(0) sometimes returns path containing ".".
// Not to confuse caller, normalize path here.
return PathResolver::ResolvePath(myself_fullpath);
}
string GetMyDirectory() {
#ifndef _WIN32
const char SEP = '/';
#else
const char SEP = '\\';
#endif
string myself_fullpath = GetMyPathname();
size_t last_slash = myself_fullpath.rfind(SEP);
CHECK(last_slash != string::npos);
return myself_fullpath.substr(0, last_slash);
}
// NOTE: When updating this, you also need to update get_temp_directory() in
// client/goma-wrapper and GetGomaTmpDir in goma_ctl.py.
string GetGomaTmpDir() {
if (FLAGS_TMP_DIR != "") {
return FLAGS_TMP_DIR;
}
string tmpdir = GetPlatformSpecificTempDirectory();
if (tmpdir.empty()) {
tmpdir = GetTempDirectoryEnv();
}
CHECK(!tmpdir.empty()) << "Could not determine temp directory. "
<< "Make sure TMPDIR or TMP are not empty.";
// Assume goma_ctl.py creates /tmp/goma_<user> or %TEMP%\goma.
#ifndef _WIN32
string private_name(kGomaTmpDirPrefix);
const string username = GetUsername();
if (username == "" || username == "unknown") {
LOG(ERROR) << "bad username:" << username;
}
private_name.append(username);
#else
string private_name(kGomaTmpDir);
#endif
string private_tmpdir = file::JoinPath(tmpdir, private_name);
return private_tmpdir;
}
void CheckTempDirectory(const string& tmpdir) {
if (!EnsureDirectory(tmpdir, 0700)) {
LOG(FATAL) << "failed to create goma tmp dir or "
<< "private goma tmp dir is not dir: " << tmpdir;
}
#ifndef _WIN32
struct stat st;
// We must use lstat instead of stat to avoid symlink attack (b/69717657).
PCHECK(lstat(tmpdir.c_str(), &st) == 0) << "lstat " << tmpdir;
if ((st.st_mode & 077) != 0) {
LOG(FATAL) << "private goma tmp dir is not owned only by you. "
<< "please check owner/permission of " << tmpdir
<< ". It must not be readable/writable by group/other. "
<< "e.g. $ chmod go-rwx " << tmpdir;
}
#endif
}
string GetCrashDumpDirectory() {
return file::JoinPath(GetGomaTmpDir(), kGomaCrashDumpDir);
}
string GetCacheDirectory() {
if (FLAGS_CACHE_DIR != "") {
return FLAGS_CACHE_DIR;
}
return file::JoinPath(GetGomaTmpDir(), kGomaCacheDir);
}
string GetCurrentDirNameOrDie(void) {
#ifndef _WIN32
// get_cwd() returns the current resolved directory. However, a compiler is
// taking PWD as current working directory. PWD might contain unresolved
// directory.
// We don't return /proc/self/cwd if it is set in PWD, since the corresponding
// directory is different among gomacc and compiler_proxy.
// See also: b/37259278
const char* pwd = getenv("PWD");
if (pwd != nullptr && IsPosixAbsolutePath(pwd) &&
!HasPrefixDir(pwd, "/proc/self/cwd")) {
// Align with llvm current_path().
// llvm checking PWD id and "." id are the same.
FileStat pwd_stat(pwd);
FileStat dot_stat(".");
if (pwd_stat.IsValid() && dot_stat.IsValid() && pwd_stat.is_directory &&
pwd_stat == dot_stat) {
return pwd;
}
}
char *dir = getcwd(nullptr, 0);
CHECK(dir) << "GOMA: Cannot find current directory ";
string dir_str(dir);
free(dir);
return dir_str;
#else
char dir[PATH_MAX];
CHECK_NE(GetCurrentDirectoryA(PATH_MAX, dir), (DWORD)0) <<
"GOMA: Cannot find current directory: " << GetLastError();
string dir_str(dir);
return dir_str;
#endif
}
} // namespace devtools_goma