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/file_helper.h>
8#endif
9
10#include <spdlog/common.h>
11#include <spdlog/details/os.h>
12
13#include <cerrno>
14#include <chrono>
15#include <cstdio>
16#include <string>
17#include <thread>
18#include <tuple>
19
20namespace spdlog {
21namespace details {
22
23SPDLOG_INLINE file_helper::file_helper(const file_event_handlers &event_handlers)
24 : event_handlers_(event_handlers) {}
25
26SPDLOG_INLINE file_helper::~file_helper() { close(); }
27
28SPDLOG_INLINE void file_helper::open(const filename_t &fname, bool truncate) {
29 close();
30 filename_ = fname;
31
32 auto *mode = SPDLOG_FILENAME_T("ab");
33 auto *trunc_mode = SPDLOG_FILENAME_T("wb");
34
35 if (event_handlers_.before_open) {
36 event_handlers_.before_open(filename_);
37 }
38 for (int tries = 0; tries < open_tries_; ++tries) {
39 // create containing folder if not exists already.
40 os::create_dir(path: os::dir_name(path: fname));
41 if (truncate) {
42 // Truncate by opening-and-closing a tmp file in "wb" mode, always
43 // opening the actual log-we-write-to in "ab" mode, since that
44 // interacts more politely with eternal processes that might
45 // rotate/truncate the file underneath us.
46 std::FILE *tmp;
47 if (os::fopen_s(fp: &tmp, filename: fname, mode: trunc_mode)) {
48 continue;
49 }
50 std::fclose(stream: tmp);
51 }
52 if (!os::fopen_s(fp: &fd_, filename: fname, mode)) {
53 if (event_handlers_.after_open) {
54 event_handlers_.after_open(filename_, fd_);
55 }
56 return;
57 }
58
59 details::os::sleep_for_millis(milliseconds: open_interval_);
60 }
61
62 throw_spdlog_ex(msg: "Failed opening file " + os::filename_to_str(filename: filename_) + " for writing",
63 errno);
64}
65
66SPDLOG_INLINE void file_helper::reopen(bool truncate) {
67 if (filename_.empty()) {
68 throw_spdlog_ex(msg: "Failed re opening file - was not opened before");
69 }
70 this->open(fname: filename_, truncate);
71}
72
73SPDLOG_INLINE void file_helper::flush() {
74 if (std::fflush(stream: fd_) != 0) {
75 throw_spdlog_ex(msg: "Failed flush to file " + os::filename_to_str(filename: filename_), errno);
76 }
77}
78
79SPDLOG_INLINE void file_helper::sync() {
80 if (!os::fsync(fp: fd_)) {
81 throw_spdlog_ex(msg: "Failed to fsync file " + os::filename_to_str(filename: filename_), errno);
82 }
83}
84
85SPDLOG_INLINE void file_helper::close() {
86 if (fd_ != nullptr) {
87 if (event_handlers_.before_close) {
88 event_handlers_.before_close(filename_, fd_);
89 }
90
91 std::fclose(stream: fd_);
92 fd_ = nullptr;
93
94 if (event_handlers_.after_close) {
95 event_handlers_.after_close(filename_);
96 }
97 }
98}
99
100SPDLOG_INLINE void file_helper::write(const memory_buf_t &buf) {
101 if (fd_ == nullptr) return;
102 size_t msg_size = buf.size();
103 auto data = buf.data();
104 if (std::fwrite(ptr: data, size: 1, n: msg_size, s: fd_) != msg_size) {
105 throw_spdlog_ex(msg: "Failed writing to file " + os::filename_to_str(filename: filename_), errno);
106 }
107}
108
109SPDLOG_INLINE size_t file_helper::size() const {
110 if (fd_ == nullptr) {
111 throw_spdlog_ex(msg: "Cannot use size() on closed file " + os::filename_to_str(filename: filename_));
112 }
113 return os::filesize(f: fd_);
114}
115
116SPDLOG_INLINE const filename_t &file_helper::filename() const { return filename_; }
117
118//
119// return file path and its extension:
120//
121// "mylog.txt" => ("mylog", ".txt")
122// "mylog" => ("mylog", "")
123// "mylog." => ("mylog.", "")
124// "/dir1/dir2/mylog.txt" => ("/dir1/dir2/mylog", ".txt")
125//
126// the starting dot in filenames is ignored (hidden files):
127//
128// ".mylog" => (".mylog". "")
129// "my_folder/.mylog" => ("my_folder/.mylog", "")
130// "my_folder/.mylog.txt" => ("my_folder/.mylog", ".txt")
131SPDLOG_INLINE std::tuple<filename_t, filename_t> file_helper::split_by_extension(
132 const filename_t &fname) {
133 auto ext_index = fname.rfind(c: '.');
134
135 // no valid extension found - return whole path and empty string as
136 // extension
137 if (ext_index == filename_t::npos || ext_index == 0 || ext_index == fname.size() - 1) {
138 return std::make_tuple(args: fname, args: filename_t());
139 }
140
141 // treat cases like "/etc/rc.d/somelogfile or "/abc/.hiddenfile"
142 auto folder_index = fname.find_last_of(s: details::os::folder_seps_filename);
143 if (folder_index != filename_t::npos && folder_index >= ext_index - 1) {
144 return std::make_tuple(args: fname, args: filename_t());
145 }
146
147 // finally - return a valid base and extension tuple
148 return std::make_tuple(args: fname.substr(pos: 0, n: ext_index), args: fname.substr(pos: ext_index));
149}
150
151} // namespace details
152} // namespace spdlog
153