diff options
Diffstat (limited to '')
-rw-r--r-- | lib/base/utility.cpp | 2023 |
1 files changed, 2023 insertions, 0 deletions
diff --git a/lib/base/utility.cpp b/lib/base/utility.cpp new file mode 100644 index 0000000..a2930a0 --- /dev/null +++ b/lib/base/utility.cpp @@ -0,0 +1,2023 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/utility.hpp" +#include "base/convert.hpp" +#include "base/application.hpp" +#include "base/logger.hpp" +#include "base/exception.hpp" +#include "base/socket.hpp" +#include "base/utility.hpp" +#include "base/json.hpp" +#include "base/objectlock.hpp" +#include <algorithm> +#include <cstdint> +#include <mmatch.h> +#include <boost/filesystem.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/system/error_code.hpp> +#include <boost/thread/tss.hpp> +#include <boost/algorithm/string/trim.hpp> +#include <boost/algorithm/string/replace.hpp> +#include <boost/uuid/uuid_io.hpp> +#include <boost/uuid/uuid_generators.hpp> +#include <boost/regex.hpp> +#include <ios> +#include <fstream> +#include <iostream> +#include <iterator> +#include <stdlib.h> +#include <future> +#include <set> +#include <utf8.h> +#include <vector> + +#ifdef __FreeBSD__ +# include <pthread_np.h> +#endif /* __FreeBSD__ */ + +#ifdef HAVE_CXXABI_H +# include <cxxabi.h> +#endif /* HAVE_CXXABI_H */ + +#ifndef _WIN32 +# include <sys/types.h> +# include <sys/utsname.h> +# include <pwd.h> +# include <grp.h> +# include <errno.h> +# include <unistd.h> +#endif /* _WIN32 */ + +#ifdef _WIN32 +# include <VersionHelpers.h> +# include <windows.h> +# include <io.h> +# include <msi.h> +# include <shlobj.h> +#endif /*_WIN32*/ + +using namespace icinga; + +boost::thread_specific_ptr<String> Utility::m_ThreadName; +boost::thread_specific_ptr<unsigned int> Utility::m_RandSeed; + +#ifdef I2_DEBUG +double Utility::m_DebugTime = -1; +#endif /* I2_DEBUG */ + +/** + * Demangles a symbol name. + * + * @param sym The symbol name. + * @returns A human-readable version of the symbol name. + */ +String Utility::DemangleSymbolName(const String& sym) +{ + String result = sym; + +#ifdef HAVE_CXXABI_H + int status; + char *realname = abi::__cxa_demangle(sym.CStr(), nullptr, nullptr, &status); + + if (realname) { + result = String(realname); + free(realname); + } +#elif defined(_MSC_VER) /* HAVE_CXXABI_H */ + CHAR output[256]; + + if (UnDecorateSymbolName(sym.CStr(), output, sizeof(output), UNDNAME_COMPLETE) > 0) + result = output; +#else /* _MSC_VER */ + /* We're pretty much out of options here. */ +#endif /* _MSC_VER */ + + return result; +} + +/** + * Returns a human-readable type name of a type_info object. + * + * @param ti A type_info object. + * @returns The type name of the object. + */ +String Utility::GetTypeName(const std::type_info& ti) +{ + return DemangleSymbolName(ti.name()); +} + +String Utility::GetSymbolName(const void *addr) +{ +#ifdef HAVE_DLADDR + Dl_info dli; + + if (dladdr(const_cast<void *>(addr), &dli) > 0) + return dli.dli_sname; +#endif /* HAVE_DLADDR */ + +#ifdef _WIN32 + char buffer[sizeof(SYMBOL_INFO)+MAX_SYM_NAME * sizeof(TCHAR)]; + PSYMBOL_INFO pSymbol = (PSYMBOL_INFO)buffer; + pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO); + pSymbol->MaxNameLen = MAX_SYM_NAME; + + DWORD64 dwAddress = (DWORD64)addr; + DWORD64 dwDisplacement; + + IMAGEHLP_LINE64 line; + line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); + + if (SymFromAddr(GetCurrentProcess(), dwAddress, &dwDisplacement, pSymbol)) { + char output[256]; + if (UnDecorateSymbolName(pSymbol->Name, output, sizeof(output), UNDNAME_COMPLETE)) + return String(output) + "+" + Convert::ToString(dwDisplacement); + else + return String(pSymbol->Name) + "+" + Convert::ToString(dwDisplacement); + } +#endif /* _WIN32 */ + + return "(unknown function)"; +} + +/** + * Performs wildcard pattern matching. + * + * @param pattern The wildcard pattern. + * @param text The String that should be checked. + * @returns true if the wildcard pattern matches, false otherwise. + */ +bool Utility::Match(const String& pattern, const String& text) +{ + return (match(pattern.CStr(), text.CStr()) == 0); +} + +static bool ParseIp(const String& ip, char addr[16], int *proto) +{ + if (inet_pton(AF_INET, ip.CStr(), addr + 12) == 1) { + /* IPv4-mapped IPv6 address (::ffff:<ipv4-bits>) */ + memset(addr, 0, 10); + memset(addr + 10, 0xff, 2); + *proto = AF_INET; + + return true; + } + + if (inet_pton(AF_INET6, ip.CStr(), addr) == 1) { + *proto = AF_INET6; + + return true; + } + + return false; +} + +static void ParseIpMask(const String& ip, char mask[16], int *bits) +{ + String::SizeType slashp = ip.FindFirstOf("/"); + String uip; + + if (slashp == String::NPos) { + uip = ip; + *bits = 0; + } else { + uip = ip.SubStr(0, slashp); + *bits = Convert::ToLong(ip.SubStr(slashp + 1)); + } + + int proto; + + if (!ParseIp(uip, mask, &proto)) + BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid IP address specified.")); + + if (proto == AF_INET) { + if (*bits > 32 || *bits < 0) + BOOST_THROW_EXCEPTION(std::invalid_argument("Mask must be between 0 and 32 for IPv4 CIDR masks.")); + + *bits += 96; + } + + if (slashp == String::NPos) + *bits = 128; + + if (*bits > 128 || *bits < 0) + BOOST_THROW_EXCEPTION(std::invalid_argument("Mask must be between 0 and 128 for IPv6 CIDR masks.")); + + for (int i = 0; i < 16; i++) { + int lbits = std::max(0, *bits - i * 8); + + if (lbits >= 8) + continue; + + if (mask[i] & (0xff >> lbits)) + BOOST_THROW_EXCEPTION(std::invalid_argument("Masked-off bits must all be zero.")); + } +} + +static bool IpMaskCheck(char addr[16], char mask[16], int bits) +{ + for (int i = 0; i < 16; i++) { + if (bits < 8) + return !((addr[i] ^ mask[i]) >> (8 - bits)); + + if (mask[i] != addr[i]) + return false; + + bits -= 8; + + if (bits == 0) + return true; + } + + return true; +} + +bool Utility::CidrMatch(const String& pattern, const String& ip) +{ + char mask[16]; + int bits; + + ParseIpMask(pattern, mask, &bits); + + char addr[16]; + int proto; + + if (!ParseIp(ip, addr, &proto)) + return false; + + return IpMaskCheck(addr, mask, bits); +} + +/** + * Returns the directory component of a path. See dirname(3) for details. + * + * @param path The full path. + * @returns The directory. + */ +String Utility::DirName(const String& path) +{ + return boost::filesystem::path(path.Begin(), path.End()).parent_path().string(); +} + +/** + * Returns the file component of a path. See basename(3) for details. + * + * @param path The full path. + * @returns The filename. + */ +String Utility::BaseName(const String& path) +{ + return boost::filesystem::path(path.Begin(), path.End()).filename().string(); +} + +/** + * Null deleter. Used as a parameter for the shared_ptr constructor. + * + * @param - The object that should be deleted. + */ +void Utility::NullDeleter(void *) +{ + /* Nothing to do here. */ +} + +#ifdef I2_DEBUG +/** + * (DEBUG / TESTING ONLY) Sets the current system time to a static value, + * that will be be retrieved by any component of Icinga, when using GetTime(). + * + * This should be only used for testing purposes, e.g. unit tests and debugging of certain functionalities. + */ +void Utility::SetTime(double time) +{ + m_DebugTime = time; +} + +/** + * (DEBUG / TESTING ONLY) Increases the set debug system time by X seconds. + * + * This should be only used for testing purposes, e.g. unit tests and debugging of certain functionalities. + */ +void Utility::IncrementTime(double diff) +{ + m_DebugTime += diff; +} +#endif /* I2_DEBUG */ + +/** + * Returns the current UNIX timestamp including fractions of seconds. + * + * @returns The current time. + */ +double Utility::GetTime() +{ +#ifdef I2_DEBUG + if (m_DebugTime >= 0) { + // (DEBUG / TESTING ONLY) this will return a *STATIC* system time, if the value has been set! + return m_DebugTime; + } +#endif /* I2_DEBUG */ +#ifdef _WIN32 + FILETIME cft; + GetSystemTimeAsFileTime(&cft); + + ULARGE_INTEGER ucft; + ucft.HighPart = cft.dwHighDateTime; + ucft.LowPart = cft.dwLowDateTime; + + SYSTEMTIME est = { 1970, 1, 4, 1, 0, 0, 0, 0}; + FILETIME eft; + SystemTimeToFileTime(&est, &eft); + + ULARGE_INTEGER ueft; + ueft.HighPart = eft.dwHighDateTime; + ueft.LowPart = eft.dwLowDateTime; + + return ((ucft.QuadPart - ueft.QuadPart) / 10000) / 1000.0; +#else /* _WIN32 */ + struct timeval tv; + + int rc = gettimeofday(&tv, nullptr); + VERIFY(rc >= 0); + + return tv.tv_sec + tv.tv_usec / 1000000.0; +#endif /* _WIN32 */ +} + +/** + * Returns the ID of the current process. + * + * @returns The PID. + */ +pid_t Utility::GetPid() +{ +#ifndef _WIN32 + return getpid(); +#else /* _WIN32 */ + return GetCurrentProcessId(); +#endif /* _WIN32 */ +} + +/** + * Sleeps for the specified amount of time. + * + * @param timeout The timeout in seconds. + */ +void Utility::Sleep(double timeout) +{ +#ifndef _WIN32 + unsigned long micros = timeout * 1000000u; + if (timeout >= 1.0) + sleep((unsigned)timeout); + + usleep(micros % 1000000u); +#else /* _WIN32 */ + ::Sleep(timeout * 1000); +#endif /* _WIN32 */ +} + +/** + * Generates a new unique ID. + * + * @returns The new unique ID. + */ +String Utility::NewUniqueID() +{ + return boost::lexical_cast<std::string>(boost::uuids::random_generator()()); +} + +#ifdef _WIN32 +static bool GlobHelper(const String& pathSpec, int type, std::vector<String>& files, std::vector<String>& dirs) +{ + HANDLE handle; + WIN32_FIND_DATA wfd; + + handle = FindFirstFile(pathSpec.CStr(), &wfd); + + if (handle == INVALID_HANDLE_VALUE) { + DWORD errorCode = GetLastError(); + + if (errorCode == ERROR_FILE_NOT_FOUND) + return false; + + BOOST_THROW_EXCEPTION(win32_error() + << boost::errinfo_api_function("FindFirstFile") + << errinfo_win32_error(errorCode) + << boost::errinfo_file_name(pathSpec)); + } + + do { + if (strcmp(wfd.cFileName, ".") == 0 || strcmp(wfd.cFileName, "..") == 0) + continue; + + String path = Utility::DirName(pathSpec) + "/" + wfd.cFileName; + + if ((wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && (type & GlobDirectory)) + dirs.push_back(path); + else if (!(wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && (type & GlobFile)) + files.push_back(path); + } while (FindNextFile(handle, &wfd)); + + if (!FindClose(handle)) { + BOOST_THROW_EXCEPTION(win32_error() + << boost::errinfo_api_function("FindClose") + << errinfo_win32_error(GetLastError())); + } + + return true; +} +#endif /* _WIN32 */ + +#ifndef _WIN32 +static int GlobErrorHandler(const char *epath, int eerrno) +{ + if (eerrno == ENOTDIR) + return 0; + + return eerrno; +} +#endif /* _WIN32 */ + +/** + * Calls the specified callback for each file matching the path specification. + * + * @param pathSpec The path specification. + * @param callback The callback which is invoked for each matching file. + * @param type The file type (a combination of GlobFile and GlobDirectory) + */ +bool Utility::Glob(const String& pathSpec, const std::function<void (const String&)>& callback, int type) +{ + std::vector<String> files, dirs; + +#ifdef _WIN32 + std::vector<String> tokens = pathSpec.Split("\\/"); + + String part1; + + for (std::vector<String>::size_type i = 0; i < tokens.size() - 1; i++) { + const String& token = tokens[i]; + + if (!part1.IsEmpty()) + part1 += "/"; + + part1 += token; + + if (token.FindFirstOf("?*") != String::NPos) { + String part2; + + for (std::vector<String>::size_type k = i + 1; k < tokens.size(); k++) { + if (!part2.IsEmpty()) + part2 += "/"; + + part2 += tokens[k]; + } + + std::vector<String> files2, dirs2; + + if (!GlobHelper(part1, GlobDirectory, files2, dirs2)) + return false; + + for (const String& dir : dirs2) { + if (!Utility::Glob(dir + "/" + part2, callback, type)) + return false; + } + + return true; + } + } + + if (!GlobHelper(part1 + "/" + tokens[tokens.size() - 1], type, files, dirs)) + return false; +#else /* _WIN32 */ + glob_t gr; + + int rc = glob(pathSpec.CStr(), GLOB_NOSORT, GlobErrorHandler, &gr); + + if (rc) { + if (rc == GLOB_NOMATCH) + return false; + + BOOST_THROW_EXCEPTION(posix_error() + << boost::errinfo_api_function("glob") + << boost::errinfo_errno(errno) + << boost::errinfo_file_name(pathSpec)); + } + + if (gr.gl_pathc == 0) { + globfree(&gr); + return false; + } + + size_t left; + char **gp; + for (gp = gr.gl_pathv, left = gr.gl_pathc; left > 0; gp++, left--) { + struct stat statbuf; + + if (stat(*gp, &statbuf) < 0) + continue; + + if (!S_ISDIR(statbuf.st_mode) && !S_ISREG(statbuf.st_mode)) + continue; + + if (S_ISDIR(statbuf.st_mode) && (type & GlobDirectory)) + dirs.emplace_back(*gp); + else if (!S_ISDIR(statbuf.st_mode) && (type & GlobFile)) + files.emplace_back(*gp); + } + + globfree(&gr); +#endif /* _WIN32 */ + + std::sort(files.begin(), files.end()); + for (const String& cpath : files) { + callback(cpath); + } + + std::sort(dirs.begin(), dirs.end()); + for (const String& cpath : dirs) { + callback(cpath); + } + + return true; +} + +/** + * Calls the specified callback for each file in the specified directory + * or any of its child directories if the file name matches the specified + * pattern. + * + * @param path The path. + * @param pattern The pattern. + * @param callback The callback which is invoked for each matching file. + * @param type The file type (a combination of GlobFile and GlobDirectory) + */ +bool Utility::GlobRecursive(const String& path, const String& pattern, const std::function<void (const String&)>& callback, int type) +{ + std::vector<String> files, dirs, alldirs; + +#ifdef _WIN32 + HANDLE handle; + WIN32_FIND_DATA wfd; + + String pathSpec = path + "/*"; + + handle = FindFirstFile(pathSpec.CStr(), &wfd); + + if (handle == INVALID_HANDLE_VALUE) { + DWORD errorCode = GetLastError(); + + if (errorCode == ERROR_FILE_NOT_FOUND) + return false; + + BOOST_THROW_EXCEPTION(win32_error() + << boost::errinfo_api_function("FindFirstFile") + << errinfo_win32_error(errorCode) + << boost::errinfo_file_name(pathSpec)); + } + + do { + if (strcmp(wfd.cFileName, ".") == 0 || strcmp(wfd.cFileName, "..") == 0) + continue; + + String cpath = path + "/" + wfd.cFileName; + + if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + alldirs.push_back(cpath); + + if (!Utility::Match(pattern, wfd.cFileName)) + continue; + + if (!(wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && (type & GlobFile)) + files.push_back(cpath); + + if ((wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && (type & GlobDirectory)) + dirs.push_back(cpath); + } while (FindNextFile(handle, &wfd)); + + if (!FindClose(handle)) { + BOOST_THROW_EXCEPTION(win32_error() + << boost::errinfo_api_function("FindClose") + << errinfo_win32_error(GetLastError())); + } +#else /* _WIN32 */ + DIR *dirp; + + dirp = opendir(path.CStr()); + + if (!dirp) + BOOST_THROW_EXCEPTION(posix_error() + << boost::errinfo_api_function("opendir") + << boost::errinfo_errno(errno) + << boost::errinfo_file_name(path)); + + while (dirp) { + dirent *pent; + + errno = 0; + pent = readdir(dirp); + if (!pent && errno != 0) { + closedir(dirp); + + BOOST_THROW_EXCEPTION(posix_error() + << boost::errinfo_api_function("readdir") + << boost::errinfo_errno(errno) + << boost::errinfo_file_name(path)); + } + + if (!pent) + break; + + if (strcmp(pent->d_name, ".") == 0 || strcmp(pent->d_name, "..") == 0) + continue; + + String cpath = path + "/" + pent->d_name; + + struct stat statbuf; + + if (stat(cpath.CStr(), &statbuf) < 0) + continue; + + if (S_ISDIR(statbuf.st_mode)) + alldirs.push_back(cpath); + + if (!Utility::Match(pattern, pent->d_name)) + continue; + + if (S_ISDIR(statbuf.st_mode) && (type & GlobDirectory)) + dirs.push_back(cpath); + + if (!S_ISDIR(statbuf.st_mode) && (type & GlobFile)) + files.push_back(cpath); + } + + closedir(dirp); + +#endif /* _WIN32 */ + + std::sort(files.begin(), files.end()); + for (const String& cpath : files) { + callback(cpath); + } + + std::sort(dirs.begin(), dirs.end()); + for (const String& cpath : dirs) { + callback(cpath); + } + + std::sort(alldirs.begin(), alldirs.end()); + for (const String& cpath : alldirs) { + GlobRecursive(cpath, pattern, callback, type); + } + + return true; +} + + +void Utility::MkDir(const String& path, int mode) +{ + +#ifndef _WIN32 + if (mkdir(path.CStr(), mode) < 0 && errno != EEXIST) { +#else /*_ WIN32 */ + if (mkdir(path.CStr()) < 0 && errno != EEXIST) { +#endif /* _WIN32 */ + + BOOST_THROW_EXCEPTION(posix_error() + << boost::errinfo_api_function("mkdir") + << boost::errinfo_errno(errno) + << boost::errinfo_file_name(path)); + } +} + +void Utility::MkDirP(const String& path, int mode) +{ + size_t pos = 0; + + while (pos != String::NPos) { +#ifndef _WIN32 + pos = path.Find("/", pos + 1); +#else /*_ WIN32 */ + pos = path.FindFirstOf("/\\", pos + 1); +#endif /* _WIN32 */ + + String spath = path.SubStr(0, pos + 1); + struct stat statbuf; + if (stat(spath.CStr(), &statbuf) < 0 && errno == ENOENT) + MkDir(path.SubStr(0, pos), mode); + } +} + +void Utility::Remove(const String& path) +{ + namespace fs = boost::filesystem; + + (void)fs::remove(fs::path(path.Begin(), path.End())); +} + +void Utility::RemoveDirRecursive(const String& path) +{ + namespace fs = boost::filesystem; + + (void)fs::remove_all(fs::path(path.Begin(), path.End())); +} + +/* + * Copies a source file to a target location. + * Caller must ensure that the target's base directory exists and is writable. + */ +void Utility::CopyFile(const String& source, const String& target) +{ + namespace fs = boost::filesystem; + +#if BOOST_VERSION >= 107400 + fs::copy_file(fs::path(source.Begin(), source.End()), fs::path(target.Begin(), target.End()), fs::copy_options::overwrite_existing); +#else /* BOOST_VERSION */ + fs::copy_file(fs::path(source.Begin(), source.End()), fs::path(target.Begin(), target.End()), fs::copy_option::overwrite_if_exists); +#endif /* BOOST_VERSION */ +} + +/* + * Renames a source file to a target location. + * Caller must ensure that the target's base directory exists and is writable. + */ +void Utility::RenameFile(const String& source, const String& target) +{ + namespace fs = boost::filesystem; + + fs::path sourcePath(source.Begin(), source.End()), targetPath(target.Begin(), target.End()); + +#ifndef _WIN32 + fs::rename(sourcePath, targetPath); +#else /* _WIN32 */ + /* + * Renaming files can be tricky on Windows, especially if your application is built around POSIX filesystem + * semantics. For example, the quite common pattern of replacing a file by writing a new version to a temporary + * location and then moving it to the final location can fail if the destination file already exists and any + * process has an open file handle to it. + * + * We try to handle this situation as best as we can by retrying the rename operation a few times hoping the other + * process closes its file handle in the meantime. This is similar to what for example Go does internally in some + * situations (https://golang.org/pkg/cmd/go/internal/robustio/#Rename): + * + * robustio.Rename is like os.Rename, but on Windows retries errors that may occur if the file is concurrently + * read or overwritten. (See https://golang.org/issue/31247 and https://golang.org/issue/32188) + */ + + double sleep = 0.1; + int last_error = ERROR_SUCCESS; + + for (int retries = 0, remaining = 15;; retries++, remaining--) { + try { + fs::rename(sourcePath, targetPath); + + if (retries > 0) { + Log(LogWarning, "Utility") << "Renaming '" << source << "' to '" << target + << "' succeeded after " << retries << " retries"; + } + + break; + } catch (const fs::filesystem_error& ex) { + int error = ex.code().value(); + bool ephemeral = error == ERROR_ACCESS_DENIED || + error == ERROR_FILE_NOT_FOUND || + error == ERROR_SHARING_VIOLATION; + + if (remaining <= 0 || !ephemeral) { + throw; // giving up + } + + if (error != last_error) { + Log(LogWarning, "Utility") << "Renaming '" << source << "' to '" << target << "' failed: " + << ex.code().message() << " (trying up to " << remaining << " more times)"; + last_error = error; + } + + Utility::Sleep(sleep); + sleep *= 1.3; + } + } +#endif /* _WIN32 */ +} + +/* + * Set file permissions + */ +bool Utility::SetFileOwnership(const String& file, const String& user, const String& group) +{ +#ifndef _WIN32 + errno = 0; + struct passwd *pw = getpwnam(user.CStr()); + + if (!pw) { + if (errno == 0) { + Log(LogCritical, "cli") + << "Invalid user specified: " << user; + return false; + } else { + Log(LogCritical, "cli") + << "getpwnam() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\""; + return false; + } + } + + errno = 0; + struct group *gr = getgrnam(group.CStr()); + + if (!gr) { + if (errno == 0) { + Log(LogCritical, "cli") + << "Invalid group specified: " << group; + return false; + } else { + Log(LogCritical, "cli") + << "getgrnam() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\""; + return false; + } + } + + if (chown(file.CStr(), pw->pw_uid, gr->gr_gid) < 0) { + Log(LogCritical, "cli") + << "chown() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\""; + return false; + } +#endif /* _WIN32 */ + + return true; +} + +#ifndef _WIN32 +void Utility::SetNonBlocking(int fd, bool nb) +{ + int flags = fcntl(fd, F_GETFL, 0); + + if (flags < 0) { + BOOST_THROW_EXCEPTION(posix_error() + << boost::errinfo_api_function("fcntl") + << boost::errinfo_errno(errno)); + } + + if (nb) + flags |= O_NONBLOCK; + else + flags &= ~O_NONBLOCK; + + if (fcntl(fd, F_SETFL, flags) < 0) { + BOOST_THROW_EXCEPTION(posix_error() + << boost::errinfo_api_function("fcntl") + << boost::errinfo_errno(errno)); + } +} + +void Utility::SetCloExec(int fd, bool cloexec) +{ + int flags = fcntl(fd, F_GETFD, 0); + + if (flags < 0) { + BOOST_THROW_EXCEPTION(posix_error() + << boost::errinfo_api_function("fcntl") + << boost::errinfo_errno(errno)); + } + + if (cloexec) + flags |= FD_CLOEXEC; + else + flags &= ~FD_CLOEXEC; + + if (fcntl(fd, F_SETFD, flags) < 0) { + BOOST_THROW_EXCEPTION(posix_error() + << boost::errinfo_api_function("fcntl") + << boost::errinfo_errno(errno)); + } +} + +void Utility::CloseAllFDs(const std::vector<int>& except, std::function<void(int)> onClose) +{ +#if defined(__linux__) || defined(__APPLE__) + namespace fs = boost::filesystem; + + std::set<int> fds; + +#ifdef __linux__ + const char *dir = "/proc/self/fd"; +#endif /* __linux__ */ +#ifdef __APPLE__ + const char *dir = "/dev/fd"; +#endif /* __APPLE__ */ + + for (fs::directory_iterator current {fs::path(dir)}, end; current != end; ++current) { + auto entry (current->path().filename()); + int fd; + + try { + fd = boost::lexical_cast<int>(entry.c_str()); + } catch (...) { + continue; + } + + fds.emplace(fd); + } + + for (auto fd : except) { + fds.erase(fd); + } + + for (auto fd : fds) { + if (close(fd) >= 0 && onClose) { + onClose(fd); + } + } +#else /* __linux__ || __APPLE__ */ + rlimit rl; + + if (getrlimit(RLIMIT_NOFILE, &rl) >= 0) { + rlim_t maxfds = rl.rlim_max; + + if (maxfds == RLIM_INFINITY) { + maxfds = 65536; + } + + for (int fd = 0; fd < maxfds; ++fd) { + if (std::find(except.begin(), except.end(), fd) == except.end() && close(fd) >= 0 && onClose) { + onClose(fd); + } + } + } +#endif /* __linux__ || __APPLE__ */ +} +#endif /* _WIN32 */ + +void Utility::SetNonBlockingSocket(SOCKET s, bool nb) +{ +#ifndef _WIN32 + SetNonBlocking(s, nb); +#else /* _WIN32 */ + unsigned long lflag = nb; + ioctlsocket(s, FIONBIO, &lflag); +#endif /* _WIN32 */ +} + +void Utility::QueueAsyncCallback(const std::function<void ()>& callback, SchedulerPolicy policy) +{ + Application::GetTP().Post(callback, policy); +} + +String Utility::NaturalJoin(const std::vector<String>& tokens) +{ + String result; + + for (std::vector<String>::size_type i = 0; i < tokens.size(); i++) { + result += tokens[i]; + + if (tokens.size() > i + 1) { + if (i < tokens.size() - 2) + result += ", "; + else if (i == tokens.size() - 2) + result += " and "; + } + } + + return result; +} + +String Utility::Join(const Array::Ptr& tokens, char separator, bool escapeSeparator) +{ + String result; + bool first = true; + + ObjectLock olock(tokens); + for (const Value& vtoken : tokens) { + String token = Convert::ToString(vtoken); + + if (escapeSeparator) { + boost::algorithm::replace_all(token, "\\", "\\\\"); + + char sep_before[2], sep_after[3]; + sep_before[0] = separator; + sep_before[1] = '\0'; + sep_after[0] = '\\'; + sep_after[1] = separator; + sep_after[2] = '\0'; + boost::algorithm::replace_all(token, sep_before, sep_after); + } + + if (first) + first = false; + else + result += String(1, separator); + + result += token; + } + + return result; +} + +String Utility::FormatDuration(double duration) +{ + std::vector<String> tokens; + String result; + + if (duration >= 86400) { + int days = duration / 86400; + tokens.emplace_back(Convert::ToString(days) + (days != 1 ? " days" : " day")); + duration = static_cast<int>(duration) % 86400; + } + + if (duration >= 3600) { + int hours = duration / 3600; + tokens.emplace_back(Convert::ToString(hours) + (hours != 1 ? " hours" : " hour")); + duration = static_cast<int>(duration) % 3600; + } + + if (duration >= 60) { + int minutes = duration / 60; + tokens.emplace_back(Convert::ToString(minutes) + (minutes != 1 ? " minutes" : " minute")); + duration = static_cast<int>(duration) % 60; + } + + if (duration >= 1) { + int seconds = duration; + tokens.emplace_back(Convert::ToString(seconds) + (seconds != 1 ? " seconds" : " second")); + } + + if (tokens.size() == 0) { + int milliseconds = std::floor(duration * 1000); + if (milliseconds >= 1) + tokens.emplace_back(Convert::ToString(milliseconds) + (milliseconds != 1 ? " milliseconds" : " millisecond")); + else + tokens.emplace_back("less than 1 millisecond"); + } + + return NaturalJoin(tokens); +} + +String Utility::FormatDateTime(const char *format, double ts) +{ + char timestamp[128]; + auto tempts = (time_t)ts; /* We don't handle sub-second timestamps here just yet. */ + tm tmthen; + +#ifdef _MSC_VER + tm *temp = localtime(&tempts); + + if (!temp) { + BOOST_THROW_EXCEPTION(posix_error() + << boost::errinfo_api_function("localtime") + << boost::errinfo_errno(errno)); + } + + tmthen = *temp; +#else /* _MSC_VER */ + if (!localtime_r(&tempts, &tmthen)) { + BOOST_THROW_EXCEPTION(posix_error() + << boost::errinfo_api_function("localtime_r") + << boost::errinfo_errno(errno)); + } +#endif /* _MSC_VER */ + + strftime(timestamp, sizeof(timestamp), format, &tmthen); + + return timestamp; +} + +String Utility::FormatErrorNumber(int code) { + std::ostringstream msgbuf; + +#ifdef _WIN32 + char *message; + String result = "Unknown error."; + + DWORD rc = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, code, 0, (char *)&message, + 0, nullptr); + + if (rc != 0) { + result = String(message); + LocalFree(message); + + /* remove trailing new-line characters */ + boost::algorithm::trim_right(result); + } + + msgbuf << code << ", \"" << result << "\""; +#else + msgbuf << strerror(code); +#endif + return msgbuf.str(); +} + +String Utility::EscapeShellCmd(const String& s) +{ + String result; + size_t prev_quote = String::NPos; + int index = -1; + + for (char ch : s) { + bool escape = false; + + index++; + +#ifdef _WIN32 + if (ch == '%' || ch == '"' || ch == '\'') + escape = true; +#else /* _WIN32 */ + if (ch == '"' || ch == '\'') { + /* Find a matching closing quotation character. */ + if (prev_quote == String::NPos && (prev_quote = s.FindFirstOf(ch, index + 1)) != String::NPos) + ; /* Empty statement. */ + else if (prev_quote != String::NPos && s[prev_quote] == ch) + prev_quote = String::NPos; + else + escape = true; + } +#endif /* _WIN32 */ + + if (ch == '#' || ch == '&' || ch == ';' || ch == '`' || ch == '|' || + ch == '*' || ch == '?' || ch == '~' || ch == '<' || ch == '>' || + ch == '^' || ch == '(' || ch == ')' || ch == '[' || ch == ']' || + ch == '{' || ch == '}' || ch == '$' || ch == '\\' || ch == '\x0A' || + ch == '\xFF') + escape = true; + + if (escape) +#ifdef _WIN32 + result += '^'; +#else /* _WIN32 */ + result += '\\'; +#endif /* _WIN32 */ + + result += ch; + } + + return result; +} + +String Utility::EscapeShellArg(const String& s) +{ + String result; + +#ifdef _WIN32 + result = "\""; +#else /* _WIN32 */ + result = "'"; +#endif /* _WIN32 */ + + for (char ch : s) { +#ifdef _WIN32 + if (ch == '"' || ch == '%') { + result += ' '; + } +#else /* _WIN32 */ + if (ch == '\'') + result += "'\\'"; +#endif + result += ch; + } + +#ifdef _WIN32 + result += '"'; +#else /* _WIN32 */ + result += '\''; +#endif /* _WIN32 */ + + return result; +} + +#ifdef _WIN32 +String Utility::EscapeCreateProcessArg(const String& arg) +{ + if (arg.FindFirstOf(" \t\n\v\"") == String::NPos) + return arg; + + String result = "\""; + + for (String::ConstIterator it = arg.Begin(); ; it++) { + int numBackslashes = 0; + + while (it != arg.End() && *it == '\\') { + it++; + numBackslashes++; + } + + if (it == arg.End()) { + result.Append(numBackslashes * 2, '\\'); + break; + } else if (*it == '"') { + result.Append(numBackslashes * 2 + 1, '\\'); + result.Append(1, *it); + } else { + result.Append(numBackslashes, '\\'); + result.Append(1, *it); + } + } + + result += "\""; + + return result; +} +#endif /* _WIN32 */ + +#ifdef _WIN32 +static void WindowsSetThreadName(const char *name) +{ + THREADNAME_INFO info; + info.dwType = 0x1000; + info.szName = name; + info.dwThreadID = -1; + info.dwFlags = 0; + + __try { + RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR *)&info); + } __except(EXCEPTION_EXECUTE_HANDLER) { + /* Nothing to do here. */ + } +} +#endif /* _WIN32 */ + +void Utility::SetThreadName(const String& name, bool os) +{ + m_ThreadName.reset(new String(name)); + + if (!os) + return; + +#ifdef _WIN32 + WindowsSetThreadName(name.CStr()); +#endif /* _WIN32 */ + +#ifdef HAVE_PTHREAD_SET_NAME_NP + pthread_set_name_np(pthread_self(), name.CStr()); +#endif /* HAVE_PTHREAD_SET_NAME_NP */ + +#ifdef HAVE_PTHREAD_SETNAME_NP +# ifdef __APPLE__ + pthread_setname_np(name.CStr()); +# else /* __APPLE__ */ + String tname = name.SubStr(0, 15); + pthread_setname_np(pthread_self(), tname.CStr()); +# endif /* __APPLE__ */ +#endif /* HAVE_PTHREAD_SETNAME_NP */ +} + +String Utility::GetThreadName() +{ + String *name = m_ThreadName.get(); + + if (!name) { + std::ostringstream idbuf; + idbuf << std::this_thread::get_id(); + return idbuf.str(); + } + + return *name; +} + +unsigned long Utility::SDBM(const String& str, size_t len) +{ + unsigned long hash = 0; + size_t current = 0; + + for (char c : str) { + if (current >= len) + break; + + hash = c + (hash << 6) + (hash << 16) - hash; + + current++; + } + + return hash; +} + +String Utility::ParseVersion(const String& v) +{ + /* + * 2.11.0-0.rc1.1 + * v2.10.5 + * r2.10.3 + * v2.11.0-rc1-58-g7c1f716da + */ + boost::regex pattern("^[vr]?(2\\.\\d+\\.\\d+).*$"); + boost::smatch result; + + if (boost::regex_search(v.GetData(), result, pattern)) { + String res(result[1].first, result[1].second); + return res; + } + + // Couldn't not extract anything, return unparsed version + return v; +} + +int Utility::CompareVersion(const String& v1, const String& v2) +{ + std::vector<String> tokensv1 = v1.Split("."); + std::vector<String> tokensv2 = v2.Split("."); + + for (std::vector<String>::size_type i = 0; i < tokensv2.size() - tokensv1.size(); i++) + tokensv1.emplace_back("0"); + + for (std::vector<String>::size_type i = 0; i < tokensv1.size() - tokensv2.size(); i++) + tokensv2.emplace_back("0"); + + for (std::vector<String>::size_type i = 0; i < tokensv1.size(); i++) { + if (Convert::ToLong(tokensv2[i]) > Convert::ToLong(tokensv1[i])) + return 1; + else if (Convert::ToLong(tokensv2[i]) < Convert::ToLong(tokensv1[i])) + return -1; + } + + return 0; +} + +String Utility::GetHostName() +{ + char name[255]; + + if (gethostname(name, sizeof(name)) < 0) + return "localhost"; + + return name; +} + +/** + * Returns the fully-qualified domain name for the host + * we're running on. + * + * @returns The FQDN. + */ +String Utility::GetFQDN() +{ + String hostname = GetHostName(); + + addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = AI_CANONNAME; + + addrinfo *result; + int rc = getaddrinfo(hostname.CStr(), nullptr, &hints, &result); + + if (rc != 0) + result = nullptr; + + if (result) { + if (strcmp(result->ai_canonname, "localhost") != 0) + hostname = result->ai_canonname; + + freeaddrinfo(result); + } + + return hostname; +} + +int Utility::Random() +{ +#ifdef _WIN32 + return rand(); +#else /* _WIN32 */ + unsigned int *seed = m_RandSeed.get(); + + if (!seed) { + seed = new unsigned int(Utility::GetTime()); + m_RandSeed.reset(seed); + } + + return rand_r(seed); +#endif /* _WIN32 */ +} + +tm Utility::LocalTime(time_t ts) +{ +#ifdef _MSC_VER + tm *result = localtime(&ts); + + if (!result) { + BOOST_THROW_EXCEPTION(posix_error() + << boost::errinfo_api_function("localtime") + << boost::errinfo_errno(errno)); + } + + return *result; +#else /* _MSC_VER */ + tm result; + + if (!localtime_r(&ts, &result)) { + BOOST_THROW_EXCEPTION(posix_error() + << boost::errinfo_api_function("localtime_r") + << boost::errinfo_errno(errno)); + } + + return result; +#endif /* _MSC_VER */ +} + +bool Utility::PathExists(const String& path) +{ + namespace fs = boost::filesystem; + + boost::system::error_code ec; + + return fs::exists(fs::path(path.Begin(), path.End()), ec) && !ec; +} + +time_t Utility::GetFileCreationTime(const String& path) +{ + namespace fs = boost::filesystem; + + return fs::last_write_time(boost::lexical_cast<fs::path>(path)); +} + +Value Utility::LoadJsonFile(const String& path) +{ + std::ifstream fp; + fp.open(path.CStr()); + + String json((std::istreambuf_iterator<char>(fp)), std::istreambuf_iterator<char>()); + + fp.close(); + + if (fp.fail()) + BOOST_THROW_EXCEPTION(std::runtime_error("Could not read JSON file '" + path + "'.")); + + return JsonDecode(json); +} + +void Utility::SaveJsonFile(const String& path, int mode, const Value& value) +{ + namespace fs = boost::filesystem; + + std::fstream fp; + String tempFilename = Utility::CreateTempFile(path + ".XXXXXX", mode, fp); + + fp.exceptions(std::ofstream::failbit | std::ofstream::badbit); + fp << JsonEncode(value); + fp.close(); + + RenameFile(tempFilename, path); +} + +static void HexEncode(char ch, std::ostream& os) +{ + const char *hex_chars = "0123456789ABCDEF"; + + os << hex_chars[ch >> 4 & 0x0f]; + os << hex_chars[ch & 0x0f]; +} + +static int HexDecode(char hc) +{ + if (hc >= '0' && hc <= '9') + return hc - '0'; + else if (hc >= 'a' && hc <= 'f') + return hc - 'a' + 10; + else if (hc >= 'A' && hc <= 'F') + return hc - 'A' + 10; + else + BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid hex character.")); +} + +String Utility::EscapeString(const String& s, const String& chars, const bool illegal) +{ + std::ostringstream result; + if (illegal) { + for (char ch : s) { + if (chars.FindFirstOf(ch) != String::NPos || ch == '%') { + result << '%'; + HexEncode(ch, result); + } else + result << ch; + } + } else { + for (char ch : s) { + if (chars.FindFirstOf(ch) == String::NPos || ch == '%') { + result << '%'; + HexEncode(ch, result); + } else + result << ch; + } + } + + return result.str(); +} + +String Utility::UnescapeString(const String& s) +{ + std::ostringstream result; + + for (String::SizeType i = 0; i < s.GetLength(); i++) { + if (s[i] == '%') { + if (i + 2 > s.GetLength() - 1) + BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid escape sequence.")); + + char ch = HexDecode(s[i + 1]) * 16 + HexDecode(s[i + 2]); + result << ch; + + i += 2; + } else + result << s[i]; + } + + return result.str(); +} + +#ifndef _WIN32 +static String UnameHelper(char type) +{ + struct utsname name; + uname(&name); + + switch (type) { + case 'm': + return (char*)name.machine; + case 'n': + return (char*)name.nodename; + case 'r': + return (char*)name.release; + case 's': + return (char*)name.sysname; + case 'v': + return (char*)name.version; + default: + VERIFY(!"Invalid uname query."); + } +} +#endif /* _WIN32 */ +static bool ReleaseHelper(String *platformName, String *platformVersion) +{ +#ifdef _WIN32 + if (platformName) + *platformName = "Windows"; + + if (platformVersion) { + *platformVersion = "Vista"; + if (IsWindowsVistaSP1OrGreater()) + *platformVersion = "Vista SP1"; + if (IsWindowsVistaSP2OrGreater()) + *platformVersion = "Vista SP2"; + if (IsWindows7OrGreater()) + *platformVersion = "7"; + if (IsWindows7SP1OrGreater()) + *platformVersion = "7 SP1"; + if (IsWindows8OrGreater()) + *platformVersion = "8"; + if (IsWindows8Point1OrGreater()) + *platformVersion = "8.1 or greater"; + if (IsWindowsServer()) + *platformVersion += " (Server)"; + } + + return true; +#else /* _WIN32 */ + if (platformName) + *platformName = "Unknown"; + + if (platformVersion) + *platformVersion = "Unknown"; + + /* You have systemd or Ubuntu etc. */ + std::ifstream release("/etc/os-release"); + if (release.is_open()) { + std::string release_line; + while (getline(release, release_line)) { + std::string::size_type pos = release_line.find("="); + + if (pos == std::string::npos) + continue; + + std::string key = release_line.substr(0, pos); + std::string value = release_line.substr(pos + 1); + + std::string::size_type firstQuote = value.find("\""); + + if (firstQuote != std::string::npos) + value.erase(0, firstQuote + 1); + + std::string::size_type lastQuote = value.rfind("\""); + + if (lastQuote != std::string::npos) + value.erase(lastQuote); + + if (platformName && key == "NAME") + *platformName = value; + + if (platformVersion && key == "VERSION") + *platformVersion = value; + } + + return true; + } + + /* You are using a distribution which supports LSB. */ + FILE *fp = popen("type lsb_release >/dev/null 2>&1 && lsb_release -s -i 2>&1", "r"); + + if (fp) { + std::ostringstream msgbuf; + char line[1024]; + while (fgets(line, sizeof(line), fp)) + msgbuf << line; + int status = pclose(fp); + if (WEXITSTATUS(status) == 0) { + if (platformName) + *platformName = msgbuf.str(); + } + } + + fp = popen("type lsb_release >/dev/null 2>&1 && lsb_release -s -r 2>&1", "r"); + + if (fp) { + std::ostringstream msgbuf; + char line[1024]; + while (fgets(line, sizeof(line), fp)) + msgbuf << line; + int status = pclose(fp); + if (WEXITSTATUS(status) == 0) { + if (platformVersion) + *platformVersion = msgbuf.str(); + } + } + + /* OS X */ + fp = popen("type sw_vers >/dev/null 2>&1 && sw_vers -productName 2>&1", "r"); + + if (fp) { + std::ostringstream msgbuf; + char line[1024]; + while (fgets(line, sizeof(line), fp)) + msgbuf << line; + int status = pclose(fp); + if (WEXITSTATUS(status) == 0) { + String info = msgbuf.str(); + info = info.Trim(); + + if (platformName) + *platformName = info; + } + } + + fp = popen("type sw_vers >/dev/null 2>&1 && sw_vers -productVersion 2>&1", "r"); + + if (fp) { + std::ostringstream msgbuf; + char line[1024]; + while (fgets(line, sizeof(line), fp)) + msgbuf << line; + int status = pclose(fp); + if (WEXITSTATUS(status) == 0) { + String info = msgbuf.str(); + info = info.Trim(); + + if (platformVersion) + *platformVersion = info; + + return true; + } + } + + /* Centos/RHEL < 7 */ + release.close(); + release.open("/etc/redhat-release"); + if (release.is_open()) { + std::string release_line; + getline(release, release_line); + + String info = release_line; + + /* example: Red Hat Enterprise Linux Server release 6.7 (Santiago) */ + if (platformName) + *platformName = info.SubStr(0, info.Find("release") - 1); + + if (platformVersion) + *platformVersion = info.SubStr(info.Find("release") + 8); + + return true; + } + + /* sles 11 sp3, opensuse w/e */ + release.close(); + release.open("/etc/SuSE-release"); + if (release.is_open()) { + std::string release_line; + getline(release, release_line); + + String info = release_line; + + if (platformName) + *platformName = info.SubStr(0, info.FindFirstOf(" ")); + + if (platformVersion) + *platformVersion = info.SubStr(info.FindFirstOf(" ") + 1); + + return true; + } + + /* Just give up */ + return false; +#endif /* _WIN32 */ +} + +String Utility::GetPlatformKernel() +{ +#ifdef _WIN32 + return "Windows"; +#else /* _WIN32 */ + return UnameHelper('s'); +#endif /* _WIN32 */ +} + +String Utility::GetPlatformKernelVersion() +{ +#ifdef _WIN32 + OSVERSIONINFO info; + info.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + GetVersionEx(&info); + + std::ostringstream msgbuf; + msgbuf << info.dwMajorVersion << "." << info.dwMinorVersion; + + return msgbuf.str(); +#else /* _WIN32 */ + return UnameHelper('r'); +#endif /* _WIN32 */ +} + +String Utility::GetPlatformName() +{ + String platformName; + if (!ReleaseHelper(&platformName, nullptr)) + return "Unknown"; + return platformName; +} + +String Utility::GetPlatformVersion() +{ + String platformVersion; + if (!ReleaseHelper(nullptr, &platformVersion)) + return "Unknown"; + return platformVersion; +} + +String Utility::GetPlatformArchitecture() +{ +#ifdef _WIN32 + SYSTEM_INFO info; + GetNativeSystemInfo(&info); + switch (info.wProcessorArchitecture) { + case PROCESSOR_ARCHITECTURE_AMD64: + return "x86_64"; + case PROCESSOR_ARCHITECTURE_ARM: + return "arm"; + case PROCESSOR_ARCHITECTURE_INTEL: + return "x86"; + default: + return "unknown"; + } +#else /* _WIN32 */ + return UnameHelper('m'); +#endif /* _WIN32 */ +} + +const char l_Utf8Replacement[] = "\xEF\xBF\xBD"; + +String Utility::ValidateUTF8(const String& input) +{ + std::string output; + output.reserve(input.GetLength()); + + try { + utf8::replace_invalid(input.Begin(), input.End(), std::back_inserter(output)); + } catch (const utf8::not_enough_room&) { + output.insert(output.end(), (const char*)l_Utf8Replacement, (const char*)l_Utf8Replacement + 3); + } + + return String(std::move(output)); +} + +String Utility::CreateTempFile(const String& path, int mode, std::fstream& fp) +{ + std::vector<char> targetPath(path.Begin(), path.End()); + targetPath.push_back('\0'); + + int fd; +#ifndef _WIN32 + fd = mkstemp(&targetPath[0]); +#else /* _WIN32 */ + fd = MksTemp(&targetPath[0]); +#endif /*_WIN32*/ + + if (fd < 0) { + BOOST_THROW_EXCEPTION(posix_error() + << boost::errinfo_api_function("mkstemp") + << boost::errinfo_errno(errno) + << boost::errinfo_file_name(path)); + } + + try { + fp.open(&targetPath[0], std::ios_base::trunc | std::ios_base::out); + } catch (const std::fstream::failure&) { + close(fd); + throw; + } + + close(fd); + + String resultPath = String(targetPath.begin(), targetPath.end() - 1); + + if (chmod(resultPath.CStr(), mode) < 0) { + BOOST_THROW_EXCEPTION(posix_error() + << boost::errinfo_api_function("chmod") + << boost::errinfo_errno(errno) + << boost::errinfo_file_name(resultPath)); + } + + return resultPath; +} + +#ifdef _WIN32 +/* mkstemp extracted from libc/sysdeps/posix/tempname.c. Copyright + * (C) 1991-1999, 2000, 2001, 2006 Free Software Foundation, Inc. + * + * The GNU C Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + */ + +#define _O_EXCL 0x0400 +#define _O_CREAT 0x0100 +#define _O_RDWR 0x0002 +#define O_EXCL _O_EXCL +#define O_CREAT _O_CREAT +#define O_RDWR _O_RDWR + +static const char letters[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + +/* Generate a temporary file name based on TMPL. TMPL must match the + * rules for mk[s]temp (i.e. end in "XXXXXX"). The name constructed + * does not exist at the time of the call to mkstemp. TMPL is + * overwritten with the result. + */ +int Utility::MksTemp(char *tmpl) +{ + int len; + char *XXXXXX; + static unsigned long long value; + unsigned long long random_time_bits; + unsigned int count; + int fd = -1; + int save_errno = errno; + + /* A lower bound on the number of temporary files to attempt to + * generate. The maximum total number of temporary file names that + * can exist for a given template is 62**6. It should never be + * necessary to try all these combinations. Instead if a reasonable + * number of names is tried (we define reasonable as 62**3) fail to + * give the system administrator the chance to remove the problems. + */ +#define ATTEMPTS_MIN (62 * 62 * 62) + + /* The number of times to attempt to generate a temporary file + * To conform to POSIX, this must be no smaller than TMP_MAX. + */ +#if ATTEMPTS_MIN < TMP_MAX + unsigned int attempts = TMP_MAX; +#else + unsigned int attempts = ATTEMPTS_MIN; +#endif + + len = strlen (tmpl); + if (len < 6 || strcmp (&tmpl[len - 6], "XXXXXX")) { + errno = EINVAL; + return -1; + } + + /* This is where the Xs start. */ + XXXXXX = &tmpl[len - 6]; + + /* Get some more or less random data. */ + { + SYSTEMTIME stNow; + FILETIME ftNow; + + // get system time + GetSystemTime(&stNow); + stNow.wMilliseconds = 500; + if (!SystemTimeToFileTime(&stNow, &ftNow)) { + errno = -1; + return -1; + } + + random_time_bits = (((unsigned long long)ftNow.dwHighDateTime << 32) | (unsigned long long)ftNow.dwLowDateTime); + } + + value += random_time_bits ^ (unsigned long long)GetCurrentThreadId(); + + for (count = 0; count < attempts; value += 7777, ++count) { + unsigned long long v = value; + + /* Fill in the random bits. */ + XXXXXX[0] = letters[v % 62]; + v /= 62; + XXXXXX[1] = letters[v % 62]; + v /= 62; + XXXXXX[2] = letters[v % 62]; + v /= 62; + XXXXXX[3] = letters[v % 62]; + v /= 62; + XXXXXX[4] = letters[v % 62]; + v /= 62; + XXXXXX[5] = letters[v % 62]; + + fd = open(tmpl, O_RDWR | O_CREAT | O_EXCL, _S_IREAD | _S_IWRITE); + if (fd >= 0) { + errno = save_errno; + return fd; + } else if (errno != EEXIST) + return -1; + } + + /* We got out of the loop because we ran out of combinations to try. */ + errno = EEXIST; + return -1; +} + +String Utility::GetIcingaInstallPath() +{ + char szProduct[39]; + + for (int i = 0; MsiEnumProducts(i, szProduct) == ERROR_SUCCESS; i++) { + char szName[128]; + DWORD cbName = sizeof(szName); + if (MsiGetProductInfo(szProduct, INSTALLPROPERTY_INSTALLEDPRODUCTNAME, szName, &cbName) != ERROR_SUCCESS) + continue; + + if (strcmp(szName, "Icinga 2") != 0) + continue; + + char szLocation[1024]; + DWORD cbLocation = sizeof(szLocation); + if (MsiGetProductInfo(szProduct, INSTALLPROPERTY_INSTALLLOCATION, szLocation, &cbLocation) == ERROR_SUCCESS) + return szLocation; + } + + return ""; +} + +String Utility::GetIcingaDataPath() +{ + char path[MAX_PATH]; + if (!SUCCEEDED(SHGetFolderPath(nullptr, CSIDL_COMMON_APPDATA, nullptr, 0, path))) + return ""; + return String(path) + "\\icinga2"; +} + +#endif /* _WIN32 */ + +/** + * Retrieve the environment variable value by given key. + * + * @param env Environment variable name. + */ + +String Utility::GetFromEnvironment(const String& env) +{ + const char *envValue = getenv(env.CStr()); + + if (envValue == NULL) + return String(); + else + return String(envValue); +} + +/** + * Compare the password entered by a client with the actual password. + * The comparision is safe against timing attacks. + */ +bool Utility::ComparePasswords(const String& enteredPassword, const String& actualPassword) +{ + volatile const char * volatile enteredPasswordCStr = enteredPassword.CStr(); + volatile size_t enteredPasswordLen = enteredPassword.GetLength(); + + volatile const char * volatile actualPasswordCStr = actualPassword.CStr(); + volatile size_t actualPasswordLen = actualPassword.GetLength(); + + volatile uint_fast8_t result = enteredPasswordLen == actualPasswordLen; + + if (result) { + auto cStr (actualPasswordCStr); + auto len (actualPasswordLen); + + actualPasswordCStr = cStr; + actualPasswordLen = len; + } else { + auto cStr (enteredPasswordCStr); + auto len (enteredPasswordLen); + + actualPasswordCStr = cStr; + actualPasswordLen = len; + } + + for (volatile size_t i = 0; i < enteredPasswordLen; ++i) { + result &= uint_fast8_t(enteredPasswordCStr[i] == actualPasswordCStr[i]); + } + + return result; +} |