1 /* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
3 #include "base/utility.hpp"
4 #include "base/convert.hpp"
5 #include "base/application.hpp"
6 #include "base/logger.hpp"
7 #include "base/exception.hpp"
8 #include "base/socket.hpp"
9 #include "base/utility.hpp"
10 #include "base/json.hpp"
11 #include "base/objectlock.hpp"
14 #include <boost/filesystem/path.hpp>
15 #include <boost/filesystem/operations.hpp>
16 #include <boost/lexical_cast.hpp>
17 #include <boost/system/error_code.hpp>
18 #include <boost/thread/tss.hpp>
19 #include <boost/algorithm/string/trim.hpp>
20 #include <boost/algorithm/string/replace.hpp>
21 #include <boost/uuid/uuid_io.hpp>
22 #include <boost/uuid/uuid_generators.hpp>
23 #include <boost/regex.hpp>
34 # include <pthread_np.h>
35 #endif /* __FreeBSD__ */
39 #endif /* HAVE_CXXABI_H */
42 # include <sys/types.h>
43 # include <sys/utsname.h>
50 # include <VersionHelpers.h>
57 using namespace icinga;
59 boost::thread_specific_ptr<String> Utility::m_ThreadName;
60 boost::thread_specific_ptr<unsigned int> Utility::m_RandSeed;
63 double Utility::m_DebugTime = -1;
67 * Demangles a symbol name.
69 * @param sym The symbol name.
70 * @returns A human-readable version of the symbol name.
72 String Utility::DemangleSymbolName(const String& sym)
78 char *realname = abi::__cxa_demangle(sym.CStr(), nullptr, nullptr, &status);
81 result = String(realname);
84 #elif defined(_MSC_VER) /* HAVE_CXXABI_H */
87 if (UnDecorateSymbolName(sym.CStr(), output, sizeof(output), UNDNAME_COMPLETE) > 0)
90 /* We're pretty much out of options here. */
97 * Returns a human-readable type name of a type_info object.
99 * @param ti A type_info object.
100 * @returns The type name of the object.
102 String Utility::GetTypeName(const std::type_info& ti)
104 return DemangleSymbolName(ti.name());
107 String Utility::GetSymbolName(const void *addr)
112 if (dladdr(const_cast<void *>(addr), &dli) > 0)
113 return dli.dli_sname;
114 #endif /* HAVE_DLADDR */
117 char buffer[sizeof(SYMBOL_INFO)+MAX_SYM_NAME * sizeof(TCHAR)];
118 PSYMBOL_INFO pSymbol = (PSYMBOL_INFO)buffer;
119 pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO);
120 pSymbol->MaxNameLen = MAX_SYM_NAME;
122 DWORD64 dwAddress = (DWORD64)addr;
123 DWORD64 dwDisplacement;
125 IMAGEHLP_LINE64 line;
126 line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
128 if (SymFromAddr(GetCurrentProcess(), dwAddress, &dwDisplacement, pSymbol)) {
130 if (UnDecorateSymbolName(pSymbol->Name, output, sizeof(output), UNDNAME_COMPLETE))
131 return String(output) + "+" + Convert::ToString(dwDisplacement);
133 return String(pSymbol->Name) + "+" + Convert::ToString(dwDisplacement);
137 return "(unknown function)";
141 * Performs wildcard pattern matching.
143 * @param pattern The wildcard pattern.
144 * @param text The String that should be checked.
145 * @returns true if the wildcard pattern matches, false otherwise.
147 bool Utility::Match(const String& pattern, const String& text)
149 return (match(pattern.CStr(), text.CStr()) == 0);
152 static bool ParseIp(const String& ip, char addr[16], int *proto)
154 if (inet_pton(AF_INET, ip.CStr(), addr + 12) == 1) {
155 /* IPv4-mapped IPv6 address (::ffff:<ipv4-bits>) */
157 memset(addr + 10, 0xff, 2);
163 if (inet_pton(AF_INET6, ip.CStr(), addr) == 1) {
172 static void ParseIpMask(const String& ip, char mask[16], int *bits)
174 String::SizeType slashp = ip.FindFirstOf("/");
177 if (slashp == String::NPos) {
181 uip = ip.SubStr(0, slashp);
182 *bits = Convert::ToLong(ip.SubStr(slashp + 1));
187 if (!ParseIp(uip, mask, &proto))
188 BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid IP address specified."));
190 if (proto == AF_INET) {
191 if (*bits > 32 || *bits < 0)
192 BOOST_THROW_EXCEPTION(std::invalid_argument("Mask must be between 0 and 32 for IPv4 CIDR masks."));
197 if (slashp == String::NPos)
200 if (*bits > 128 || *bits < 0)
201 BOOST_THROW_EXCEPTION(std::invalid_argument("Mask must be between 0 and 128 for IPv6 CIDR masks."));
203 for (int i = 0; i < 16; i++) {
204 int lbits = std::max(0, *bits - i * 8);
209 if (mask[i] & (0xff >> lbits))
210 BOOST_THROW_EXCEPTION(std::invalid_argument("Masked-off bits must all be zero."));
214 static bool IpMaskCheck(char addr[16], char mask[16], int bits)
216 for (int i = 0; i < 16; i++) {
218 return !((addr[i] ^ mask[i]) >> (8 - bits));
220 if (mask[i] != addr[i])
232 bool Utility::CidrMatch(const String& pattern, const String& ip)
237 ParseIpMask(pattern, mask, &bits);
242 if (!ParseIp(ip, addr, &proto))
245 return IpMaskCheck(addr, mask, bits);
249 * Returns the directory component of a path. See dirname(3) for details.
251 * @param path The full path.
252 * @returns The directory.
254 String Utility::DirName(const String& path)
256 return boost::filesystem::path(path.Begin(), path.End()).parent_path().string();
260 * Returns the file component of a path. See basename(3) for details.
262 * @param path The full path.
263 * @returns The filename.
265 String Utility::BaseName(const String& path)
267 return boost::filesystem::path(path.Begin(), path.End()).filename().string();
271 * Null deleter. Used as a parameter for the shared_ptr constructor.
273 * @param - The object that should be deleted.
275 void Utility::NullDeleter(void *)
277 /* Nothing to do here. */
282 * (DEBUG / TESTING ONLY) Sets the current system time to a static value,
283 * that will be be retrieved by any component of Icinga, when using GetTime().
285 * This should be only used for testing purposes, e.g. unit tests and debugging of certain functionalities.
287 void Utility::SetTime(double time)
293 * (DEBUG / TESTING ONLY) Increases the set debug system time by X seconds.
295 * This should be only used for testing purposes, e.g. unit tests and debugging of certain functionalities.
297 void Utility::IncrementTime(double diff)
301 #endif /* I2_DEBUG */
304 * Returns the current UNIX timestamp including fractions of seconds.
306 * @returns The current time.
308 double Utility::GetTime()
311 if (m_DebugTime >= 0) {
312 // (DEBUG / TESTING ONLY) this will return a *STATIC* system time, if the value has been set!
315 #endif /* I2_DEBUG */
318 GetSystemTimeAsFileTime(&cft);
321 ucft.HighPart = cft.dwHighDateTime;
322 ucft.LowPart = cft.dwLowDateTime;
324 SYSTEMTIME est = { 1970, 1, 4, 1, 0, 0, 0, 0};
326 SystemTimeToFileTime(&est, &eft);
329 ueft.HighPart = eft.dwHighDateTime;
330 ueft.LowPart = eft.dwLowDateTime;
332 return ((ucft.QuadPart - ueft.QuadPart) / 10000) / 1000.0;
336 int rc = gettimeofday(&tv, nullptr);
339 return tv.tv_sec + tv.tv_usec / 1000000.0;
344 * Returns the ID of the current process.
348 pid_t Utility::GetPid()
353 return GetCurrentProcessId();
358 * Sleeps for the specified amount of time.
360 * @param timeout The timeout in seconds.
362 void Utility::Sleep(double timeout)
365 unsigned long micros = timeout * 1000000u;
367 sleep((unsigned)timeout);
369 usleep(micros % 1000000u);
371 ::Sleep(timeout * 1000);
376 * Generates a new unique ID.
378 * @returns The new unique ID.
380 String Utility::NewUniqueID()
382 return boost::lexical_cast<std::string>(boost::uuids::random_generator()());
386 static bool GlobHelper(const String& pathSpec, int type, std::vector<String>& files, std::vector<String>& dirs)
391 handle = FindFirstFile(pathSpec.CStr(), &wfd);
393 if (handle == INVALID_HANDLE_VALUE) {
394 DWORD errorCode = GetLastError();
396 if (errorCode == ERROR_FILE_NOT_FOUND)
399 BOOST_THROW_EXCEPTION(win32_error()
400 << boost::errinfo_api_function("FindFirstFile")
401 << errinfo_win32_error(errorCode)
402 << boost::errinfo_file_name(pathSpec));
406 if (strcmp(wfd.cFileName, ".") == 0 || strcmp(wfd.cFileName, "..") == 0)
409 String path = Utility::DirName(pathSpec) + "/" + wfd.cFileName;
411 if ((wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && (type & GlobDirectory))
412 dirs.push_back(path);
413 else if (!(wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && (type & GlobFile))
414 files.push_back(path);
415 } while (FindNextFile(handle, &wfd));
417 if (!FindClose(handle)) {
418 BOOST_THROW_EXCEPTION(win32_error()
419 << boost::errinfo_api_function("FindClose")
420 << errinfo_win32_error(GetLastError()));
428 static int GlobErrorHandler(const char *epath, int eerrno)
430 if (eerrno == ENOTDIR)
438 * Calls the specified callback for each file matching the path specification.
440 * @param pathSpec The path specification.
441 * @param callback The callback which is invoked for each matching file.
442 * @param type The file type (a combination of GlobFile and GlobDirectory)
444 bool Utility::Glob(const String& pathSpec, const std::function<void (const String&)>& callback, int type)
446 std::vector<String> files, dirs;
449 std::vector<String> tokens = pathSpec.Split("\\/");
453 for (std::vector<String>::size_type i = 0; i < tokens.size() - 1; i++) {
454 const String& token = tokens[i];
456 if (!part1.IsEmpty())
461 if (token.FindFirstOf("?*") != String::NPos) {
464 for (std::vector<String>::size_type k = i + 1; k < tokens.size(); k++) {
465 if (!part2.IsEmpty())
471 std::vector<String> files2, dirs2;
473 if (!GlobHelper(part1, GlobDirectory, files2, dirs2))
476 for (const String& dir : dirs2) {
477 if (!Utility::Glob(dir + "/" + part2, callback, type))
485 if (!GlobHelper(part1 + "/" + tokens[tokens.size() - 1], type, files, dirs))
490 int rc = glob(pathSpec.CStr(), GLOB_NOSORT, GlobErrorHandler, &gr);
493 if (rc == GLOB_NOMATCH)
496 BOOST_THROW_EXCEPTION(posix_error()
497 << boost::errinfo_api_function("glob")
498 << boost::errinfo_errno(errno)
499 << boost::errinfo_file_name(pathSpec));
502 if (gr.gl_pathc == 0) {
509 for (gp = gr.gl_pathv, left = gr.gl_pathc; left > 0; gp++, left--) {
512 if (stat(*gp, &statbuf) < 0)
515 if (!S_ISDIR(statbuf.st_mode) && !S_ISREG(statbuf.st_mode))
518 if (S_ISDIR(statbuf.st_mode) && (type & GlobDirectory))
519 dirs.emplace_back(*gp);
520 else if (!S_ISDIR(statbuf.st_mode) && (type & GlobFile))
521 files.emplace_back(*gp);
527 std::sort(files.begin(), files.end());
528 for (const String& cpath : files) {
532 std::sort(dirs.begin(), dirs.end());
533 for (const String& cpath : dirs) {
541 * Calls the specified callback for each file in the specified directory
542 * or any of its child directories if the file name matches the specified
545 * @param path The path.
546 * @param pattern The pattern.
547 * @param callback The callback which is invoked for each matching file.
548 * @param type The file type (a combination of GlobFile and GlobDirectory)
550 bool Utility::GlobRecursive(const String& path, const String& pattern, const std::function<void (const String&)>& callback, int type)
552 std::vector<String> files, dirs, alldirs;
558 String pathSpec = path + "/*";
560 handle = FindFirstFile(pathSpec.CStr(), &wfd);
562 if (handle == INVALID_HANDLE_VALUE) {
563 DWORD errorCode = GetLastError();
565 if (errorCode == ERROR_FILE_NOT_FOUND)
568 BOOST_THROW_EXCEPTION(win32_error()
569 << boost::errinfo_api_function("FindFirstFile")
570 << errinfo_win32_error(errorCode)
571 << boost::errinfo_file_name(pathSpec));
575 if (strcmp(wfd.cFileName, ".") == 0 || strcmp(wfd.cFileName, "..") == 0)
578 String cpath = path + "/" + wfd.cFileName;
580 if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
581 alldirs.push_back(cpath);
583 if (!Utility::Match(pattern, wfd.cFileName))
586 if (!(wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && (type & GlobFile))
587 files.push_back(cpath);
589 if ((wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && (type & GlobDirectory))
590 dirs.push_back(cpath);
591 } while (FindNextFile(handle, &wfd));
593 if (!FindClose(handle)) {
594 BOOST_THROW_EXCEPTION(win32_error()
595 << boost::errinfo_api_function("FindClose")
596 << errinfo_win32_error(GetLastError()));
601 dirp = opendir(path.CStr());
604 BOOST_THROW_EXCEPTION(posix_error()
605 << boost::errinfo_api_function("opendir")
606 << boost::errinfo_errno(errno)
607 << boost::errinfo_file_name(path));
613 pent = readdir(dirp);
614 if (!pent && errno != 0) {
617 BOOST_THROW_EXCEPTION(posix_error()
618 << boost::errinfo_api_function("readdir")
619 << boost::errinfo_errno(errno)
620 << boost::errinfo_file_name(path));
626 if (strcmp(pent->d_name, ".") == 0 || strcmp(pent->d_name, "..") == 0)
629 String cpath = path + "/" + pent->d_name;
633 if (stat(cpath.CStr(), &statbuf) < 0)
636 if (S_ISDIR(statbuf.st_mode))
637 alldirs.push_back(cpath);
639 if (!Utility::Match(pattern, pent->d_name))
642 if (S_ISDIR(statbuf.st_mode) && (type & GlobDirectory))
643 dirs.push_back(cpath);
645 if (!S_ISDIR(statbuf.st_mode) && (type & GlobFile))
646 files.push_back(cpath);
653 std::sort(files.begin(), files.end());
654 for (const String& cpath : files) {
658 std::sort(dirs.begin(), dirs.end());
659 for (const String& cpath : dirs) {
663 std::sort(alldirs.begin(), alldirs.end());
664 for (const String& cpath : alldirs) {
665 GlobRecursive(cpath, pattern, callback, type);
672 void Utility::MkDir(const String& path, int mode)
676 if (mkdir(path.CStr(), mode) < 0 && errno != EEXIST) {
678 if (mkdir(path.CStr()) < 0 && errno != EEXIST) {
681 BOOST_THROW_EXCEPTION(posix_error()
682 << boost::errinfo_api_function("mkdir")
683 << boost::errinfo_errno(errno)
684 << boost::errinfo_file_name(path));
688 void Utility::MkDirP(const String& path, int mode)
692 while (pos != String::NPos) {
694 pos = path.Find("/", pos + 1);
696 pos = path.FindFirstOf("/\\", pos + 1);
699 String spath = path.SubStr(0, pos + 1);
701 if (stat(spath.CStr(), &statbuf) < 0 && errno == ENOENT)
702 MkDir(path.SubStr(0, pos), mode);
706 void Utility::Remove(const String& path)
708 namespace fs = boost::filesystem;
710 (void)fs::remove(fs::path(path.Begin(), path.End()));
713 void Utility::RemoveDirRecursive(const String& path)
715 namespace fs = boost::filesystem;
717 (void)fs::remove_all(fs::path(path.Begin(), path.End()));
721 * Copies a source file to a target location.
722 * Caller must ensure that the target's base directory exists and is writable.
724 void Utility::CopyFile(const String& source, const String& target)
726 namespace fs = boost::filesystem;
728 fs::copy_file(fs::path(source.Begin(), source.End()), fs::path(target.Begin(), target.End()), fs::copy_option::overwrite_if_exists);
732 * Renames a source file to a target location.
733 * Caller must ensure that the target's base directory exists and is writable.
735 void Utility::RenameFile(const String& source, const String& target)
737 namespace fs = boost::filesystem;
739 fs::rename(fs::path(source.Begin(), source.End()), fs::path(target.Begin(), target.End()));
743 * Set file permissions
745 bool Utility::SetFileOwnership(const String& file, const String& user, const String& group)
749 struct passwd *pw = getpwnam(user.CStr());
753 Log(LogCritical, "cli")
754 << "Invalid user specified: " << user;
757 Log(LogCritical, "cli")
758 << "getpwnam() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
764 struct group *gr = getgrnam(group.CStr());
768 Log(LogCritical, "cli")
769 << "Invalid group specified: " << group;
772 Log(LogCritical, "cli")
773 << "getgrnam() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
778 if (chown(file.CStr(), pw->pw_uid, gr->gr_gid) < 0) {
779 Log(LogCritical, "cli")
780 << "chown() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
789 void Utility::SetNonBlocking(int fd, bool nb)
791 int flags = fcntl(fd, F_GETFL, 0);
794 BOOST_THROW_EXCEPTION(posix_error()
795 << boost::errinfo_api_function("fcntl")
796 << boost::errinfo_errno(errno));
802 flags &= ~O_NONBLOCK;
804 if (fcntl(fd, F_SETFL, flags) < 0) {
805 BOOST_THROW_EXCEPTION(posix_error()
806 << boost::errinfo_api_function("fcntl")
807 << boost::errinfo_errno(errno));
811 void Utility::SetCloExec(int fd, bool cloexec)
813 int flags = fcntl(fd, F_GETFD, 0);
816 BOOST_THROW_EXCEPTION(posix_error()
817 << boost::errinfo_api_function("fcntl")
818 << boost::errinfo_errno(errno));
824 flags &= ~FD_CLOEXEC;
826 if (fcntl(fd, F_SETFD, flags) < 0) {
827 BOOST_THROW_EXCEPTION(posix_error()
828 << boost::errinfo_api_function("fcntl")
829 << boost::errinfo_errno(errno));
834 void Utility::SetNonBlockingSocket(SOCKET s, bool nb)
837 SetNonBlocking(s, nb);
839 unsigned long lflag = nb;
840 ioctlsocket(s, FIONBIO, &lflag);
844 void Utility::QueueAsyncCallback(const std::function<void ()>& callback, SchedulerPolicy policy)
846 Application::GetTP().Post(callback, policy);
849 String Utility::NaturalJoin(const std::vector<String>& tokens)
853 for (std::vector<String>::size_type i = 0; i < tokens.size(); i++) {
856 if (tokens.size() > i + 1) {
857 if (i < tokens.size() - 2)
859 else if (i == tokens.size() - 2)
867 String Utility::Join(const Array::Ptr& tokens, char separator, bool escapeSeparator)
872 ObjectLock olock(tokens);
873 for (const Value& vtoken : tokens) {
874 String token = Convert::ToString(vtoken);
876 if (escapeSeparator) {
877 boost::algorithm::replace_all(token, "\\", "\\\\");
879 char sep_before[2], sep_after[3];
880 sep_before[0] = separator;
881 sep_before[1] = '\0';
883 sep_after[1] = separator;
885 boost::algorithm::replace_all(token, sep_before, sep_after);
891 result += String(1, separator);
899 String Utility::FormatDuration(double duration)
901 std::vector<String> tokens;
904 if (duration >= 86400) {
905 int days = duration / 86400;
906 tokens.emplace_back(Convert::ToString(days) + (days != 1 ? " days" : " day"));
907 duration = static_cast<int>(duration) % 86400;
910 if (duration >= 3600) {
911 int hours = duration / 3600;
912 tokens.emplace_back(Convert::ToString(hours) + (hours != 1 ? " hours" : " hour"));
913 duration = static_cast<int>(duration) % 3600;
916 if (duration >= 60) {
917 int minutes = duration / 60;
918 tokens.emplace_back(Convert::ToString(minutes) + (minutes != 1 ? " minutes" : " minute"));
919 duration = static_cast<int>(duration) % 60;
923 int seconds = duration;
924 tokens.emplace_back(Convert::ToString(seconds) + (seconds != 1 ? " seconds" : " second"));
927 if (tokens.size() == 0) {
928 int milliseconds = std::floor(duration * 1000);
929 if (milliseconds >= 1)
930 tokens.emplace_back(Convert::ToString(milliseconds) + (milliseconds != 1 ? " milliseconds" : " millisecond"));
932 tokens.emplace_back("less than 1 millisecond");
935 return NaturalJoin(tokens);
938 String Utility::FormatDateTime(const char *format, double ts)
941 auto tempts = (time_t)ts; /* We don't handle sub-second timestamps here just yet. */
945 tm *temp = localtime(&tempts);
948 BOOST_THROW_EXCEPTION(posix_error()
949 << boost::errinfo_api_function("localtime")
950 << boost::errinfo_errno(errno));
955 if (!localtime_r(&tempts, &tmthen)) {
956 BOOST_THROW_EXCEPTION(posix_error()
957 << boost::errinfo_api_function("localtime_r")
958 << boost::errinfo_errno(errno));
960 #endif /* _MSC_VER */
962 strftime(timestamp, sizeof(timestamp), format, &tmthen);
967 String Utility::FormatErrorNumber(int code) {
968 std::ostringstream msgbuf;
972 String result = "Unknown error.";
974 DWORD rc = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
975 FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, code, 0, (char *)&message,
979 result = String(message);
982 /* remove trailing new-line characters */
983 boost::algorithm::trim_right(result);
986 msgbuf << code << ", \"" << result << "\"";
988 msgbuf << strerror(code);
993 String Utility::EscapeShellCmd(const String& s)
996 size_t prev_quote = String::NPos;
1000 bool escape = false;
1005 if (ch == '%' || ch == '"' || ch == '\'')
1008 if (ch == '"' || ch == '\'') {
1009 /* Find a matching closing quotation character. */
1010 if (prev_quote == String::NPos && (prev_quote = s.FindFirstOf(ch, index + 1)) != String::NPos)
1011 ; /* Empty statement. */
1012 else if (prev_quote != String::NPos && s[prev_quote] == ch)
1013 prev_quote = String::NPos;
1019 if (ch == '#' || ch == '&' || ch == ';' || ch == '`' || ch == '|' ||
1020 ch == '*' || ch == '?' || ch == '~' || ch == '<' || ch == '>' ||
1021 ch == '^' || ch == '(' || ch == ')' || ch == '[' || ch == ']' ||
1022 ch == '{' || ch == '}' || ch == '$' || ch == '\\' || ch == '\x0A' ||
1039 String Utility::EscapeShellArg(const String& s)
1051 if (ch == '"' || ch == '%') {
1071 String Utility::EscapeCreateProcessArg(const String& arg)
1073 if (arg.FindFirstOf(" \t\n\v\"") == String::NPos)
1076 String result = "\"";
1078 for (String::ConstIterator it = arg.Begin(); ; it++) {
1079 int numBackslashes = 0;
1081 while (it != arg.End() && *it == '\\') {
1086 if (it == arg.End()) {
1087 result.Append(numBackslashes * 2, '\\');
1089 } else if (*it == '"') {
1090 result.Append(numBackslashes * 2, '\\');
1091 result.Append(1, *it);
1093 result.Append(numBackslashes, '\\');
1094 result.Append(1, *it);
1105 static void WindowsSetThreadName(const char *name)
1107 THREADNAME_INFO info;
1108 info.dwType = 0x1000;
1110 info.dwThreadID = -1;
1114 RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR *)&info);
1115 } __except(EXCEPTION_EXECUTE_HANDLER) {
1116 /* Nothing to do here. */
1121 void Utility::SetThreadName(const String& name, bool os)
1123 m_ThreadName.reset(new String(name));
1129 WindowsSetThreadName(name.CStr());
1132 #ifdef HAVE_PTHREAD_SET_NAME_NP
1133 pthread_set_name_np(pthread_self(), name.CStr());
1134 #endif /* HAVE_PTHREAD_SET_NAME_NP */
1136 #ifdef HAVE_PTHREAD_SETNAME_NP
1138 pthread_setname_np(name.CStr());
1139 # else /* __APPLE__ */
1140 String tname = name.SubStr(0, 15);
1141 pthread_setname_np(pthread_self(), tname.CStr());
1142 # endif /* __APPLE__ */
1143 #endif /* HAVE_PTHREAD_SETNAME_NP */
1146 String Utility::GetThreadName()
1148 String *name = m_ThreadName.get();
1151 std::ostringstream idbuf;
1152 idbuf << std::this_thread::get_id();
1159 unsigned long Utility::SDBM(const String& str, size_t len)
1161 unsigned long hash = 0;
1164 for (char c : str) {
1168 hash = c + (hash << 6) + (hash << 16) - hash;
1176 String Utility::ParseVersion(const String& v)
1182 * v2.11.0-rc1-58-g7c1f716da
1184 boost::regex pattern("^[vr]?(2\\.\\d+\\.\\d+).*$");
1185 boost::smatch result;
1187 if (boost::regex_search(v.GetData(), result, pattern)) {
1188 String res(result[1].first, result[1].second);
1192 // Couldn't not extract anything, return unparsed version
1196 int Utility::CompareVersion(const String& v1, const String& v2)
1198 std::vector<String> tokensv1 = v1.Split(".");
1199 std::vector<String> tokensv2 = v2.Split(".");
1201 for (std::vector<String>::size_type i = 0; i < tokensv2.size() - tokensv1.size(); i++)
1202 tokensv1.emplace_back("0");
1204 for (std::vector<String>::size_type i = 0; i < tokensv1.size() - tokensv2.size(); i++)
1205 tokensv2.emplace_back("0");
1207 for (std::vector<String>::size_type i = 0; i < tokensv1.size(); i++) {
1208 if (Convert::ToLong(tokensv2[i]) > Convert::ToLong(tokensv1[i]))
1210 else if (Convert::ToLong(tokensv2[i]) < Convert::ToLong(tokensv1[i]))
1217 String Utility::GetHostName()
1221 if (gethostname(name, sizeof(name)) < 0)
1228 * Returns the fully-qualified domain name for the host
1231 * @returns The FQDN.
1233 String Utility::GetFQDN()
1235 String hostname = GetHostName();
1238 memset(&hints, 0, sizeof(hints));
1239 hints.ai_family = AF_UNSPEC;
1240 hints.ai_socktype = SOCK_DGRAM;
1241 hints.ai_flags = AI_CANONNAME;
1244 int rc = getaddrinfo(hostname.CStr(), nullptr, &hints, &result);
1250 if (strcmp(result->ai_canonname, "localhost") != 0)
1251 hostname = result->ai_canonname;
1253 freeaddrinfo(result);
1259 int Utility::Random()
1264 unsigned int *seed = m_RandSeed.get();
1267 seed = new unsigned int(Utility::GetTime());
1268 m_RandSeed.reset(seed);
1271 return rand_r(seed);
1275 tm Utility::LocalTime(time_t ts)
1278 tm *result = localtime(&ts);
1281 BOOST_THROW_EXCEPTION(posix_error()
1282 << boost::errinfo_api_function("localtime")
1283 << boost::errinfo_errno(errno));
1287 #else /* _MSC_VER */
1290 if (!localtime_r(&ts, &result)) {
1291 BOOST_THROW_EXCEPTION(posix_error()
1292 << boost::errinfo_api_function("localtime_r")
1293 << boost::errinfo_errno(errno));
1297 #endif /* _MSC_VER */
1300 bool Utility::PathExists(const String& path)
1302 namespace fs = boost::filesystem;
1304 boost::system::error_code ec;
1306 return fs::exists(fs::path(path.Begin(), path.End()), ec) && !ec;
1309 Value Utility::LoadJsonFile(const String& path)
1312 fp.open(path.CStr());
1314 String json((std::istreambuf_iterator<char>(fp)), std::istreambuf_iterator<char>());
1319 BOOST_THROW_EXCEPTION(std::runtime_error("Could not read JSON file '" + path + "'."));
1321 return JsonDecode(json);
1324 void Utility::SaveJsonFile(const String& path, int mode, const Value& value)
1326 namespace fs = boost::filesystem;
1329 String tempFilename = Utility::CreateTempFile(path + ".XXXXXX", mode, fp);
1331 fp.exceptions(std::ofstream::failbit | std::ofstream::badbit);
1332 fp << JsonEncode(value);
1335 RenameFile(tempFilename, path);
1338 static void HexEncode(char ch, std::ostream& os)
1340 const char *hex_chars = "0123456789ABCDEF";
1342 os << hex_chars[ch >> 4 & 0x0f];
1343 os << hex_chars[ch & 0x0f];
1346 static int HexDecode(char hc)
1348 if (hc >= '0' && hc <= '9')
1350 else if (hc >= 'a' && hc <= 'f')
1351 return hc - 'a' + 10;
1352 else if (hc >= 'A' && hc <= 'F')
1353 return hc - 'A' + 10;
1355 BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid hex character."));
1358 String Utility::EscapeString(const String& s, const String& chars, const bool illegal)
1360 std::ostringstream result;
1363 if (chars.FindFirstOf(ch) != String::NPos || ch == '%') {
1365 HexEncode(ch, result);
1371 if (chars.FindFirstOf(ch) == String::NPos || ch == '%') {
1373 HexEncode(ch, result);
1379 return result.str();
1382 String Utility::UnescapeString(const String& s)
1384 std::ostringstream result;
1386 for (String::SizeType i = 0; i < s.GetLength(); i++) {
1388 if (i + 2 > s.GetLength() - 1)
1389 BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid escape sequence."));
1391 char ch = HexDecode(s[i + 1]) * 16 + HexDecode(s[i + 2]);
1399 return result.str();
1403 static String UnameHelper(char type)
1405 struct utsname name;
1410 return (char*)name.machine;
1412 return (char*)name.nodename;
1414 return (char*)name.release;
1416 return (char*)name.sysname;
1418 return (char*)name.version;
1420 VERIFY(!"Invalid uname query.");
1424 static bool ReleaseHelper(String *platformName, String *platformVersion)
1428 *platformName = "Windows";
1430 if (platformVersion) {
1431 *platformVersion = "Vista";
1432 if (IsWindowsVistaSP1OrGreater())
1433 *platformVersion = "Vista SP1";
1434 if (IsWindowsVistaSP2OrGreater())
1435 *platformVersion = "Vista SP2";
1436 if (IsWindows7OrGreater())
1437 *platformVersion = "7";
1438 if (IsWindows7SP1OrGreater())
1439 *platformVersion = "7 SP1";
1440 if (IsWindows8OrGreater())
1441 *platformVersion = "8";
1442 if (IsWindows8Point1OrGreater())
1443 *platformVersion = "8.1 or greater";
1444 if (IsWindowsServer())
1445 *platformVersion += " (Server)";
1451 *platformName = "Unknown";
1453 if (platformVersion)
1454 *platformVersion = "Unknown";
1456 /* You have systemd or Ubuntu etc. */
1457 std::ifstream release("/etc/os-release");
1458 if (release.is_open()) {
1459 std::string release_line;
1460 while (getline(release, release_line)) {
1461 std::string::size_type pos = release_line.find("=");
1463 if (pos == std::string::npos)
1466 std::string key = release_line.substr(0, pos);
1467 std::string value = release_line.substr(pos + 1);
1469 std::string::size_type firstQuote = value.find("\"");
1471 if (firstQuote != std::string::npos)
1472 value.erase(0, firstQuote + 1);
1474 std::string::size_type lastQuote = value.rfind("\"");
1476 if (lastQuote != std::string::npos)
1477 value.erase(lastQuote);
1479 if (platformName && key == "NAME")
1480 *platformName = value;
1482 if (platformVersion && key == "VERSION")
1483 *platformVersion = value;
1489 /* You are using a distribution which supports LSB. */
1490 FILE *fp = popen("type lsb_release >/dev/null 2>&1 && lsb_release -s -i 2>&1", "r");
1493 std::ostringstream msgbuf;
1495 while (fgets(line, sizeof(line), fp))
1497 int status = pclose(fp);
1498 if (WEXITSTATUS(status) == 0) {
1500 *platformName = msgbuf.str();
1504 fp = popen("type lsb_release >/dev/null 2>&1 && lsb_release -s -r 2>&1", "r");
1507 std::ostringstream msgbuf;
1509 while (fgets(line, sizeof(line), fp))
1511 int status = pclose(fp);
1512 if (WEXITSTATUS(status) == 0) {
1513 if (platformVersion)
1514 *platformVersion = msgbuf.str();
1519 fp = popen("type sw_vers >/dev/null 2>&1 && sw_vers -productName 2>&1", "r");
1522 std::ostringstream msgbuf;
1524 while (fgets(line, sizeof(line), fp))
1526 int status = pclose(fp);
1527 if (WEXITSTATUS(status) == 0) {
1528 String info = msgbuf.str();
1532 *platformName = info;
1536 fp = popen("type sw_vers >/dev/null 2>&1 && sw_vers -productVersion 2>&1", "r");
1539 std::ostringstream msgbuf;
1541 while (fgets(line, sizeof(line), fp))
1543 int status = pclose(fp);
1544 if (WEXITSTATUS(status) == 0) {
1545 String info = msgbuf.str();
1548 if (platformVersion)
1549 *platformVersion = info;
1555 /* Centos/RHEL < 7 */
1557 release.open("/etc/redhat-release");
1558 if (release.is_open()) {
1559 std::string release_line;
1560 getline(release, release_line);
1562 String info = release_line;
1564 /* example: Red Hat Enterprise Linux Server release 6.7 (Santiago) */
1566 *platformName = info.SubStr(0, info.Find("release") - 1);
1568 if (platformVersion)
1569 *platformVersion = info.SubStr(info.Find("release") + 8);
1574 /* sles 11 sp3, opensuse w/e */
1576 release.open("/etc/SuSE-release");
1577 if (release.is_open()) {
1578 std::string release_line;
1579 getline(release, release_line);
1581 String info = release_line;
1584 *platformName = info.SubStr(0, info.FindFirstOf(" "));
1586 if (platformVersion)
1587 *platformVersion = info.SubStr(info.FindFirstOf(" ") + 1);
1597 String Utility::GetPlatformKernel()
1602 return UnameHelper('s');
1606 String Utility::GetPlatformKernelVersion()
1610 info.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
1611 GetVersionEx(&info);
1613 std::ostringstream msgbuf;
1614 msgbuf << info.dwMajorVersion << "." << info.dwMinorVersion;
1616 return msgbuf.str();
1618 return UnameHelper('r');
1622 String Utility::GetPlatformName()
1624 String platformName;
1625 if (!ReleaseHelper(&platformName, nullptr))
1627 return platformName;
1630 String Utility::GetPlatformVersion()
1632 String platformVersion;
1633 if (!ReleaseHelper(nullptr, &platformVersion))
1635 return platformVersion;
1638 String Utility::GetPlatformArchitecture()
1642 GetNativeSystemInfo(&info);
1643 switch (info.wProcessorArchitecture) {
1644 case PROCESSOR_ARCHITECTURE_AMD64:
1646 case PROCESSOR_ARCHITECTURE_ARM:
1648 case PROCESSOR_ARCHITECTURE_INTEL:
1654 return UnameHelper('m');
1658 const char l_Utf8Replacement[] = "\xEF\xBF\xBD";
1660 String Utility::ValidateUTF8(const String& input)
1662 std::vector<char> output;
1663 output.reserve(input.GetLength() * 3u);
1666 utf8::replace_invalid(input.Begin(), input.End(), std::back_inserter(output));
1667 } catch (const utf8::not_enough_room&) {
1668 output.insert(output.end(), (const char*)l_Utf8Replacement, (const char*)l_Utf8Replacement + 3);
1671 return String(output.begin(), output.end());
1674 String Utility::CreateTempFile(const String& path, int mode, std::fstream& fp)
1676 std::vector<char> targetPath(path.Begin(), path.End());
1677 targetPath.push_back('\0');
1681 fd = mkstemp(&targetPath[0]);
1683 fd = MksTemp(&targetPath[0]);
1687 BOOST_THROW_EXCEPTION(posix_error()
1688 << boost::errinfo_api_function("mkstemp")
1689 << boost::errinfo_errno(errno)
1690 << boost::errinfo_file_name(path));
1694 fp.open(&targetPath[0], std::ios_base::trunc | std::ios_base::out);
1695 } catch (const std::fstream::failure&) {
1702 String resultPath = String(targetPath.begin(), targetPath.end() - 1);
1704 if (chmod(resultPath.CStr(), mode) < 0) {
1705 BOOST_THROW_EXCEPTION(posix_error()
1706 << boost::errinfo_api_function("chmod")
1707 << boost::errinfo_errno(errno)
1708 << boost::errinfo_file_name(resultPath));
1715 /* mkstemp extracted from libc/sysdeps/posix/tempname.c. Copyright
1716 * (C) 1991-1999, 2000, 2001, 2006 Free Software Foundation, Inc.
1718 * The GNU C Library is free software; you can redistribute it and/or
1719 * modify it under the terms of the GNU Lesser General Public
1720 * License as published by the Free Software Foundation; either
1721 * version 2.1 of the License, or (at your option) any later version.
1724 #define _O_EXCL 0x0400
1725 #define _O_CREAT 0x0100
1726 #define _O_RDWR 0x0002
1727 #define O_EXCL _O_EXCL
1728 #define O_CREAT _O_CREAT
1729 #define O_RDWR _O_RDWR
1731 static const char letters[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
1733 /* Generate a temporary file name based on TMPL. TMPL must match the
1734 * rules for mk[s]temp (i.e. end in "XXXXXX"). The name constructed
1735 * does not exist at the time of the call to mkstemp. TMPL is
1736 * overwritten with the result.
1738 int Utility::MksTemp(char *tmpl)
1742 static unsigned long long value;
1743 unsigned long long random_time_bits;
1746 int save_errno = errno;
1748 /* A lower bound on the number of temporary files to attempt to
1749 * generate. The maximum total number of temporary file names that
1750 * can exist for a given template is 62**6. It should never be
1751 * necessary to try all these combinations. Instead if a reasonable
1752 * number of names is tried (we define reasonable as 62**3) fail to
1753 * give the system administrator the chance to remove the problems.
1755 #define ATTEMPTS_MIN (62 * 62 * 62)
1757 /* The number of times to attempt to generate a temporary file
1758 * To conform to POSIX, this must be no smaller than TMP_MAX.
1760 #if ATTEMPTS_MIN < TMP_MAX
1761 unsigned int attempts = TMP_MAX;
1763 unsigned int attempts = ATTEMPTS_MIN;
1766 len = strlen (tmpl);
1767 if (len < 6 || strcmp (&tmpl[len - 6], "XXXXXX")) {
1772 /* This is where the Xs start. */
1773 XXXXXX = &tmpl[len - 6];
1775 /* Get some more or less random data. */
1781 GetSystemTime(&stNow);
1782 stNow.wMilliseconds = 500;
1783 if (!SystemTimeToFileTime(&stNow, &ftNow)) {
1788 random_time_bits = (((unsigned long long)ftNow.dwHighDateTime << 32) | (unsigned long long)ftNow.dwLowDateTime);
1791 value += random_time_bits ^ (unsigned long long)GetCurrentThreadId();
1793 for (count = 0; count < attempts; value += 7777, ++count) {
1794 unsigned long long v = value;
1796 /* Fill in the random bits. */
1797 XXXXXX[0] = letters[v % 62];
1799 XXXXXX[1] = letters[v % 62];
1801 XXXXXX[2] = letters[v % 62];
1803 XXXXXX[3] = letters[v % 62];
1805 XXXXXX[4] = letters[v % 62];
1807 XXXXXX[5] = letters[v % 62];
1809 fd = open(tmpl, O_RDWR | O_CREAT | O_EXCL, _S_IREAD | _S_IWRITE);
1813 } else if (errno != EEXIST)
1817 /* We got out of the loop because we ran out of combinations to try. */
1822 String Utility::GetIcingaInstallPath()
1826 for (int i = 0; MsiEnumProducts(i, szProduct) == ERROR_SUCCESS; i++) {
1828 DWORD cbName = sizeof(szName);
1829 if (MsiGetProductInfo(szProduct, INSTALLPROPERTY_INSTALLEDPRODUCTNAME, szName, &cbName) != ERROR_SUCCESS)
1832 if (strcmp(szName, "Icinga 2") != 0)
1835 char szLocation[1024];
1836 DWORD cbLocation = sizeof(szLocation);
1837 if (MsiGetProductInfo(szProduct, INSTALLPROPERTY_INSTALLLOCATION, szLocation, &cbLocation) == ERROR_SUCCESS)
1844 String Utility::GetIcingaDataPath()
1846 char path[MAX_PATH];
1847 if (!SUCCEEDED(SHGetFolderPath(nullptr, CSIDL_COMMON_APPDATA, nullptr, 0, path)))
1849 return String(path) + "\\icinga2";
1855 * Retrieve the environment variable value by given key.
1857 * @param env Environment variable name.
1860 String Utility::GetFromEnvironment(const String& env)
1862 const char *envValue = getenv(env.CStr());
1864 if (envValue == NULL)
1867 return String(envValue);
1871 * Compare the password entered by a client with the actual password.
1872 * The comparision is safe against timing attacks.
1874 bool Utility::ComparePasswords(const String& enteredPassword, const String& actualPassword)
1876 volatile const char * volatile enteredPasswordCStr = enteredPassword.CStr();
1877 volatile size_t enteredPasswordLen = enteredPassword.GetLength();
1879 volatile const char * volatile actualPasswordCStr = actualPassword.CStr();
1880 volatile size_t actualPasswordLen = actualPassword.GetLength();
1882 volatile uint_fast8_t result = enteredPasswordLen == actualPasswordLen;
1885 auto cStr (actualPasswordCStr);
1886 auto len (actualPasswordLen);
1888 actualPasswordCStr = cStr;
1889 actualPasswordLen = len;
1891 auto cStr (enteredPasswordCStr);
1892 auto len (enteredPasswordLen);
1894 actualPasswordCStr = cStr;
1895 actualPasswordLen = len;
1898 for (volatile size_t i = 0; i < enteredPasswordLen; ++i) {
1899 result &= uint_fast8_t(enteredPasswordCStr[i] == actualPasswordCStr[i]);