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