]> granicus.if.org Git - icinga2/blob - lib/base/utility.cpp
Add Utility::ParseVersion() and unit tests
[icinga2] / lib / base / utility.cpp
1 /* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
2
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"
12 #include <cstdint>
13 #include <mmatch.h>
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>
24 #include <ios>
25 #include <fstream>
26 #include <iostream>
27 #include <iterator>
28 #include <stdlib.h>
29 #include <future>
30 #include <utf8.h>
31 #include <vector>
32
33 #ifdef __FreeBSD__
34 #       include <pthread_np.h>
35 #endif /* __FreeBSD__ */
36
37 #ifdef HAVE_CXXABI_H
38 #       include <cxxabi.h>
39 #endif /* HAVE_CXXABI_H */
40
41 #ifndef _WIN32
42 #       include <sys/types.h>
43 #       include <sys/utsname.h>
44 #       include <pwd.h>
45 #       include <grp.h>
46 #       include <errno.h>
47 #endif /* _WIN32 */
48
49 #ifdef _WIN32
50 #       include <VersionHelpers.h>
51 #       include <windows.h>
52 #       include <io.h>
53 #       include <msi.h>
54 #       include <shlobj.h>
55 #endif /*_WIN32*/
56
57 using namespace icinga;
58
59 boost::thread_specific_ptr<String> Utility::m_ThreadName;
60 boost::thread_specific_ptr<unsigned int> Utility::m_RandSeed;
61
62 #ifdef I2_DEBUG
63 double Utility::m_DebugTime = -1;
64 #endif /* I2_DEBUG */
65
66 /**
67  * Demangles a symbol name.
68  *
69  * @param sym The symbol name.
70  * @returns A human-readable version of the symbol name.
71  */
72 String Utility::DemangleSymbolName(const String& sym)
73 {
74         String result = sym;
75
76 #ifdef HAVE_CXXABI_H
77         int status;
78         char *realname = abi::__cxa_demangle(sym.CStr(), nullptr, nullptr, &status);
79
80         if (realname) {
81                 result = String(realname);
82                 free(realname);
83         }
84 #elif defined(_MSC_VER) /* HAVE_CXXABI_H */
85         CHAR output[256];
86
87         if (UnDecorateSymbolName(sym.CStr(), output, sizeof(output), UNDNAME_COMPLETE) > 0)
88                 result = output;
89 #else /* _MSC_VER */
90         /* We're pretty much out of options here. */
91 #endif /* _MSC_VER */
92
93         return result;
94 }
95
96 /**
97  * Returns a human-readable type name of a type_info object.
98  *
99  * @param ti A type_info object.
100  * @returns The type name of the object.
101  */
102 String Utility::GetTypeName(const std::type_info& ti)
103 {
104         return DemangleSymbolName(ti.name());
105 }
106
107 String Utility::GetSymbolName(const void *addr)
108 {
109 #ifdef HAVE_DLADDR
110         Dl_info dli;
111
112         if (dladdr(const_cast<void *>(addr), &dli) > 0)
113                 return dli.dli_sname;
114 #endif /* HAVE_DLADDR */
115
116 #ifdef _WIN32
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;
121
122         DWORD64 dwAddress = (DWORD64)addr;
123         DWORD64 dwDisplacement;
124
125         IMAGEHLP_LINE64 line;
126         line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
127
128         if (SymFromAddr(GetCurrentProcess(), dwAddress, &dwDisplacement, pSymbol)) {
129                 char output[256];
130                 if (UnDecorateSymbolName(pSymbol->Name, output, sizeof(output), UNDNAME_COMPLETE))
131                         return String(output) + "+" + Convert::ToString(dwDisplacement);
132                 else
133                         return String(pSymbol->Name) + "+" + Convert::ToString(dwDisplacement);
134         }
135 #endif /* _WIN32 */
136
137         return "(unknown function)";
138 }
139
140 /**
141  * Performs wildcard pattern matching.
142  *
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.
146  */
147 bool Utility::Match(const String& pattern, const String& text)
148 {
149         return (match(pattern.CStr(), text.CStr()) == 0);
150 }
151
152 static bool ParseIp(const String& ip, char addr[16], int *proto)
153 {
154         if (inet_pton(AF_INET, ip.CStr(), addr + 12) == 1) {
155                 /* IPv4-mapped IPv6 address (::ffff:<ipv4-bits>) */
156                 memset(addr, 0, 10);
157                 memset(addr + 10, 0xff, 2);
158                 *proto = AF_INET;
159
160                 return true;
161         }
162
163         if (inet_pton(AF_INET6, ip.CStr(), addr) == 1) {
164                 *proto = AF_INET6;
165
166                 return true;
167         }
168
169         return false;
170 }
171
172 static void ParseIpMask(const String& ip, char mask[16], int *bits)
173 {
174         String::SizeType slashp = ip.FindFirstOf("/");
175         String uip;
176
177         if (slashp == String::NPos) {
178                 uip = ip;
179                 *bits = 0;
180         } else {
181                 uip = ip.SubStr(0, slashp);
182                 *bits = Convert::ToLong(ip.SubStr(slashp + 1));
183         }
184
185         int proto;
186
187         if (!ParseIp(uip, mask, &proto))
188                 BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid IP address specified."));
189
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."));
193
194                 *bits += 96;
195         }
196
197         if (slashp == String::NPos)
198                 *bits = 128;
199
200         if (*bits > 128 || *bits < 0)
201                 BOOST_THROW_EXCEPTION(std::invalid_argument("Mask must be between 0 and 128 for IPv6 CIDR masks."));
202
203         for (int i = 0; i < 16; i++) {
204                 int lbits = std::max(0, *bits - i * 8);
205
206                 if (lbits >= 8)
207                         continue;
208
209                 if (mask[i] & (0xff >> lbits))
210                         BOOST_THROW_EXCEPTION(std::invalid_argument("Masked-off bits must all be zero."));
211         }
212 }
213
214 static bool IpMaskCheck(char addr[16], char mask[16], int bits)
215 {
216         for (int i = 0; i < 16; i++) {
217                 if (bits < 8)
218                         return !((addr[i] ^ mask[i]) >> (8 - bits));
219
220                 if (mask[i] != addr[i])
221                         return false;
222
223                 bits -= 8;
224
225                 if (bits == 0)
226                         return true;
227         }
228
229         return true;
230 }
231
232 bool Utility::CidrMatch(const String& pattern, const String& ip)
233 {
234         char mask[16];
235         int bits;
236
237         ParseIpMask(pattern, mask, &bits);
238
239         char addr[16];
240         int proto;
241
242         if (!ParseIp(ip, addr, &proto))
243                 return false;
244
245         return IpMaskCheck(addr, mask, bits);
246 }
247
248 /**
249  * Returns the directory component of a path. See dirname(3) for details.
250  *
251  * @param path The full path.
252  * @returns The directory.
253  */
254 String Utility::DirName(const String& path)
255 {
256         return boost::filesystem::path(path.Begin(), path.End()).parent_path().string();
257 }
258
259 /**
260  * Returns the file component of a path. See basename(3) for details.
261  *
262  * @param path The full path.
263  * @returns The filename.
264  */
265 String Utility::BaseName(const String& path)
266 {
267         return boost::filesystem::path(path.Begin(), path.End()).filename().string();
268 }
269
270 /**
271  * Null deleter. Used as a parameter for the shared_ptr constructor.
272  *
273  * @param - The object that should be deleted.
274  */
275 void Utility::NullDeleter(void *)
276 {
277         /* Nothing to do here. */
278 }
279
280 #ifdef I2_DEBUG
281 /**
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().
284  *
285  * This should be only used for testing purposes, e.g. unit tests and debugging of certain functionalities.
286  */
287 void Utility::SetTime(double time)
288 {
289         m_DebugTime = time;
290 }
291
292 /**
293  * (DEBUG / TESTING ONLY) Increases the set debug system time by X seconds.
294  *
295  * This should be only used for testing purposes, e.g. unit tests and debugging of certain functionalities.
296  */
297 void Utility::IncrementTime(double diff)
298 {
299         m_DebugTime += diff;
300 }
301 #endif /* I2_DEBUG */
302
303 /**
304  * Returns the current UNIX timestamp including fractions of seconds.
305  *
306  * @returns The current time.
307  */
308 double Utility::GetTime()
309 {
310 #ifdef I2_DEBUG
311         if (m_DebugTime >= 0) {
312                 // (DEBUG / TESTING ONLY) this will return a *STATIC* system time, if the value has been set!
313                 return m_DebugTime;
314         }
315 #endif /* I2_DEBUG */
316 #ifdef _WIN32
317         FILETIME cft;
318         GetSystemTimeAsFileTime(&cft);
319
320         ULARGE_INTEGER ucft;
321         ucft.HighPart = cft.dwHighDateTime;
322         ucft.LowPart = cft.dwLowDateTime;
323
324         SYSTEMTIME est = { 1970, 1, 4, 1, 0, 0, 0, 0};
325         FILETIME eft;
326         SystemTimeToFileTime(&est, &eft);
327
328         ULARGE_INTEGER ueft;
329         ueft.HighPart = eft.dwHighDateTime;
330         ueft.LowPart = eft.dwLowDateTime;
331
332         return ((ucft.QuadPart - ueft.QuadPart) / 10000) / 1000.0;
333 #else /* _WIN32 */
334         struct timeval tv;
335
336         int rc = gettimeofday(&tv, nullptr);
337         VERIFY(rc >= 0);
338
339         return tv.tv_sec + tv.tv_usec / 1000000.0;
340 #endif /* _WIN32 */
341 }
342
343 /**
344  * Returns the ID of the current process.
345  *
346  * @returns The PID.
347  */
348 pid_t Utility::GetPid()
349 {
350 #ifndef _WIN32
351         return getpid();
352 #else /* _WIN32 */
353         return GetCurrentProcessId();
354 #endif /* _WIN32 */
355 }
356
357 /**
358  * Sleeps for the specified amount of time.
359  *
360  * @param timeout The timeout in seconds.
361  */
362 void Utility::Sleep(double timeout)
363 {
364 #ifndef _WIN32
365         unsigned long micros = timeout * 1000000u;
366         if (timeout >= 1.0)
367                 sleep((unsigned)timeout);
368
369         usleep(micros % 1000000u);
370 #else /* _WIN32 */
371         ::Sleep(timeout * 1000);
372 #endif /* _WIN32 */
373 }
374
375 /**
376  * Generates a new unique ID.
377  *
378  * @returns The new unique ID.
379  */
380 String Utility::NewUniqueID()
381 {
382         return boost::lexical_cast<std::string>(boost::uuids::random_generator()());
383 }
384
385 #ifdef _WIN32
386 static bool GlobHelper(const String& pathSpec, int type, std::vector<String>& files, std::vector<String>& dirs)
387 {
388         HANDLE handle;
389         WIN32_FIND_DATA wfd;
390
391         handle = FindFirstFile(pathSpec.CStr(), &wfd);
392
393         if (handle == INVALID_HANDLE_VALUE) {
394                 DWORD errorCode = GetLastError();
395
396                 if (errorCode == ERROR_FILE_NOT_FOUND)
397                         return false;
398
399                 BOOST_THROW_EXCEPTION(win32_error()
400                         << boost::errinfo_api_function("FindFirstFile")
401                         << errinfo_win32_error(errorCode)
402                         << boost::errinfo_file_name(pathSpec));
403         }
404
405         do {
406                 if (strcmp(wfd.cFileName, ".") == 0 || strcmp(wfd.cFileName, "..") == 0)
407                         continue;
408
409                 String path = Utility::DirName(pathSpec) + "/" + wfd.cFileName;
410
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));
416
417         if (!FindClose(handle)) {
418                 BOOST_THROW_EXCEPTION(win32_error()
419                         << boost::errinfo_api_function("FindClose")
420                         << errinfo_win32_error(GetLastError()));
421         }
422
423         return true;
424 }
425 #endif /* _WIN32 */
426
427 #ifndef _WIN32
428 static int GlobErrorHandler(const char *epath, int eerrno)
429 {
430         if (eerrno == ENOTDIR)
431                 return 0;
432
433         return eerrno;
434 }
435 #endif /* _WIN32 */
436
437 /**
438  * Calls the specified callback for each file matching the path specification.
439  *
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)
443  */
444 bool Utility::Glob(const String& pathSpec, const std::function<void (const String&)>& callback, int type)
445 {
446         std::vector<String> files, dirs;
447
448 #ifdef _WIN32
449         std::vector<String> tokens = pathSpec.Split("\\/");
450
451         String part1;
452
453         for (std::vector<String>::size_type i = 0; i < tokens.size() - 1; i++) {
454                 const String& token = tokens[i];
455
456                 if (!part1.IsEmpty())
457                         part1 += "/";
458
459                 part1 += token;
460
461                 if (token.FindFirstOf("?*") != String::NPos) {
462                         String part2;
463
464                         for (std::vector<String>::size_type k = i + 1; k < tokens.size(); k++) {
465                                 if (!part2.IsEmpty())
466                                         part2 += "/";
467
468                                 part2 += tokens[k];
469                         }
470
471                         std::vector<String> files2, dirs2;
472
473                         if (!GlobHelper(part1, GlobDirectory, files2, dirs2))
474                                 return false;
475
476                         for (const String& dir : dirs2) {
477                                 if (!Utility::Glob(dir + "/" + part2, callback, type))
478                                         return false;
479                         }
480
481                         return true;
482                 }
483         }
484
485         if (!GlobHelper(part1 + "/" + tokens[tokens.size() - 1], type, files, dirs))
486                 return false;
487 #else /* _WIN32 */
488         glob_t gr;
489
490         int rc = glob(pathSpec.CStr(), GLOB_NOSORT, GlobErrorHandler, &gr);
491
492         if (rc) {
493                 if (rc == GLOB_NOMATCH)
494                         return false;
495
496                 BOOST_THROW_EXCEPTION(posix_error()
497                         << boost::errinfo_api_function("glob")
498                         << boost::errinfo_errno(errno)
499                         << boost::errinfo_file_name(pathSpec));
500         }
501
502         if (gr.gl_pathc == 0) {
503                 globfree(&gr);
504                 return false;
505         }
506
507         size_t left;
508         char **gp;
509         for (gp = gr.gl_pathv, left = gr.gl_pathc; left > 0; gp++, left--) {
510                 struct stat statbuf;
511
512                 if (stat(*gp, &statbuf) < 0)
513                         continue;
514
515                 if (!S_ISDIR(statbuf.st_mode) && !S_ISREG(statbuf.st_mode))
516                         continue;
517
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);
522         }
523
524         globfree(&gr);
525 #endif /* _WIN32 */
526
527         std::sort(files.begin(), files.end());
528         for (const String& cpath : files) {
529                 callback(cpath);
530         }
531
532         std::sort(dirs.begin(), dirs.end());
533         for (const String& cpath : dirs) {
534                 callback(cpath);
535         }
536
537         return true;
538 }
539
540 /**
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
543  * pattern.
544  *
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)
549  */
550 bool Utility::GlobRecursive(const String& path, const String& pattern, const std::function<void (const String&)>& callback, int type)
551 {
552         std::vector<String> files, dirs, alldirs;
553
554 #ifdef _WIN32
555         HANDLE handle;
556         WIN32_FIND_DATA wfd;
557
558         String pathSpec = path + "/*";
559
560         handle = FindFirstFile(pathSpec.CStr(), &wfd);
561
562         if (handle == INVALID_HANDLE_VALUE) {
563                 DWORD errorCode = GetLastError();
564
565                 if (errorCode == ERROR_FILE_NOT_FOUND)
566                         return false;
567
568                 BOOST_THROW_EXCEPTION(win32_error()
569                         << boost::errinfo_api_function("FindFirstFile")
570                         << errinfo_win32_error(errorCode)
571                         << boost::errinfo_file_name(pathSpec));
572         }
573
574         do {
575                 if (strcmp(wfd.cFileName, ".") == 0 || strcmp(wfd.cFileName, "..") == 0)
576                         continue;
577
578                 String cpath = path + "/" + wfd.cFileName;
579
580                 if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
581                         alldirs.push_back(cpath);
582
583                 if (!Utility::Match(pattern, wfd.cFileName))
584                         continue;
585
586                 if (!(wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && (type & GlobFile))
587                         files.push_back(cpath);
588
589                 if ((wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && (type & GlobDirectory))
590                         dirs.push_back(cpath);
591         } while (FindNextFile(handle, &wfd));
592
593         if (!FindClose(handle)) {
594                 BOOST_THROW_EXCEPTION(win32_error()
595                         << boost::errinfo_api_function("FindClose")
596                         << errinfo_win32_error(GetLastError()));
597         }
598 #else /* _WIN32 */
599         DIR *dirp;
600
601         dirp = opendir(path.CStr());
602
603         if (!dirp)
604                 BOOST_THROW_EXCEPTION(posix_error()
605                         << boost::errinfo_api_function("opendir")
606                         << boost::errinfo_errno(errno)
607                         << boost::errinfo_file_name(path));
608
609         while (dirp) {
610                 dirent *pent;
611
612                 errno = 0;
613                 pent = readdir(dirp);
614                 if (!pent && errno != 0) {
615                         closedir(dirp);
616
617                         BOOST_THROW_EXCEPTION(posix_error()
618                                 << boost::errinfo_api_function("readdir")
619                                 << boost::errinfo_errno(errno)
620                                 << boost::errinfo_file_name(path));
621                 }
622
623                 if (!pent)
624                         break;
625
626                 if (strcmp(pent->d_name, ".") == 0 || strcmp(pent->d_name, "..") == 0)
627                         continue;
628
629                 String cpath = path + "/" + pent->d_name;
630
631                 struct stat statbuf;
632
633                 if (stat(cpath.CStr(), &statbuf) < 0)
634                         continue;
635
636                 if (S_ISDIR(statbuf.st_mode))
637                         alldirs.push_back(cpath);
638
639                 if (!Utility::Match(pattern, pent->d_name))
640                         continue;
641
642                 if (S_ISDIR(statbuf.st_mode) && (type & GlobDirectory))
643                         dirs.push_back(cpath);
644
645                 if (!S_ISDIR(statbuf.st_mode) && (type & GlobFile))
646                         files.push_back(cpath);
647         }
648
649         closedir(dirp);
650
651 #endif /* _WIN32 */
652
653         std::sort(files.begin(), files.end());
654         for (const String& cpath : files) {
655                 callback(cpath);
656         }
657
658         std::sort(dirs.begin(), dirs.end());
659         for (const String& cpath : dirs) {
660                 callback(cpath);
661         }
662
663         std::sort(alldirs.begin(), alldirs.end());
664         for (const String& cpath : alldirs) {
665                 GlobRecursive(cpath, pattern, callback, type);
666         }
667
668         return true;
669 }
670
671
672 void Utility::MkDir(const String& path, int mode)
673 {
674
675 #ifndef _WIN32
676         if (mkdir(path.CStr(), mode) < 0 && errno != EEXIST) {
677 #else /*_ WIN32 */
678         if (mkdir(path.CStr()) < 0 && errno != EEXIST) {
679 #endif /* _WIN32 */
680
681                 BOOST_THROW_EXCEPTION(posix_error()
682                         << boost::errinfo_api_function("mkdir")
683                         << boost::errinfo_errno(errno)
684                         << boost::errinfo_file_name(path));
685         }
686 }
687
688 void Utility::MkDirP(const String& path, int mode)
689 {
690         size_t pos = 0;
691
692         while (pos != String::NPos) {
693 #ifndef _WIN32
694                 pos = path.Find("/", pos + 1);
695 #else /*_ WIN32 */
696                 pos = path.FindFirstOf("/\\", pos + 1);
697 #endif /* _WIN32 */
698
699                 String spath = path.SubStr(0, pos + 1);
700                 struct stat statbuf;
701                 if (stat(spath.CStr(), &statbuf) < 0 && errno == ENOENT)
702                         MkDir(path.SubStr(0, pos), mode);
703         }
704 }
705
706 void Utility::Remove(const String& path)
707 {
708         namespace fs = boost::filesystem;
709
710         (void)fs::remove(fs::path(path.Begin(), path.End()));
711 }
712
713 void Utility::RemoveDirRecursive(const String& path)
714 {
715         namespace fs = boost::filesystem;
716
717         (void)fs::remove_all(fs::path(path.Begin(), path.End()));
718 }
719
720 /*
721  * Copies a source file to a target location.
722  * Caller must ensure that the target's base directory exists and is writable.
723  */
724 void Utility::CopyFile(const String& source, const String& target)
725 {
726         namespace fs = boost::filesystem;
727
728         fs::copy_file(fs::path(source.Begin(), source.End()), fs::path(target.Begin(), target.End()), fs::copy_option::overwrite_if_exists);
729 }
730
731 /*
732  * Renames a source file to a target location.
733  * Caller must ensure that the target's base directory exists and is writable.
734  */
735 void Utility::RenameFile(const String& source, const String& target)
736 {
737         namespace fs = boost::filesystem;
738
739         fs::rename(fs::path(source.Begin(), source.End()), fs::path(target.Begin(), target.End()));
740 }
741
742 /*
743  * Set file permissions
744  */
745 bool Utility::SetFileOwnership(const String& file, const String& user, const String& group)
746 {
747 #ifndef _WIN32
748         errno = 0;
749         struct passwd *pw = getpwnam(user.CStr());
750
751         if (!pw) {
752                 if (errno == 0) {
753                         Log(LogCritical, "cli")
754                                 << "Invalid user specified: " << user;
755                         return false;
756                 } else {
757                         Log(LogCritical, "cli")
758                                 << "getpwnam() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
759                         return false;
760                 }
761         }
762
763         errno = 0;
764         struct group *gr = getgrnam(group.CStr());
765
766         if (!gr) {
767                 if (errno == 0) {
768                         Log(LogCritical, "cli")
769                                 << "Invalid group specified: " << group;
770                         return false;
771                 } else {
772                         Log(LogCritical, "cli")
773                                 << "getgrnam() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
774                         return false;
775                 }
776         }
777
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) << "\"";
781                 return false;
782         }
783 #endif /* _WIN32 */
784
785         return true;
786 }
787
788 #ifndef _WIN32
789 void Utility::SetNonBlocking(int fd, bool nb)
790 {
791         int flags = fcntl(fd, F_GETFL, 0);
792
793         if (flags < 0) {
794                 BOOST_THROW_EXCEPTION(posix_error()
795                         << boost::errinfo_api_function("fcntl")
796                         << boost::errinfo_errno(errno));
797         }
798
799         if (nb)
800                 flags |= O_NONBLOCK;
801         else
802                 flags &= ~O_NONBLOCK;
803
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));
808         }
809 }
810
811 void Utility::SetCloExec(int fd, bool cloexec)
812 {
813         int flags = fcntl(fd, F_GETFD, 0);
814
815         if (flags < 0) {
816                 BOOST_THROW_EXCEPTION(posix_error()
817                         << boost::errinfo_api_function("fcntl")
818                         << boost::errinfo_errno(errno));
819         }
820
821         if (cloexec)
822                 flags |= FD_CLOEXEC;
823         else
824                 flags &= ~FD_CLOEXEC;
825
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));
830         }
831 }
832 #endif /* _WIN32 */
833
834 void Utility::SetNonBlockingSocket(SOCKET s, bool nb)
835 {
836 #ifndef _WIN32
837         SetNonBlocking(s, nb);
838 #else /* _WIN32 */
839         unsigned long lflag = nb;
840         ioctlsocket(s, FIONBIO, &lflag);
841 #endif /* _WIN32 */
842 }
843
844 void Utility::QueueAsyncCallback(const std::function<void ()>& callback, SchedulerPolicy policy)
845 {
846         Application::GetTP().Post(callback, policy);
847 }
848
849 String Utility::NaturalJoin(const std::vector<String>& tokens)
850 {
851         String result;
852
853         for (std::vector<String>::size_type i = 0; i < tokens.size(); i++) {
854                 result += tokens[i];
855
856                 if (tokens.size() > i + 1) {
857                         if (i < tokens.size() - 2)
858                                 result += ", ";
859                         else if (i == tokens.size() - 2)
860                                 result += " and ";
861                 }
862         }
863
864         return result;
865 }
866
867 String Utility::Join(const Array::Ptr& tokens, char separator, bool escapeSeparator)
868 {
869         String result;
870         bool first = true;
871
872         ObjectLock olock(tokens);
873         for (const Value& vtoken : tokens) {
874                 String token = Convert::ToString(vtoken);
875
876                 if (escapeSeparator) {
877                         boost::algorithm::replace_all(token, "\\", "\\\\");
878
879                         char sep_before[2], sep_after[3];
880                         sep_before[0] = separator;
881                         sep_before[1] = '\0';
882                         sep_after[0] = '\\';
883                         sep_after[1] = separator;
884                         sep_after[2] = '\0';
885                         boost::algorithm::replace_all(token, sep_before, sep_after);
886                 }
887
888                 if (first)
889                         first = false;
890                 else
891                         result += String(1, separator);
892
893                 result += token;
894         }
895
896         return result;
897 }
898
899 String Utility::FormatDuration(double duration)
900 {
901         std::vector<String> tokens;
902         String result;
903
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;
908         }
909
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;
914         }
915
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;
920         }
921
922         if (duration >= 1) {
923                 int seconds = duration;
924                 tokens.emplace_back(Convert::ToString(seconds) + (seconds != 1 ? " seconds" : " second"));
925         }
926
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"));
931                 else
932                         tokens.emplace_back("less than 1 millisecond");
933         }
934
935         return NaturalJoin(tokens);
936 }
937
938 String Utility::FormatDateTime(const char *format, double ts)
939 {
940         char timestamp[128];
941         auto tempts = (time_t)ts; /* We don't handle sub-second timestamps here just yet. */
942         tm tmthen;
943
944 #ifdef _MSC_VER
945         tm *temp = localtime(&tempts);
946
947         if (!temp) {
948                 BOOST_THROW_EXCEPTION(posix_error()
949                         << boost::errinfo_api_function("localtime")
950                         << boost::errinfo_errno(errno));
951         }
952
953         tmthen = *temp;
954 #else /* _MSC_VER */
955         if (!localtime_r(&tempts, &tmthen)) {
956                 BOOST_THROW_EXCEPTION(posix_error()
957                         << boost::errinfo_api_function("localtime_r")
958                         << boost::errinfo_errno(errno));
959         }
960 #endif /* _MSC_VER */
961
962         strftime(timestamp, sizeof(timestamp), format, &tmthen);
963
964         return timestamp;
965 }
966
967 String Utility::FormatErrorNumber(int code) {
968         std::ostringstream msgbuf;
969
970 #ifdef _WIN32
971         char *message;
972         String result = "Unknown error.";
973
974         DWORD rc = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
975                 FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, code, 0, (char *)&message,
976                 0, nullptr);
977
978         if (rc != 0) {
979                 result = String(message);
980                 LocalFree(message);
981
982                 /* remove trailing new-line characters */
983                 boost::algorithm::trim_right(result);
984         }
985
986         msgbuf << code << ", \"" << result << "\"";
987 #else
988         msgbuf << strerror(code);
989 #endif
990         return msgbuf.str();
991 }
992
993 String Utility::EscapeShellCmd(const String& s)
994 {
995         String result;
996         size_t prev_quote = String::NPos;
997         int index = -1;
998
999         for (char ch : s) {
1000                 bool escape = false;
1001
1002                 index++;
1003
1004 #ifdef _WIN32
1005                 if (ch == '%' || ch == '"' || ch == '\'')
1006                         escape = true;
1007 #else /* _WIN32 */
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;
1014                         else
1015                                 escape = true;
1016                 }
1017 #endif /* _WIN32 */
1018
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' ||
1023                         ch == '\xFF')
1024                         escape = true;
1025
1026                 if (escape)
1027 #ifdef _WIN32
1028                         result += '^';
1029 #else /* _WIN32 */
1030                         result += '\\';
1031 #endif /* _WIN32 */
1032
1033                 result += ch;
1034         }
1035
1036         return result;
1037 }
1038
1039 String Utility::EscapeShellArg(const String& s)
1040 {
1041         String result;
1042
1043 #ifdef _WIN32
1044         result = "\"";
1045 #else /* _WIN32 */
1046         result = "'";
1047 #endif /* _WIN32 */
1048
1049         for (char ch : s) {
1050 #ifdef _WIN32
1051                 if (ch == '"' || ch == '%') {
1052                         result += ' ';
1053                 }
1054 #else /* _WIN32 */
1055                 if (ch == '\'')
1056                         result += "'\\'";
1057 #endif
1058                 result += ch;
1059         }
1060
1061 #ifdef _WIN32
1062         result += '"';
1063 #else /* _WIN32 */
1064         result += '\'';
1065 #endif /* _WIN32 */
1066
1067         return result;
1068 }
1069
1070 #ifdef _WIN32
1071 String Utility::EscapeCreateProcessArg(const String& arg)
1072 {
1073         if (arg.FindFirstOf(" \t\n\v\"") == String::NPos)
1074                 return arg;
1075
1076         String result = "\"";
1077
1078         for (String::ConstIterator it = arg.Begin(); ; it++) {
1079                 int numBackslashes = 0;
1080
1081                 while (it != arg.End() && *it == '\\') {
1082                         it++;
1083                         numBackslashes++;
1084                 }
1085
1086                 if (it == arg.End()) {
1087                         result.Append(numBackslashes * 2, '\\');
1088                         break;
1089                 } else if (*it == '"') {
1090                         result.Append(numBackslashes * 2, '\\');
1091                         result.Append(1, *it);
1092                 } else {
1093                         result.Append(numBackslashes, '\\');
1094                         result.Append(1, *it);
1095                 }
1096         }
1097
1098         result += "\"";
1099
1100         return result;
1101 }
1102 #endif /* _WIN32 */
1103
1104 #ifdef _WIN32
1105 static void WindowsSetThreadName(const char *name)
1106 {
1107         THREADNAME_INFO info;
1108         info.dwType = 0x1000;
1109         info.szName = name;
1110         info.dwThreadID = -1;
1111         info.dwFlags = 0;
1112
1113         __try {
1114                 RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR *)&info);
1115         } __except(EXCEPTION_EXECUTE_HANDLER) {
1116                 /* Nothing to do here. */
1117         }
1118 }
1119 #endif /* _WIN32 */
1120
1121 void Utility::SetThreadName(const String& name, bool os)
1122 {
1123         m_ThreadName.reset(new String(name));
1124
1125         if (!os)
1126                 return;
1127
1128 #ifdef _WIN32
1129         WindowsSetThreadName(name.CStr());
1130 #endif /* _WIN32 */
1131
1132 #ifdef HAVE_PTHREAD_SET_NAME_NP
1133         pthread_set_name_np(pthread_self(), name.CStr());
1134 #endif /* HAVE_PTHREAD_SET_NAME_NP */
1135
1136 #ifdef HAVE_PTHREAD_SETNAME_NP
1137 #       ifdef __APPLE__
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 */
1144 }
1145
1146 String Utility::GetThreadName()
1147 {
1148         String *name = m_ThreadName.get();
1149
1150         if (!name) {
1151                 std::ostringstream idbuf;
1152                 idbuf << std::this_thread::get_id();
1153                 return idbuf.str();
1154         }
1155
1156         return *name;
1157 }
1158
1159 unsigned long Utility::SDBM(const String& str, size_t len)
1160 {
1161         unsigned long hash = 0;
1162         size_t current = 0;
1163
1164         for (char c : str) {
1165                 if (current >= len)
1166                         break;
1167
1168                 hash = c + (hash << 6) + (hash << 16) - hash;
1169
1170                 current++;
1171         }
1172
1173         return hash;
1174 }
1175
1176 String Utility::ParseVersion(const String& v)
1177 {
1178         /*
1179          * 2.11.0-0.rc1.1
1180          * v2.10.5
1181          * r2.10.3
1182          * v2.11.0-rc1-58-g7c1f716da
1183          */
1184         boost::regex pattern("^[vr]?(2\\.\\d+\\.\\d+).*$");
1185         boost::smatch result;
1186
1187         if (boost::regex_search(v.GetData(), result, pattern)) {
1188                 String res(result[1].first, result[1].second);
1189                 return res;
1190         }
1191
1192         // Couldn't not extract anything, return unparsed version
1193         return v;
1194 }
1195
1196 int Utility::CompareVersion(const String& v1, const String& v2)
1197 {
1198         std::vector<String> tokensv1 = v1.Split(".");
1199         std::vector<String> tokensv2 = v2.Split(".");
1200
1201         for (std::vector<String>::size_type i = 0; i < tokensv2.size() - tokensv1.size(); i++)
1202                 tokensv1.emplace_back("0");
1203
1204         for (std::vector<String>::size_type i = 0; i < tokensv1.size() - tokensv2.size(); i++)
1205                 tokensv2.emplace_back("0");
1206
1207         for (std::vector<String>::size_type i = 0; i < tokensv1.size(); i++) {
1208                 if (Convert::ToLong(tokensv2[i]) > Convert::ToLong(tokensv1[i]))
1209                         return 1;
1210                 else if (Convert::ToLong(tokensv2[i]) < Convert::ToLong(tokensv1[i]))
1211                         return -1;
1212         }
1213
1214         return 0;
1215 }
1216
1217 String Utility::GetHostName()
1218 {
1219         char name[255];
1220
1221         if (gethostname(name, sizeof(name)) < 0)
1222                 return "localhost";
1223
1224         return name;
1225 }
1226
1227 /**
1228  * Returns the fully-qualified domain name for the host
1229  * we're running on.
1230  *
1231  * @returns The FQDN.
1232  */
1233 String Utility::GetFQDN()
1234 {
1235         String hostname = GetHostName();
1236
1237         addrinfo hints;
1238         memset(&hints, 0, sizeof(hints));
1239         hints.ai_family = AF_UNSPEC;
1240         hints.ai_socktype = SOCK_DGRAM;
1241         hints.ai_flags = AI_CANONNAME;
1242
1243         addrinfo *result;
1244         int rc = getaddrinfo(hostname.CStr(), nullptr, &hints, &result);
1245
1246         if (rc != 0)
1247                 result = nullptr;
1248
1249         if (result) {
1250                 if (strcmp(result->ai_canonname, "localhost") != 0)
1251                         hostname = result->ai_canonname;
1252
1253                 freeaddrinfo(result);
1254         }
1255
1256         return hostname;
1257 }
1258
1259 int Utility::Random()
1260 {
1261 #ifdef _WIN32
1262         return rand();
1263 #else /* _WIN32 */
1264         unsigned int *seed = m_RandSeed.get();
1265
1266         if (!seed) {
1267                 seed = new unsigned int(Utility::GetTime());
1268                 m_RandSeed.reset(seed);
1269         }
1270
1271         return rand_r(seed);
1272 #endif /* _WIN32 */
1273 }
1274
1275 tm Utility::LocalTime(time_t ts)
1276 {
1277 #ifdef _MSC_VER
1278         tm *result = localtime(&ts);
1279
1280         if (!result) {
1281                 BOOST_THROW_EXCEPTION(posix_error()
1282                         << boost::errinfo_api_function("localtime")
1283                         << boost::errinfo_errno(errno));
1284         }
1285
1286         return *result;
1287 #else /* _MSC_VER */
1288         tm result;
1289
1290         if (!localtime_r(&ts, &result)) {
1291                 BOOST_THROW_EXCEPTION(posix_error()
1292                         << boost::errinfo_api_function("localtime_r")
1293                         << boost::errinfo_errno(errno));
1294         }
1295
1296         return result;
1297 #endif /* _MSC_VER */
1298 }
1299
1300 bool Utility::PathExists(const String& path)
1301 {
1302         namespace fs = boost::filesystem;
1303
1304         boost::system::error_code ec;
1305
1306         return fs::exists(fs::path(path.Begin(), path.End()), ec) && !ec;
1307 }
1308
1309 Value Utility::LoadJsonFile(const String& path)
1310 {
1311         std::ifstream fp;
1312         fp.open(path.CStr());
1313
1314         String json((std::istreambuf_iterator<char>(fp)), std::istreambuf_iterator<char>());
1315
1316         fp.close();
1317
1318         if (fp.fail())
1319                 BOOST_THROW_EXCEPTION(std::runtime_error("Could not read JSON file '" + path + "'."));
1320
1321         return JsonDecode(json);
1322 }
1323
1324 void Utility::SaveJsonFile(const String& path, int mode, const Value& value)
1325 {
1326         namespace fs = boost::filesystem;
1327
1328         std::fstream fp;
1329         String tempFilename = Utility::CreateTempFile(path + ".XXXXXX", mode, fp);
1330
1331         fp.exceptions(std::ofstream::failbit | std::ofstream::badbit);
1332         fp << JsonEncode(value);
1333         fp.close();
1334
1335         RenameFile(tempFilename, path);
1336 }
1337
1338 static void HexEncode(char ch, std::ostream& os)
1339 {
1340         const char *hex_chars = "0123456789ABCDEF";
1341
1342         os << hex_chars[ch >> 4 & 0x0f];
1343         os << hex_chars[ch & 0x0f];
1344 }
1345
1346 static int HexDecode(char hc)
1347 {
1348         if (hc >= '0' && hc <= '9')
1349                 return hc - '0';
1350         else if (hc >= 'a' && hc <= 'f')
1351                 return hc - 'a' + 10;
1352         else if (hc >= 'A' && hc <= 'F')
1353                 return hc - 'A' + 10;
1354         else
1355                 BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid hex character."));
1356 }
1357
1358 String Utility::EscapeString(const String& s, const String& chars, const bool illegal)
1359 {
1360         std::ostringstream result;
1361         if (illegal) {
1362                 for (char ch : s) {
1363                         if (chars.FindFirstOf(ch) != String::NPos || ch == '%') {
1364                                 result << '%';
1365                                 HexEncode(ch, result);
1366                         } else
1367                                 result << ch;
1368                 }
1369         } else {
1370                 for (char ch : s) {
1371                         if (chars.FindFirstOf(ch) == String::NPos || ch == '%') {
1372                                 result << '%';
1373                                 HexEncode(ch, result);
1374                         } else
1375                                 result << ch;
1376                 }
1377         }
1378
1379         return result.str();
1380 }
1381
1382 String Utility::UnescapeString(const String& s)
1383 {
1384         std::ostringstream result;
1385
1386         for (String::SizeType i = 0; i < s.GetLength(); i++) {
1387                 if (s[i] == '%') {
1388                         if (i + 2 > s.GetLength() - 1)
1389                                 BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid escape sequence."));
1390
1391                         char ch = HexDecode(s[i + 1]) * 16 + HexDecode(s[i + 2]);
1392                         result << ch;
1393
1394                         i += 2;
1395                 } else
1396                         result << s[i];
1397         }
1398
1399         return result.str();
1400 }
1401
1402 #ifndef _WIN32
1403 static String UnameHelper(char type)
1404 {
1405         struct utsname name;
1406         uname(&name);
1407
1408         switch (type) {
1409                 case 'm':
1410                         return (char*)name.machine;
1411                 case 'n':
1412                         return (char*)name.nodename;
1413                 case 'r':
1414                         return (char*)name.release;
1415                 case 's':
1416                         return (char*)name.sysname;
1417                 case 'v':
1418                         return (char*)name.version;
1419                 default:
1420                         VERIFY(!"Invalid uname query.");
1421         }
1422 }
1423 #endif /* _WIN32 */
1424 static bool ReleaseHelper(String *platformName, String *platformVersion)
1425 {
1426 #ifdef _WIN32
1427         if (platformName)
1428                 *platformName = "Windows";
1429
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)";
1446         }
1447
1448         return true;
1449 #else /* _WIN32 */
1450         if (platformName)
1451                 *platformName = "Unknown";
1452
1453         if (platformVersion)
1454                 *platformVersion = "Unknown";
1455
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("=");
1462
1463                         if (pos == std::string::npos)
1464                                 continue;
1465
1466                         std::string key = release_line.substr(0, pos);
1467                         std::string value = release_line.substr(pos + 1);
1468
1469                         std::string::size_type firstQuote = value.find("\"");
1470
1471                         if (firstQuote != std::string::npos)
1472                                 value.erase(0, firstQuote + 1);
1473
1474                         std::string::size_type lastQuote = value.rfind("\"");
1475
1476                         if (lastQuote != std::string::npos)
1477                                 value.erase(lastQuote);
1478
1479                         if (platformName && key == "NAME")
1480                                 *platformName = value;
1481
1482                         if (platformVersion && key == "VERSION")
1483                                 *platformVersion = value;
1484                 }
1485
1486                 return true;
1487         }
1488
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");
1491
1492         if (fp) {
1493                 std::ostringstream msgbuf;
1494                 char line[1024];
1495                 while (fgets(line, sizeof(line), fp))
1496                         msgbuf << line;
1497                 int status = pclose(fp);
1498                 if (WEXITSTATUS(status) == 0) {
1499                         if (platformName)
1500                                 *platformName = msgbuf.str();
1501                 }
1502         }
1503
1504         fp = popen("type lsb_release >/dev/null 2>&1 && lsb_release -s -r 2>&1", "r");
1505
1506         if (fp) {
1507                 std::ostringstream msgbuf;
1508                 char line[1024];
1509                 while (fgets(line, sizeof(line), fp))
1510                         msgbuf << line;
1511                 int status = pclose(fp);
1512                 if (WEXITSTATUS(status) == 0) {
1513                         if (platformVersion)
1514                                 *platformVersion = msgbuf.str();
1515                 }
1516         }
1517
1518         /* OS X */
1519         fp = popen("type sw_vers >/dev/null 2>&1 && sw_vers -productName 2>&1", "r");
1520
1521         if (fp) {
1522                 std::ostringstream msgbuf;
1523                 char line[1024];
1524                 while (fgets(line, sizeof(line), fp))
1525                         msgbuf << line;
1526                 int status = pclose(fp);
1527                 if (WEXITSTATUS(status) == 0) {
1528                         String info = msgbuf.str();
1529                         info = info.Trim();
1530
1531                         if (platformName)
1532                                 *platformName = info;
1533                 }
1534         }
1535
1536         fp = popen("type sw_vers >/dev/null 2>&1 && sw_vers -productVersion 2>&1", "r");
1537
1538         if (fp) {
1539                 std::ostringstream msgbuf;
1540                 char line[1024];
1541                 while (fgets(line, sizeof(line), fp))
1542                         msgbuf << line;
1543                 int status = pclose(fp);
1544                 if (WEXITSTATUS(status) == 0) {
1545                         String info = msgbuf.str();
1546                         info = info.Trim();
1547
1548                         if (platformVersion)
1549                                 *platformVersion = info;
1550
1551                         return true;
1552                 }
1553         }
1554
1555         /* Centos/RHEL < 7 */
1556         release.close();
1557         release.open("/etc/redhat-release");
1558         if (release.is_open()) {
1559                 std::string release_line;
1560                 getline(release, release_line);
1561
1562                 String info = release_line;
1563
1564                 /* example: Red Hat Enterprise Linux Server release 6.7 (Santiago) */
1565                 if (platformName)
1566                         *platformName = info.SubStr(0, info.Find("release") - 1);
1567
1568                 if (platformVersion)
1569                         *platformVersion = info.SubStr(info.Find("release") + 8);
1570
1571                 return true;
1572         }
1573
1574         /* sles 11 sp3, opensuse w/e */
1575         release.close();
1576         release.open("/etc/SuSE-release");
1577         if (release.is_open()) {
1578                 std::string release_line;
1579                 getline(release, release_line);
1580
1581                 String info = release_line;
1582
1583                 if (platformName)
1584                         *platformName = info.SubStr(0, info.FindFirstOf(" "));
1585
1586                 if (platformVersion)
1587                         *platformVersion = info.SubStr(info.FindFirstOf(" ") + 1);
1588
1589                 return true;
1590         }
1591
1592         /* Just give up */
1593         return false;
1594 #endif /* _WIN32 */
1595 }
1596
1597 String Utility::GetPlatformKernel()
1598 {
1599 #ifdef _WIN32
1600         return "Windows";
1601 #else /* _WIN32 */
1602         return UnameHelper('s');
1603 #endif /* _WIN32 */
1604 }
1605
1606 String Utility::GetPlatformKernelVersion()
1607 {
1608 #ifdef _WIN32
1609         OSVERSIONINFO info;
1610         info.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
1611         GetVersionEx(&info);
1612
1613         std::ostringstream msgbuf;
1614         msgbuf << info.dwMajorVersion << "." << info.dwMinorVersion;
1615
1616         return msgbuf.str();
1617 #else /* _WIN32 */
1618         return UnameHelper('r');
1619 #endif /* _WIN32 */
1620 }
1621
1622 String Utility::GetPlatformName()
1623 {
1624         String platformName;
1625         if (!ReleaseHelper(&platformName, nullptr))
1626                 return "Unknown";
1627         return platformName;
1628 }
1629
1630 String Utility::GetPlatformVersion()
1631 {
1632         String platformVersion;
1633         if (!ReleaseHelper(nullptr, &platformVersion))
1634                 return "Unknown";
1635         return platformVersion;
1636 }
1637
1638 String Utility::GetPlatformArchitecture()
1639 {
1640 #ifdef _WIN32
1641         SYSTEM_INFO info;
1642         GetNativeSystemInfo(&info);
1643         switch (info.wProcessorArchitecture) {
1644                 case PROCESSOR_ARCHITECTURE_AMD64:
1645                         return "x86_64";
1646                 case PROCESSOR_ARCHITECTURE_ARM:
1647                         return "arm";
1648                 case PROCESSOR_ARCHITECTURE_INTEL:
1649                         return "x86";
1650                 default:
1651                         return "unknown";
1652         }
1653 #else /* _WIN32 */
1654         return UnameHelper('m');
1655 #endif /* _WIN32 */
1656 }
1657
1658 const char l_Utf8Replacement[] = "\xEF\xBF\xBD";
1659
1660 String Utility::ValidateUTF8(const String& input)
1661 {
1662         std::vector<char> output;
1663         output.reserve(input.GetLength() * 3u);
1664
1665         try {
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);
1669         }
1670
1671         return String(output.begin(), output.end());
1672 }
1673
1674 String Utility::CreateTempFile(const String& path, int mode, std::fstream& fp)
1675 {
1676         std::vector<char> targetPath(path.Begin(), path.End());
1677         targetPath.push_back('\0');
1678
1679         int fd;
1680 #ifndef _WIN32
1681         fd = mkstemp(&targetPath[0]);
1682 #else /* _WIN32 */
1683         fd = MksTemp(&targetPath[0]);
1684 #endif /*_WIN32*/
1685
1686         if (fd < 0) {
1687                 BOOST_THROW_EXCEPTION(posix_error()
1688                         << boost::errinfo_api_function("mkstemp")
1689                         << boost::errinfo_errno(errno)
1690                         << boost::errinfo_file_name(path));
1691         }
1692
1693         try {
1694                 fp.open(&targetPath[0], std::ios_base::trunc | std::ios_base::out);
1695         } catch (const std::fstream::failure&) {
1696                 close(fd);
1697                 throw;
1698         }
1699
1700         close(fd);
1701
1702         String resultPath = String(targetPath.begin(), targetPath.end() - 1);
1703
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));
1709         }
1710
1711         return resultPath;
1712 }
1713
1714 #ifdef _WIN32
1715 /* mkstemp extracted from libc/sysdeps/posix/tempname.c.  Copyright
1716  * (C) 1991-1999, 2000, 2001, 2006 Free Software Foundation, Inc.
1717  *
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.
1722  */
1723
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
1730
1731 static const char letters[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
1732
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.
1737  */
1738 int Utility::MksTemp(char *tmpl)
1739 {
1740         int len;
1741         char *XXXXXX;
1742         static unsigned long long value;
1743         unsigned long long random_time_bits;
1744         unsigned int count;
1745         int fd = -1;
1746         int save_errno = errno;
1747
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.
1754          */
1755 #define ATTEMPTS_MIN (62 * 62 * 62)
1756
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.
1759          */
1760 #if ATTEMPTS_MIN < TMP_MAX
1761         unsigned int attempts = TMP_MAX;
1762 #else
1763         unsigned int attempts = ATTEMPTS_MIN;
1764 #endif
1765
1766         len = strlen (tmpl);
1767         if (len < 6 || strcmp (&tmpl[len - 6], "XXXXXX")) {
1768                 errno = EINVAL;
1769                 return -1;
1770         }
1771
1772         /* This is where the Xs start.  */
1773         XXXXXX = &tmpl[len - 6];
1774
1775         /* Get some more or less random data.  */
1776         {
1777                 SYSTEMTIME stNow;
1778                 FILETIME ftNow;
1779
1780                 // get system time
1781                 GetSystemTime(&stNow);
1782                 stNow.wMilliseconds = 500;
1783                 if (!SystemTimeToFileTime(&stNow, &ftNow)) {
1784                         errno = -1;
1785                         return -1;
1786                 }
1787
1788                 random_time_bits = (((unsigned long long)ftNow.dwHighDateTime << 32) | (unsigned long long)ftNow.dwLowDateTime);
1789         }
1790
1791         value += random_time_bits ^ (unsigned long long)GetCurrentThreadId();
1792
1793         for (count = 0; count < attempts; value += 7777, ++count) {
1794                 unsigned long long v = value;
1795
1796                 /* Fill in the random bits.  */
1797                 XXXXXX[0] = letters[v % 62];
1798                 v /= 62;
1799                 XXXXXX[1] = letters[v % 62];
1800                 v /= 62;
1801                 XXXXXX[2] = letters[v % 62];
1802                 v /= 62;
1803                 XXXXXX[3] = letters[v % 62];
1804                 v /= 62;
1805                 XXXXXX[4] = letters[v % 62];
1806                 v /= 62;
1807                 XXXXXX[5] = letters[v % 62];
1808
1809                 fd = open(tmpl, O_RDWR | O_CREAT | O_EXCL, _S_IREAD | _S_IWRITE);
1810                 if (fd >= 0) {
1811                         errno = save_errno;
1812                         return fd;
1813                 } else if (errno != EEXIST)
1814                         return -1;
1815         }
1816
1817         /* We got out of the loop because we ran out of combinations to try.  */
1818         errno = EEXIST;
1819         return -1;
1820 }
1821
1822 String Utility::GetIcingaInstallPath()
1823 {
1824         char szProduct[39];
1825
1826         for (int i = 0; MsiEnumProducts(i, szProduct) == ERROR_SUCCESS; i++) {
1827                 char szName[128];
1828                 DWORD cbName = sizeof(szName);
1829                 if (MsiGetProductInfo(szProduct, INSTALLPROPERTY_INSTALLEDPRODUCTNAME, szName, &cbName) != ERROR_SUCCESS)
1830                         continue;
1831
1832                 if (strcmp(szName, "Icinga 2") != 0)
1833                         continue;
1834
1835                 char szLocation[1024];
1836                 DWORD cbLocation = sizeof(szLocation);
1837                 if (MsiGetProductInfo(szProduct, INSTALLPROPERTY_INSTALLLOCATION, szLocation, &cbLocation) == ERROR_SUCCESS)
1838                         return szLocation;
1839         }
1840
1841         return "";
1842 }
1843
1844 String Utility::GetIcingaDataPath()
1845 {
1846         char path[MAX_PATH];
1847         if (!SUCCEEDED(SHGetFolderPath(nullptr, CSIDL_COMMON_APPDATA, nullptr, 0, path)))
1848                 return "";
1849         return String(path) + "\\icinga2";
1850 }
1851
1852 #endif /* _WIN32 */
1853
1854 /**
1855  * Retrieve the environment variable value by given key.
1856  *
1857  * @param env Environment variable name.
1858  */
1859
1860 String Utility::GetFromEnvironment(const String& env)
1861 {
1862         const char *envValue = getenv(env.CStr());
1863
1864         if (envValue == NULL)
1865                 return String();
1866         else
1867                 return String(envValue);
1868 }
1869
1870 /**
1871  * Compare the password entered by a client with the actual password.
1872  * The comparision is safe against timing attacks.
1873  */
1874 bool Utility::ComparePasswords(const String& enteredPassword, const String& actualPassword)
1875 {
1876         volatile const char * volatile enteredPasswordCStr = enteredPassword.CStr();
1877         volatile size_t enteredPasswordLen = enteredPassword.GetLength();
1878
1879         volatile const char * volatile actualPasswordCStr = actualPassword.CStr();
1880         volatile size_t actualPasswordLen = actualPassword.GetLength();
1881
1882         volatile uint_fast8_t result = enteredPasswordLen == actualPasswordLen;
1883
1884         if (result) {
1885                 auto cStr (actualPasswordCStr);
1886                 auto len (actualPasswordLen);
1887
1888                 actualPasswordCStr = cStr;
1889                 actualPasswordLen = len;
1890         } else {
1891                 auto cStr (enteredPasswordCStr);
1892                 auto len (enteredPasswordLen);
1893
1894                 actualPasswordCStr = cStr;
1895                 actualPasswordLen = len;
1896         }
1897
1898         for (volatile size_t i = 0; i < enteredPasswordLen; ++i) {
1899                 result &= uint_fast8_t(enteredPasswordCStr[i] == actualPasswordCStr[i]);
1900         }
1901
1902         return result;
1903 }