1 // Copyright(c) 2015-present, Gabi Melman & spdlog contributors.
2 // Distributed under the MIT License (http://opensource.org/licenses/MIT)
3
4 #pragma once
5
6 #ifndef SPDLOG_HEADER_ONLY
7 #include <spdlog/details/os.h>
8 #endif
9
10 #include <spdlog/common.h>
11
12 #include <algorithm>
13 #include <chrono>
14 #include <cstdio>
15 #include <cstdlib>
16 #include <cstring>
17 #include <ctime>
18 #include <string>
19 #include <thread>
20 #include <array>
21 #include <sys/stat.h>
22 #include <sys/types.h>
23
24 #ifdef _WIN32
25
26 #include <io.h> // _get_osfhandle and _isatty support
27 #include <process.h> // _get_pid support
28 #include <spdlog/details/windows_include.h>
29
30 #ifdef __MINGW32__
31 #include <share.h>
32 #endif
33
34 #if defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT) || defined(SPDLOG_WCHAR_FILENAMES)
35 #include <limits>
36 #endif
37
38 #include <direct.h> // for _mkdir/_wmkdir
39
40 #else // unix
41
42 #include <fcntl.h>
43 #include <unistd.h>
44
45 #ifdef __linux__
46 #include <sys/syscall.h> //Use gettid() syscall under linux to get thread id
47
48 #elif defined(_AIX)
49 #include <pthread.h> // for pthread_getthreadid_np
50
51 #elif defined(__DragonFly__) || defined(__FreeBSD__)
52 #include <pthread_np.h> // for pthread_getthreadid_np
53
54 #elif defined(__NetBSD__)
55 #include <lwp.h> // for _lwp_self
56
57 #elif defined(__sun)
58 #include <thread.h> // for thr_self
59 #endif
60
61 #endif // unix
62
63 #ifndef __has_feature // Clang - feature checking macros.
64 #define __has_feature(x) 0 // Compatibility with non-clang compilers.
65 #endif
66
67 namespace spdlog {
68 namespace details {
69 namespace os {
70
now()71 SPDLOG_INLINE spdlog::log_clock::time_point now() SPDLOG_NOEXCEPT
72 {
73
74 #if defined __linux__ && defined SPDLOG_CLOCK_COARSE
75 timespec ts;
76 ::clock_gettime(CLOCK_REALTIME_COARSE, &ts);
77 return std::chrono::time_point<log_clock, typename log_clock::duration>(
78 std::chrono::duration_cast<typename log_clock::duration>(std::chrono::seconds(ts.tv_sec) + std::chrono::nanoseconds(ts.tv_nsec)));
79
80 #else
81 return log_clock::now();
82 #endif
83 }
localtime(const std::time_t & time_tt)84 SPDLOG_INLINE std::tm localtime(const std::time_t &time_tt) SPDLOG_NOEXCEPT
85 {
86
87 #ifdef _WIN32
88 std::tm tm;
89 ::localtime_s(&tm, &time_tt);
90 #else
91 std::tm tm;
92 ::localtime_r(&time_tt, &tm);
93 #endif
94 return tm;
95 }
96
localtime()97 SPDLOG_INLINE std::tm localtime() SPDLOG_NOEXCEPT
98 {
99 std::time_t now_t = ::time(nullptr);
100 return localtime(now_t);
101 }
102
gmtime(const std::time_t & time_tt)103 SPDLOG_INLINE std::tm gmtime(const std::time_t &time_tt) SPDLOG_NOEXCEPT
104 {
105
106 #ifdef _WIN32
107 std::tm tm;
108 ::gmtime_s(&tm, &time_tt);
109 #else
110 std::tm tm;
111 ::gmtime_r(&time_tt, &tm);
112 #endif
113 return tm;
114 }
115
gmtime()116 SPDLOG_INLINE std::tm gmtime() SPDLOG_NOEXCEPT
117 {
118 std::time_t now_t = ::time(nullptr);
119 return gmtime(now_t);
120 }
121
122 // fopen_s on non windows for writing
fopen_s(FILE ** fp,const filename_t & filename,const filename_t & mode)123 SPDLOG_INLINE bool fopen_s(FILE **fp, const filename_t &filename, const filename_t &mode)
124 {
125 #ifdef _WIN32
126 #ifdef SPDLOG_WCHAR_FILENAMES
127 *fp = ::_wfsopen((filename.c_str()), mode.c_str(), _SH_DENYNO);
128 #else
129 *fp = ::_fsopen((filename.c_str()), mode.c_str(), _SH_DENYNO);
130 #endif
131 #if defined(SPDLOG_PREVENT_CHILD_FD)
132 if (*fp != nullptr)
133 {
134 auto file_handle = reinterpret_cast<HANDLE>(_get_osfhandle(::_fileno(*fp)));
135 if (!::SetHandleInformation(file_handle, HANDLE_FLAG_INHERIT, 0))
136 {
137 ::fclose(*fp);
138 *fp = nullptr;
139 }
140 }
141 #endif
142 #else // unix
143 #if defined(SPDLOG_PREVENT_CHILD_FD)
144 const int mode_flag = mode == SPDLOG_FILENAME_T("ab") ? O_APPEND : O_TRUNC;
145 const int fd = ::open((filename.c_str()), O_CREAT | O_WRONLY | O_CLOEXEC | mode_flag, mode_t(0644));
146 if (fd == -1)
147 {
148 return false;
149 }
150 *fp = ::fdopen(fd, mode.c_str());
151 if (*fp == nullptr)
152 {
153 ::close(fd);
154 }
155 #else
156 *fp = ::fopen((filename.c_str()), mode.c_str());
157 #endif
158 #endif
159
160 return *fp == nullptr;
161 }
162
remove(const filename_t & filename)163 SPDLOG_INLINE int remove(const filename_t &filename) SPDLOG_NOEXCEPT
164 {
165 #if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES)
166 return ::_wremove(filename.c_str());
167 #else
168 return std::remove(filename.c_str());
169 #endif
170 }
171
remove_if_exists(const filename_t & filename)172 SPDLOG_INLINE int remove_if_exists(const filename_t &filename) SPDLOG_NOEXCEPT
173 {
174 return path_exists(filename) ? remove(filename) : 0;
175 }
176
rename(const filename_t & filename1,const filename_t & filename2)177 SPDLOG_INLINE int rename(const filename_t &filename1, const filename_t &filename2) SPDLOG_NOEXCEPT
178 {
179 #if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES)
180 return ::_wrename(filename1.c_str(), filename2.c_str());
181 #else
182 return std::rename(filename1.c_str(), filename2.c_str());
183 #endif
184 }
185
186 // Return true if path exists (file or directory)
path_exists(const filename_t & filename)187 SPDLOG_INLINE bool path_exists(const filename_t &filename) SPDLOG_NOEXCEPT
188 {
189 #ifdef _WIN32
190 #ifdef SPDLOG_WCHAR_FILENAMES
191 auto attribs = ::GetFileAttributesW(filename.c_str());
192 #else
193 auto attribs = ::GetFileAttributesA(filename.c_str());
194 #endif
195 return attribs != INVALID_FILE_ATTRIBUTES;
196 #else // common linux/unix all have the stat system call
197 struct stat buffer;
198 return (::stat(filename.c_str(), &buffer) == 0);
199 #endif
200 }
201
202 // Return file size according to open FILE* object
filesize(FILE * f)203 SPDLOG_INLINE size_t filesize(FILE *f)
204 {
205 if (f == nullptr)
206 {
207 throw_spdlog_ex("Failed getting file size. fd is null");
208 }
209 #if defined(_WIN32) && !defined(__CYGWIN__)
210 int fd = ::_fileno(f);
211 #if _WIN64 // 64 bits
212 __int64 ret = ::_filelengthi64(fd);
213 if (ret >= 0)
214 {
215 return static_cast<size_t>(ret);
216 }
217
218 #else // windows 32 bits
219 long ret = ::_filelength(fd);
220 if (ret >= 0)
221 {
222 return static_cast<size_t>(ret);
223 }
224 #endif
225
226 #else // unix
227 // OpenBSD doesn't compile with :: before the fileno(..)
228 #if defined(__OpenBSD__)
229 int fd = fileno(f);
230 #else
231 int fd = ::fileno(f);
232 #endif
233 // 64 bits(but not in osx or cygwin, where fstat64 is deprecated)
234 #if (defined(__linux__) || defined(__sun) || defined(_AIX)) && (defined(__LP64__) || defined(_LP64))
235 struct stat64 st;
236 if (::fstat64(fd, &st) == 0)
237 {
238 return static_cast<size_t>(st.st_size);
239 }
240 #else // other unix or linux 32 bits or cygwin
241 struct stat st;
242 if (::fstat(fd, &st) == 0)
243 {
244 return static_cast<size_t>(st.st_size);
245 }
246 #endif
247 #endif
248 throw_spdlog_ex("Failed getting file size from fd", errno);
249 return 0; // will not be reached.
250 }
251
252 // Return utc offset in minutes or throw spdlog_ex on failure
utc_minutes_offset(const std::tm & tm)253 SPDLOG_INLINE int utc_minutes_offset(const std::tm &tm)
254 {
255
256 #ifdef _WIN32
257 #if _WIN32_WINNT < _WIN32_WINNT_WS08
258 TIME_ZONE_INFORMATION tzinfo;
259 auto rv = ::GetTimeZoneInformation(&tzinfo);
260 #else
261 DYNAMIC_TIME_ZONE_INFORMATION tzinfo;
262 auto rv = ::GetDynamicTimeZoneInformation(&tzinfo);
263 #endif
264 if (rv == TIME_ZONE_ID_INVALID)
265 throw_spdlog_ex("Failed getting timezone info. ", errno);
266
267 int offset = -tzinfo.Bias;
268 if (tm.tm_isdst)
269 {
270 offset -= tzinfo.DaylightBias;
271 }
272 else
273 {
274 offset -= tzinfo.StandardBias;
275 }
276 return offset;
277 #else
278
279 #if defined(sun) || defined(__sun) || defined(_AIX) || (!defined(_BSD_SOURCE) && !defined(_GNU_SOURCE))
280 // 'tm_gmtoff' field is BSD extension and it's missing on SunOS/Solaris
281 struct helper
282 {
283 static long int calculate_gmt_offset(const std::tm &localtm = details::os::localtime(), const std::tm &gmtm = details::os::gmtime())
284 {
285 int local_year = localtm.tm_year + (1900 - 1);
286 int gmt_year = gmtm.tm_year + (1900 - 1);
287
288 long int days = (
289 // difference in day of year
290 localtm.tm_yday -
291 gmtm.tm_yday
292
293 // + intervening leap days
294 + ((local_year >> 2) - (gmt_year >> 2)) - (local_year / 100 - gmt_year / 100) +
295 ((local_year / 100 >> 2) - (gmt_year / 100 >> 2))
296
297 // + difference in years * 365 */
298 + (long int)(local_year - gmt_year) * 365);
299
300 long int hours = (24 * days) + (localtm.tm_hour - gmtm.tm_hour);
301 long int mins = (60 * hours) + (localtm.tm_min - gmtm.tm_min);
302 long int secs = (60 * mins) + (localtm.tm_sec - gmtm.tm_sec);
303
304 return secs;
305 }
306 };
307
308 auto offset_seconds = helper::calculate_gmt_offset(tm);
309 #else
310 auto offset_seconds = tm.tm_gmtoff;
311 #endif
312
313 return static_cast<int>(offset_seconds / 60);
314 #endif
315 }
316
317 // Return current thread id as size_t
318 // It exists because the std::this_thread::get_id() is much slower(especially
319 // under VS 2013)
_thread_id()320 SPDLOG_INLINE size_t _thread_id() SPDLOG_NOEXCEPT
321 {
322 #ifdef _WIN32
323 return static_cast<size_t>(::GetCurrentThreadId());
324 #elif defined(__linux__)
325 #if defined(__ANDROID__) && defined(__ANDROID_API__) && (__ANDROID_API__ < 21)
326 #define SYS_gettid __NR_gettid
327 #endif
328 return static_cast<size_t>(::syscall(SYS_gettid));
329 #elif defined(_AIX) || defined(__DragonFly__) || defined(__FreeBSD__)
330 return static_cast<size_t>(::pthread_getthreadid_np());
331 #elif defined(__NetBSD__)
332 return static_cast<size_t>(::_lwp_self());
333 #elif defined(__OpenBSD__)
334 return static_cast<size_t>(::getthrid());
335 #elif defined(__sun)
336 return static_cast<size_t>(::thr_self());
337 #elif __APPLE__
338 uint64_t tid;
339 pthread_threadid_np(nullptr, &tid);
340 return static_cast<size_t>(tid);
341 #else // Default to standard C++11 (other Unix)
342 return static_cast<size_t>(std::hash<std::thread::id>()(std::this_thread::get_id()));
343 #endif
344 }
345
346 // Return current thread id as size_t (from thread local storage)
thread_id()347 SPDLOG_INLINE size_t thread_id() SPDLOG_NOEXCEPT
348 {
349 #if defined(SPDLOG_NO_TLS)
350 return _thread_id();
351 #else // cache thread id in tls
352 static thread_local const size_t tid = _thread_id();
353 return tid;
354 #endif
355 }
356
357 // This is avoid msvc issue in sleep_for that happens if the clock changes.
358 // See https://github.com/gabime/spdlog/issues/609
sleep_for_millis(int milliseconds)359 SPDLOG_INLINE void sleep_for_millis(int milliseconds) SPDLOG_NOEXCEPT
360 {
361 #if defined(_WIN32)
362 ::Sleep(milliseconds);
363 #else
364 std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds));
365 #endif
366 }
367
368 // wchar support for windows file names (SPDLOG_WCHAR_FILENAMES must be defined)
369 #if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES)
filename_to_str(const filename_t & filename)370 SPDLOG_INLINE std::string filename_to_str(const filename_t &filename)
371 {
372 memory_buf_t buf;
373 wstr_to_utf8buf(filename, buf);
374 return fmt::to_string(buf);
375 }
376 #else
filename_to_str(const filename_t & filename)377 SPDLOG_INLINE std::string filename_to_str(const filename_t &filename)
378 {
379 return filename;
380 }
381 #endif
382
pid()383 SPDLOG_INLINE int pid() SPDLOG_NOEXCEPT
384 {
385
386 #ifdef _WIN32
387 return static_cast<int>(::GetCurrentProcessId());
388 #else
389 return static_cast<int>(::getpid());
390 #endif
391 }
392
393 // Determine if the terminal supports colors
394 // Based on: https://github.com/agauniyal/rang/
is_color_terminal()395 SPDLOG_INLINE bool is_color_terminal() SPDLOG_NOEXCEPT
396 {
397 #ifdef _WIN32
398 return true;
399 #else
400
401 static const bool result = []() {
402 const char *env_colorterm_p = std::getenv("COLORTERM");
403 if (env_colorterm_p != nullptr)
404 {
405 return true;
406 }
407
408 static constexpr std::array<const char *, 15> terms = {{"ansi", "color", "console", "cygwin", "gnome", "konsole", "kterm", "linux",
409 "msys", "putty", "rxvt", "screen", "vt100", "xterm", "alacritty"}};
410
411 const char *env_term_p = std::getenv("TERM");
412 if (env_term_p == nullptr)
413 {
414 return false;
415 }
416
417 return std::any_of(terms.begin(), terms.end(), [&](const char *term) { return std::strstr(env_term_p, term) != nullptr; });
418 }();
419
420 return result;
421 #endif
422 }
423
424 // Determine if the terminal attached
425 // Source: https://github.com/agauniyal/rang/
in_terminal(FILE * file)426 SPDLOG_INLINE bool in_terminal(FILE *file) SPDLOG_NOEXCEPT
427 {
428
429 #ifdef _WIN32
430 return ::_isatty(_fileno(file)) != 0;
431 #else
432 return ::isatty(fileno(file)) != 0;
433 #endif
434 }
435
436 #if (defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT) || defined(SPDLOG_WCHAR_FILENAMES)) && defined(_WIN32)
wstr_to_utf8buf(wstring_view_t wstr,memory_buf_t & target)437 SPDLOG_INLINE void wstr_to_utf8buf(wstring_view_t wstr, memory_buf_t &target)
438 {
439 if (wstr.size() > static_cast<size_t>((std::numeric_limits<int>::max)()))
440 {
441 throw_spdlog_ex("UTF-16 string is too big to be converted to UTF-8");
442 }
443
444 int wstr_size = static_cast<int>(wstr.size());
445 if (wstr_size == 0)
446 {
447 target.resize(0);
448 return;
449 }
450
451 int result_size = static_cast<int>(target.capacity());
452 if ((wstr_size + 1) * 2 > result_size)
453 {
454 result_size = ::WideCharToMultiByte(CP_UTF8, 0, wstr.data(), wstr_size, NULL, 0, NULL, NULL);
455 }
456
457 if (result_size > 0)
458 {
459 target.resize(result_size);
460 result_size = ::WideCharToMultiByte(CP_UTF8, 0, wstr.data(), wstr_size, target.data(), result_size, NULL, NULL);
461
462 if (result_size > 0)
463 {
464 target.resize(result_size);
465 return;
466 }
467 }
468
469 throw_spdlog_ex(fmt::format("WideCharToMultiByte failed. Last error: {}", ::GetLastError()));
470 }
471
utf8_to_wstrbuf(string_view_t str,memory_buf_t & target)472 SPDLOG_INLINE void utf8_to_wstrbuf(string_view_t str, memory_buf_t &target)
473 {
474 if (str.size() > static_cast<size_t>((std::numeric_limits<int>::max)()))
475 {
476 throw_spdlog_ex("UTF-8 string is too big to be converted to UTF-16");
477 }
478
479 int str_size = static_cast<int>(str.size());
480 if (str_size == 0)
481 {
482 target.resize(0);
483 return;
484 }
485
486 int result_size = static_cast<int>(target.capacity());
487 if ((str_size + 1) * 2 < result_size)
488 {
489 result_size = ::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, str.data(), str_size, NULL, 0);
490 }
491
492 if (result_size > 0)
493 {
494 target.resize(result_size);
495 result_size = ::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, str.data(), str_size, (LPWSTR)target.data(), result_size);
496
497 if (result_size > 0)
498 {
499 target.resize(result_size);
500 return;
501 }
502 }
503
504 throw_spdlog_ex(fmt::format("MultiByteToWideChar failed. Last error: {}", ::GetLastError()));
505 }
506 #endif // (defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT) || defined(SPDLOG_WCHAR_FILENAMES)) && defined(_WIN32)
507
508 // return true on success
mkdir_(const filename_t & path)509 static SPDLOG_INLINE bool mkdir_(const filename_t &path)
510 {
511 #ifdef _WIN32
512 #ifdef SPDLOG_WCHAR_FILENAMES
513 return ::_wmkdir(path.c_str()) == 0;
514 #else
515 return ::_mkdir(path.c_str()) == 0;
516 #endif
517 #else
518 return ::mkdir(path.c_str(), mode_t(0755)) == 0;
519 #endif
520 }
521
522 // create the given directory - and all directories leading to it
523 // return true on success or if the directory already exists
create_dir(filename_t path)524 SPDLOG_INLINE bool create_dir(filename_t path)
525 {
526 if (path_exists(path))
527 {
528 return true;
529 }
530
531 if (path.empty())
532 {
533 return false;
534 }
535
536 #ifdef _WIN32
537 // support forward slash in windows
538 std::replace(path.begin(), path.end(), '/', folder_sep);
539 #endif
540
541 size_t search_offset = 0;
542 do
543 {
544 auto token_pos = path.find(folder_sep, search_offset);
545 // treat the entire path as a folder if no folder separator not found
546 if (token_pos == filename_t::npos)
547 {
548 token_pos = path.size();
549 }
550
551 auto subdir = path.substr(0, token_pos);
552
553 if (!subdir.empty() && !path_exists(subdir) && !mkdir_(subdir))
554 {
555 return false; // return error if failed creating dir
556 }
557 search_offset = token_pos + 1;
558 } while (search_offset < path.size());
559
560 return true;
561 }
562
563 // Return directory name from given path or empty string
564 // "abc/file" => "abc"
565 // "abc/" => "abc"
566 // "abc" => ""
567 // "abc///" => "abc//"
dir_name(filename_t path)568 SPDLOG_INLINE filename_t dir_name(filename_t path)
569 {
570 #ifdef _WIN32
571 // support forward slash in windows
572 std::replace(path.begin(), path.end(), '/', folder_sep);
573 #endif
574 auto pos = path.find_last_of(folder_sep);
575 return pos != filename_t::npos ? path.substr(0, pos) : filename_t{};
576 }
577
getenv(const char * field)578 std::string SPDLOG_INLINE getenv(const char *field)
579 {
580
581 #if defined(_MSC_VER)
582 #if defined(__cplusplus_winrt)
583 return std::string{}; // not supported under uwp
584 #else
585 size_t len = 0;
586 char buf[128];
587 bool ok = ::getenv_s(&len, buf, sizeof(buf), field) == 0;
588 return ok ? buf : std::string{};
589 #endif
590 #else // revert to getenv
591 char *buf = ::getenv(field);
592 return buf ? buf : std::string{};
593 #endif
594 }
595
596 } // namespace os
597 } // namespace details
598 } // namespace spdlog
599