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_getthrds_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 | |
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 | } |
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 | |
97 | SPDLOG_INLINE std::tm localtime() SPDLOG_NOEXCEPT |
98 | { |
99 | std::time_t now_t = ::time(nullptr); |
100 | return localtime(now_t); |
101 | } |
102 | |
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 | |
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 |
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 true; |
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 | |
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 | |
172 | SPDLOG_INLINE int remove_if_exists(const filename_t &filename) SPDLOG_NOEXCEPT |
173 | { |
174 | return path_exists(filename) ? remove(filename) : 0; |
175 | } |
176 | |
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) |
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 | #ifdef _MSC_VER |
203 | // avoid warning about unreachable statement at the end of filesize() |
204 | # pragma warning(push) |
205 | # pragma warning(disable : 4702) |
206 | #endif |
207 | |
208 | // Return file size according to open FILE* object |
209 | SPDLOG_INLINE size_t filesize(FILE *f) |
210 | { |
211 | if (f == nullptr) |
212 | { |
213 | throw_spdlog_ex("Failed getting file size. fd is null" ); |
214 | } |
215 | #if defined(_WIN32) && !defined(__CYGWIN__) |
216 | int fd = ::_fileno(f); |
217 | # if defined(_WIN64) // 64 bits |
218 | __int64 ret = ::_filelengthi64(fd); |
219 | if (ret >= 0) |
220 | { |
221 | return static_cast<size_t>(ret); |
222 | } |
223 | |
224 | # else // windows 32 bits |
225 | long ret = ::_filelength(fd); |
226 | if (ret >= 0) |
227 | { |
228 | return static_cast<size_t>(ret); |
229 | } |
230 | # endif |
231 | |
232 | #else // unix |
233 | // OpenBSD and AIX doesn't compile with :: before the fileno(..) |
234 | # if defined(__OpenBSD__) || defined(_AIX) |
235 | int fd = fileno(f); |
236 | # else |
237 | int fd = ::fileno(f); |
238 | # endif |
239 | // 64 bits(but not in osx or cygwin, where fstat64 is deprecated) |
240 | # if (defined(__linux__) || defined(__sun) || defined(_AIX)) && (defined(__LP64__) || defined(_LP64)) |
241 | struct stat64 st; |
242 | if (::fstat64(fd, &st) == 0) |
243 | { |
244 | return static_cast<size_t>(st.st_size); |
245 | } |
246 | # else // other unix or linux 32 bits or cygwin |
247 | struct stat st; |
248 | if (::fstat(fd, &st) == 0) |
249 | { |
250 | return static_cast<size_t>(st.st_size); |
251 | } |
252 | # endif |
253 | #endif |
254 | throw_spdlog_ex("Failed getting file size from fd" , errno); |
255 | return 0; // will not be reached. |
256 | } |
257 | |
258 | #ifdef _MSC_VER |
259 | # pragma warning(pop) |
260 | #endif |
261 | |
262 | // Return utc offset in minutes or throw spdlog_ex on failure |
263 | SPDLOG_INLINE int utc_minutes_offset(const std::tm &tm) |
264 | { |
265 | |
266 | #ifdef _WIN32 |
267 | # if _WIN32_WINNT < _WIN32_WINNT_WS08 |
268 | TIME_ZONE_INFORMATION tzinfo; |
269 | auto rv = ::GetTimeZoneInformation(&tzinfo); |
270 | # else |
271 | DYNAMIC_TIME_ZONE_INFORMATION tzinfo; |
272 | auto rv = ::GetDynamicTimeZoneInformation(&tzinfo); |
273 | # endif |
274 | if (rv == TIME_ZONE_ID_INVALID) |
275 | throw_spdlog_ex("Failed getting timezone info. " , errno); |
276 | |
277 | int offset = -tzinfo.Bias; |
278 | if (tm.tm_isdst) |
279 | { |
280 | offset -= tzinfo.DaylightBias; |
281 | } |
282 | else |
283 | { |
284 | offset -= tzinfo.StandardBias; |
285 | } |
286 | return offset; |
287 | #else |
288 | |
289 | # if defined(sun) || defined(__sun) || defined(_AIX) || (!defined(_BSD_SOURCE) && !defined(_GNU_SOURCE)) |
290 | // 'tm_gmtoff' field is BSD extension and it's missing on SunOS/Solaris |
291 | struct helper |
292 | { |
293 | static long int calculate_gmt_offset(const std::tm &localtm = details::os::localtime(), const std::tm &gmtm = details::os::gmtime()) |
294 | { |
295 | int local_year = localtm.tm_year + (1900 - 1); |
296 | int gmt_year = gmtm.tm_year + (1900 - 1); |
297 | |
298 | long int days = ( |
299 | // difference in day of year |
300 | localtm.tm_yday - |
301 | gmtm.tm_yday |
302 | |
303 | // + intervening leap days |
304 | + ((local_year >> 2) - (gmt_year >> 2)) - (local_year / 100 - gmt_year / 100) + |
305 | ((local_year / 100 >> 2) - (gmt_year / 100 >> 2)) |
306 | |
307 | // + difference in years * 365 */ |
308 | + static_cast<long int>(local_year - gmt_year) * 365); |
309 | |
310 | long int hours = (24 * days) + (localtm.tm_hour - gmtm.tm_hour); |
311 | long int mins = (60 * hours) + (localtm.tm_min - gmtm.tm_min); |
312 | long int secs = (60 * mins) + (localtm.tm_sec - gmtm.tm_sec); |
313 | |
314 | return secs; |
315 | } |
316 | }; |
317 | |
318 | auto offset_seconds = helper::calculate_gmt_offset(tm); |
319 | # else |
320 | auto offset_seconds = tm.tm_gmtoff; |
321 | # endif |
322 | |
323 | return static_cast<int>(offset_seconds / 60); |
324 | #endif |
325 | } |
326 | |
327 | // Return current thread id as size_t |
328 | // It exists because the std::this_thread::get_id() is much slower(especially |
329 | // under VS 2013) |
330 | SPDLOG_INLINE size_t _thread_id() SPDLOG_NOEXCEPT |
331 | { |
332 | #ifdef _WIN32 |
333 | return static_cast<size_t>(::GetCurrentThreadId()); |
334 | #elif defined(__linux__) |
335 | # if defined(__ANDROID__) && defined(__ANDROID_API__) && (__ANDROID_API__ < 21) |
336 | # define SYS_gettid __NR_gettid |
337 | # endif |
338 | return static_cast<size_t>(::syscall(SYS_gettid)); |
339 | #elif defined(_AIX) |
340 | struct __pthrdsinfo buf; |
341 | int reg_size = 0; |
342 | pthread_t pt = pthread_self(); |
343 | int retval = pthread_getthrds_np(&pt, PTHRDSINFO_QUERY_TID, &buf, sizeof(buf), NULL, ®_size); |
344 | int tid = (!retval) ? buf.__pi_tid : 0; |
345 | return static_cast<size_t>(tid); |
346 | #elif defined(__DragonFly__) || defined(__FreeBSD__) |
347 | return static_cast<size_t>(::pthread_getthreadid_np()); |
348 | #elif defined(__NetBSD__) |
349 | return static_cast<size_t>(::_lwp_self()); |
350 | #elif defined(__OpenBSD__) |
351 | return static_cast<size_t>(::getthrid()); |
352 | #elif defined(__sun) |
353 | return static_cast<size_t>(::thr_self()); |
354 | #elif __APPLE__ |
355 | uint64_t tid; |
356 | pthread_threadid_np(nullptr, &tid); |
357 | return static_cast<size_t>(tid); |
358 | #else // Default to standard C++11 (other Unix) |
359 | return static_cast<size_t>(std::hash<std::thread::id>()(std::this_thread::get_id())); |
360 | #endif |
361 | } |
362 | |
363 | // Return current thread id as size_t (from thread local storage) |
364 | SPDLOG_INLINE size_t thread_id() SPDLOG_NOEXCEPT |
365 | { |
366 | #if defined(SPDLOG_NO_TLS) |
367 | return _thread_id(); |
368 | #else // cache thread id in tls |
369 | static thread_local const size_t tid = _thread_id(); |
370 | return tid; |
371 | #endif |
372 | } |
373 | |
374 | // This is avoid msvc issue in sleep_for that happens if the clock changes. |
375 | // See https://github.com/gabime/spdlog/issues/609 |
376 | SPDLOG_INLINE void sleep_for_millis(unsigned int milliseconds) SPDLOG_NOEXCEPT |
377 | { |
378 | #if defined(_WIN32) |
379 | ::Sleep(milliseconds); |
380 | #else |
381 | std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds)); |
382 | #endif |
383 | } |
384 | |
385 | // wchar support for windows file names (SPDLOG_WCHAR_FILENAMES must be defined) |
386 | #if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES) |
387 | SPDLOG_INLINE std::string filename_to_str(const filename_t &filename) |
388 | { |
389 | memory_buf_t buf; |
390 | wstr_to_utf8buf(filename, buf); |
391 | return SPDLOG_BUF_TO_STRING(buf); |
392 | } |
393 | #else |
394 | SPDLOG_INLINE std::string filename_to_str(const filename_t &filename) |
395 | { |
396 | return filename; |
397 | } |
398 | #endif |
399 | |
400 | SPDLOG_INLINE int pid() SPDLOG_NOEXCEPT |
401 | { |
402 | |
403 | #ifdef _WIN32 |
404 | return conditional_static_cast<int>(::GetCurrentProcessId()); |
405 | #else |
406 | return conditional_static_cast<int>(::getpid()); |
407 | #endif |
408 | } |
409 | |
410 | // Determine if the terminal supports colors |
411 | // Based on: https://github.com/agauniyal/rang/ |
412 | SPDLOG_INLINE bool is_color_terminal() SPDLOG_NOEXCEPT |
413 | { |
414 | #ifdef _WIN32 |
415 | return true; |
416 | #else |
417 | |
418 | static const bool result = []() { |
419 | const char *env_colorterm_p = std::getenv("COLORTERM" ); |
420 | if (env_colorterm_p != nullptr) |
421 | { |
422 | return true; |
423 | } |
424 | |
425 | static constexpr std::array<const char *, 16> terms = {{"ansi" , "color" , "console" , "cygwin" , "gnome" , "konsole" , "kterm" , "linux" , |
426 | "msys" , "putty" , "rxvt" , "screen" , "vt100" , "xterm" , "alacritty" , "vt102" }}; |
427 | |
428 | const char *env_term_p = std::getenv("TERM" ); |
429 | if (env_term_p == nullptr) |
430 | { |
431 | return false; |
432 | } |
433 | |
434 | return std::any_of(terms.begin(), terms.end(), [&](const char *term) { return std::strstr(env_term_p, term) != nullptr; }); |
435 | }(); |
436 | |
437 | return result; |
438 | #endif |
439 | } |
440 | |
441 | // Determine if the terminal attached |
442 | // Source: https://github.com/agauniyal/rang/ |
443 | SPDLOG_INLINE bool in_terminal(FILE *file) SPDLOG_NOEXCEPT |
444 | { |
445 | |
446 | #ifdef _WIN32 |
447 | return ::_isatty(_fileno(file)) != 0; |
448 | #else |
449 | return ::isatty(fileno(file)) != 0; |
450 | #endif |
451 | } |
452 | |
453 | #if (defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT) || defined(SPDLOG_WCHAR_FILENAMES)) && defined(_WIN32) |
454 | SPDLOG_INLINE void wstr_to_utf8buf(wstring_view_t wstr, memory_buf_t &target) |
455 | { |
456 | if (wstr.size() > static_cast<size_t>((std::numeric_limits<int>::max)()) / 2 - 1) |
457 | { |
458 | throw_spdlog_ex("UTF-16 string is too big to be converted to UTF-8" ); |
459 | } |
460 | |
461 | int wstr_size = static_cast<int>(wstr.size()); |
462 | if (wstr_size == 0) |
463 | { |
464 | target.resize(0); |
465 | return; |
466 | } |
467 | |
468 | int result_size = static_cast<int>(target.capacity()); |
469 | if ((wstr_size + 1) * 2 > result_size) |
470 | { |
471 | result_size = ::WideCharToMultiByte(CP_UTF8, 0, wstr.data(), wstr_size, NULL, 0, NULL, NULL); |
472 | } |
473 | |
474 | if (result_size > 0) |
475 | { |
476 | target.resize(result_size); |
477 | result_size = ::WideCharToMultiByte(CP_UTF8, 0, wstr.data(), wstr_size, target.data(), result_size, NULL, NULL); |
478 | |
479 | if (result_size > 0) |
480 | { |
481 | target.resize(result_size); |
482 | return; |
483 | } |
484 | } |
485 | |
486 | throw_spdlog_ex(fmt_lib::format("WideCharToMultiByte failed. Last error: {}" , ::GetLastError())); |
487 | } |
488 | |
489 | SPDLOG_INLINE void utf8_to_wstrbuf(string_view_t str, wmemory_buf_t &target) |
490 | { |
491 | if (str.size() > static_cast<size_t>((std::numeric_limits<int>::max)()) - 1) |
492 | { |
493 | throw_spdlog_ex("UTF-8 string is too big to be converted to UTF-16" ); |
494 | } |
495 | |
496 | int str_size = static_cast<int>(str.size()); |
497 | if (str_size == 0) |
498 | { |
499 | target.resize(0); |
500 | return; |
501 | } |
502 | |
503 | int result_size = static_cast<int>(target.capacity()); |
504 | if (str_size + 1 > result_size) |
505 | { |
506 | result_size = ::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, str.data(), str_size, NULL, 0); |
507 | } |
508 | |
509 | if (result_size > 0) |
510 | { |
511 | target.resize(result_size); |
512 | result_size = ::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, str.data(), str_size, target.data(), result_size); |
513 | |
514 | if (result_size > 0) |
515 | { |
516 | target.resize(result_size); |
517 | return; |
518 | } |
519 | } |
520 | |
521 | throw_spdlog_ex(fmt_lib::format("MultiByteToWideChar failed. Last error: {}" , ::GetLastError())); |
522 | } |
523 | #endif // (defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT) || defined(SPDLOG_WCHAR_FILENAMES)) && defined(_WIN32) |
524 | |
525 | // return true on success |
526 | static SPDLOG_INLINE bool mkdir_(const filename_t &path) |
527 | { |
528 | #ifdef _WIN32 |
529 | # ifdef SPDLOG_WCHAR_FILENAMES |
530 | return ::_wmkdir(path.c_str()) == 0; |
531 | # else |
532 | return ::_mkdir(path.c_str()) == 0; |
533 | # endif |
534 | #else |
535 | return ::mkdir(path.c_str(), mode_t(0755)) == 0; |
536 | #endif |
537 | } |
538 | |
539 | // create the given directory - and all directories leading to it |
540 | // return true on success or if the directory already exists |
541 | SPDLOG_INLINE bool create_dir(const filename_t &path) |
542 | { |
543 | if (path_exists(path)) |
544 | { |
545 | return true; |
546 | } |
547 | |
548 | if (path.empty()) |
549 | { |
550 | return false; |
551 | } |
552 | |
553 | size_t search_offset = 0; |
554 | do |
555 | { |
556 | auto token_pos = path.find_first_of(folder_seps_filename, search_offset); |
557 | // treat the entire path as a folder if no folder separator not found |
558 | if (token_pos == filename_t::npos) |
559 | { |
560 | token_pos = path.size(); |
561 | } |
562 | |
563 | auto subdir = path.substr(0, token_pos); |
564 | |
565 | if (!subdir.empty() && !path_exists(subdir) && !mkdir_(subdir)) |
566 | { |
567 | return false; // return error if failed creating dir |
568 | } |
569 | search_offset = token_pos + 1; |
570 | } while (search_offset < path.size()); |
571 | |
572 | return true; |
573 | } |
574 | |
575 | // Return directory name from given path or empty string |
576 | // "abc/file" => "abc" |
577 | // "abc/" => "abc" |
578 | // "abc" => "" |
579 | // "abc///" => "abc//" |
580 | SPDLOG_INLINE filename_t dir_name(const filename_t &path) |
581 | { |
582 | auto pos = path.find_last_of(folder_seps_filename); |
583 | return pos != filename_t::npos ? path.substr(0, pos) : filename_t{}; |
584 | } |
585 | |
586 | std::string SPDLOG_INLINE getenv(const char *field) |
587 | { |
588 | |
589 | #if defined(_MSC_VER) |
590 | # if defined(__cplusplus_winrt) |
591 | return std::string{}; // not supported under uwp |
592 | # else |
593 | size_t len = 0; |
594 | char buf[128]; |
595 | bool ok = ::getenv_s(&len, buf, sizeof(buf), field) == 0; |
596 | return ok ? buf : std::string{}; |
597 | # endif |
598 | #else // revert to getenv |
599 | char *buf = ::getenv(field); |
600 | return buf ? buf : std::string{}; |
601 | #endif |
602 | } |
603 | |
604 | } // namespace os |
605 | } // namespace details |
606 | } // namespace spdlog |
607 | |