#if defined(__unix__) || defined(__APPLE__) #include "platform.h" #include "utils.h" #include "loguru.hpp" #include #if defined(__FreeBSD__) #include #include #elif defined(__OpenBSD__) #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include // required for stat.h #include #include #include #include #include #if defined(__FreeBSD__) #include // MAXPATHLEN #include // sysctl #elif defined(__linux__) #include #endif #include namespace { // Returns the canonicalized absolute pathname, without expanding symbolic // links. This is a variant of realpath(2), C++ rewrite of // https://github.com/freebsd/freebsd/blob/master/lib/libc/stdlib/realpath.c std::optional RealPathNotExpandSymlink(std::string path) { if (path.empty()) { errno = EINVAL; return std::nullopt; } if (path[0] == '\0') { errno = ENOENT; return std::nullopt; } // Do not use PATH_MAX because it is tricky on Linux. // See https://eklitzke.org/path-max-is-tricky char tmp[1024]; std::string resolved; size_t i = 0; struct stat sb; if (path[0] == '/') { resolved = "/"; i = 1; } else { if (!getcwd(tmp, sizeof tmp)) return std::nullopt; resolved = tmp; } while (i < path.size()) { auto j = path.find('/', i); if (j == std::string::npos) j = path.size(); auto next_token = path.substr(i, j - i); i = j + 1; if (resolved.back() != '/') resolved += '/'; if (next_token.empty() || next_token == ".") { // Handle consequential slashes and "." continue; } else if (next_token == "..") { // Strip the last path component except when it is single "/" if (resolved.size() > 1) resolved.resize(resolved.rfind('/', resolved.size() - 2) + 1); continue; } // Append the next path component. // Here we differ from realpath(3), we use stat(2) instead of // lstat(2) because we do not want to resolve symlinks. resolved += next_token; if (stat(resolved.c_str(), &sb) != 0) return std::nullopt; if (!S_ISDIR(sb.st_mode) && j < path.size()) { errno = ENOTDIR; return std::nullopt; } } // Remove trailing slash except when a single "/". if (resolved.size() > 1 && resolved.back() == '/') resolved.pop_back(); return resolved; } } // namespace void PlatformInit() {} #ifdef __APPLE__ extern "C" int _NSGetExecutablePath(char* buf, uint32_t* bufsize); #endif // See // https://stackoverflow.com/questions/143174/how-do-i-get-the-directory-that-a-program-is-running-from std::string GetExecutablePath() { #ifdef __APPLE__ uint32_t size = 0; _NSGetExecutablePath(nullptr, &size); char* buffer = new char[size]; _NSGetExecutablePath(buffer, &size); char* resolved = realpath(buffer, nullptr); std::string result(resolved); delete[] buffer; free(resolved); return result; #elif defined(__FreeBSD__) static const int name[] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1, }; char path[MAXPATHLEN]; size_t len = sizeof(path); path[0] = '\0'; (void)sysctl(name, 4, path, &len, NULL, 0); return std::string(path); #else char buffer[PATH_MAX] = {0}; if (-1 == readlink("/proc/self/exe", buffer, PATH_MAX)) return ""; return buffer; #endif } std::string GetWorkingDirectory() { char result[FILENAME_MAX]; if (!getcwd(result, sizeof(result))) return ""; std::string working_dir = std::string(result, strlen(result)); EnsureEndsInSlash(working_dir); return working_dir; } std::string NormalizePath(const std::string& path) { std::optional resolved = RealPathNotExpandSymlink(path); return resolved ? *resolved : path; } bool TryMakeDirectory(const std::string& absolute_path) { const mode_t kMode = 0777; // UNIX style permissions if (mkdir(absolute_path.c_str(), kMode) == -1) { // Success if the directory exists. return errno == EEXIST; } return true; } void SetCurrentThreadName(const std::string& thread_name) { loguru::set_thread_name(thread_name.c_str()); #if defined(__APPLE__) pthread_setname_np(thread_name.c_str()); #elif defined(__FreeBSD__) || defined(__OpenBSD__) pthread_set_name_np(pthread_self(), thread_name.c_str()); #elif defined(__linux__) pthread_setname_np(pthread_self(), thread_name.c_str()); #endif } std::optional GetLastModificationTime(const std::string& absolute_path) { struct stat buf; if (stat(absolute_path.c_str(), &buf) != 0) { switch (errno) { case ENOENT: // std::cerr << "GetLastModificationTime: unable to find file " << // absolute_path << std::endl; return std::nullopt; case EINVAL: // std::cerr << "GetLastModificationTime: invalid param to _stat for // file file " << absolute_path << std::endl; return std::nullopt; default: // std::cerr << "GetLastModificationTime: unhandled for " << // absolute_path << std::endl; exit(1); return std::nullopt; } } return buf.st_mtime; } void MoveFileTo(const std::string& dest, const std::string& source) { // TODO/FIXME - do a real move. CopyFileTo(dest, source); } // See http://stackoverflow.com/q/13198627 void CopyFileTo(const std::string& dest, const std::string& source) { int fd_from = open(source.c_str(), O_RDONLY); if (fd_from < 0) return; int fd_to = open(dest.c_str(), O_WRONLY | O_CREAT, 0666); if (fd_to < 0) goto out_error; char buf[4096]; ssize_t nread; while (nread = read(fd_from, buf, sizeof buf), nread > 0) { char* out_ptr = buf; ssize_t nwritten; do { nwritten = write(fd_to, out_ptr, nread); if (nwritten >= 0) { nread -= nwritten; out_ptr += nwritten; } else if (errno != EINTR) goto out_error; } while (nread > 0); } if (nread == 0) { if (close(fd_to) < 0) { fd_to = -1; goto out_error; } close(fd_from); return; } out_error: close(fd_from); if (fd_to >= 0) close(fd_to); } bool IsSymLink(const std::string& path) { struct stat buf; return lstat(path.c_str(), &buf) == 0 && S_ISLNK(buf.st_mode); } void FreeUnusedMemory() { #if defined(__GLIBC__) malloc_trim(0); #endif } bool RunObjectiveCIndexTests() { #if defined(__APPLE__) return true; #else return false; #endif } void TraceMe() { // If the environment variable is defined, wait for a debugger. // In gdb, you need to invoke `signal SIGCONT` if you want ccls to continue // after detaching. if (getenv("CCLS_TRACEME")) raise(SIGTSTP); } std::string GetExternalCommandOutput(const std::vector& command, std::string_view input) { int pin[2], pout[2]; if (pipe(pin) < 0) return ""; if (pipe(pout) < 0) { close(pin[0]); close(pin[1]); return ""; } pid_t child = fork(); if (child == 0) { dup2(pout[0], 0); dup2(pin[1], 1); close(pin[0]); close(pin[1]); close(pout[0]); close(pout[1]); auto argv = new char*[command.size() + 1]; for (size_t i = 0; i < command.size(); i++) argv[i] = const_cast(command[i].c_str()); argv[command.size()] = nullptr; execvp(argv[0], argv); _Exit(127); } close(pin[1]); close(pout[0]); // O_NONBLOCK is disabled, write(2) blocks until all bytes are written. (void)write(pout[1], input.data(), input.size()); close(pout[1]); std::string ret; char buf[4096]; ssize_t n; while ((n = read(pin[0], buf, sizeof buf)) > 0) ret.append(buf, n); waitpid(child, NULL, 0); return ret; } #endif