blob: f1afa0f78c9a70da94905331d34465e2c1aa4467 [file] [log] [blame]
John Park33858a32018-09-28 23:05:48 -07001#include "aos/util/file.h"
Brian Silverman61175fb2016-03-13 15:35:56 -04002
Stephan Pleinesb1177672024-05-27 17:48:32 -07003#include <errno.h>
Brian Silverman61175fb2016-03-13 15:35:56 -04004#include <fcntl.h>
Austin Schuhe991fe22020-11-18 16:53:39 -08005#include <fts.h>
Stephan Pleinesb1177672024-05-27 17:48:32 -07006#include <stdio.h>
7#include <string.h>
davidjevans8b9b52f2021-09-17 08:57:30 -07008#include <sys/mman.h>
Austin Schuhfccb2d02020-01-26 16:11:19 -08009#include <sys/stat.h>
10#include <sys/types.h>
Brian Silverman61175fb2016-03-13 15:35:56 -040011#include <unistd.h>
12
Stephan Pleinesb1177672024-05-27 17:48:32 -070013#include <algorithm>
14#include <iterator>
payton.rehlf8cb3932023-06-13 11:20:44 -070015#include <optional>
Stephan Pleinesb1177672024-05-27 17:48:32 -070016#include <ostream>
James Kuszmaul3ae42262019-11-08 12:33:41 -080017#include <string_view>
Austin Schuhe2df81b2021-08-16 10:52:27 -070018#if __has_feature(memory_sanitizer)
19#include <sanitizer/msan_interface.h>
20#endif
James Kuszmaul3ae42262019-11-08 12:33:41 -080021
Stephan Pleinesb1177672024-05-27 17:48:32 -070022#include "flatbuffers/util.h"
23
John Park33858a32018-09-28 23:05:48 -070024#include "aos/scoped/scoped_fd.h"
Brian Silverman61175fb2016-03-13 15:35:56 -040025
Stephan Pleinesf63bde82024-01-13 15:59:33 -080026namespace aos::util {
Brian Silverman61175fb2016-03-13 15:35:56 -040027
payton.rehlf8cb3932023-06-13 11:20:44 -070028std::string ReadFileToStringOrDie(const std::string_view filename) {
29 std::optional<std::string> r = MaybeReadFileToString(filename);
30 PCHECK(r.has_value()) << "Failed to read " << filename << " to string";
31 return r.value();
32}
33
34std::optional<std::string> MaybeReadFileToString(
35 const std::string_view filename) {
36 std::string r;
Austin Schuhcb108412019-10-13 16:09:54 -070037 ScopedFD fd(open(::std::string(filename).c_str(), O_RDONLY));
payton.rehlf8cb3932023-06-13 11:20:44 -070038 if (fd.get() == -1) {
39 PLOG(ERROR) << "Failed to open " << filename;
40 return std::nullopt;
41 }
Brian Silverman61175fb2016-03-13 15:35:56 -040042 while (true) {
43 char buffer[1024];
44 const ssize_t result = read(fd.get(), buffer, sizeof(buffer));
payton.rehlf8cb3932023-06-13 11:20:44 -070045 if (result < 0) {
46 PLOG(ERROR) << "Failed to read from " << filename;
47 return std::nullopt;
48 }
Alex Perrycb7da4b2019-08-28 19:35:56 -070049 if (result == 0) {
Brian Silverman61175fb2016-03-13 15:35:56 -040050 break;
51 }
52 r.append(buffer, result);
53 }
54 return r;
55}
56
Adam Snaider96a0f4b2023-05-18 20:41:19 -070057std::vector<uint8_t> ReadFileToVecOrDie(const std::string_view filename) {
58 std::vector<uint8_t> r;
59 ScopedFD fd(open(::std::string(filename).c_str(), O_RDONLY));
60 PCHECK(fd.get() != -1) << ": opening " << filename;
61 while (true) {
62 uint8_t buffer[1024];
63 const ssize_t result = read(fd.get(), buffer, sizeof(buffer));
64 PCHECK(result >= 0) << ": reading from " << filename;
65 if (result == 0) {
66 break;
67 }
68 std::copy(buffer, buffer + result, std::back_inserter(r));
69 }
70 return r;
71}
72
James Kuszmaul3ae42262019-11-08 12:33:41 -080073void WriteStringToFileOrDie(const std::string_view filename,
Austin Schuhe3fc0532021-02-07 22:14:22 -080074 const std::string_view contents,
75 mode_t permissions) {
James Kuszmaulfd43f4e2022-12-16 15:19:35 -080076 FileWriter writer(filename, permissions);
77 writer.WriteBytesOrDie(
78 {reinterpret_cast<const uint8_t *>(contents.data()), contents.size()});
Alex Perrycb7da4b2019-08-28 19:35:56 -070079}
80
Brian Silvermana9f2ec92020-10-06 18:00:53 -070081bool MkdirPIfSpace(std::string_view path, mode_t mode) {
Austin Schuhfccb2d02020-01-26 16:11:19 -080082 auto last_slash_pos = path.find_last_of("/");
83
84 std::string folder(last_slash_pos == std::string_view::npos
85 ? std::string_view("")
86 : path.substr(0, last_slash_pos));
Brian Silvermana9f2ec92020-10-06 18:00:53 -070087 if (folder.empty()) {
88 return true;
89 }
90 if (!MkdirPIfSpace(folder, mode)) {
91 return false;
92 }
Austin Schuhfccb2d02020-01-26 16:11:19 -080093 const int result = mkdir(folder.c_str(), mode);
94 if (result == -1 && errno == EEXIST) {
95 VLOG(2) << folder << " already exists";
Brian Silvermana9f2ec92020-10-06 18:00:53 -070096 return true;
97 } else if (result == -1 && errno == ENOSPC) {
98 VLOG(2) << "Out of space";
99 return false;
Austin Schuhfccb2d02020-01-26 16:11:19 -0800100 } else {
101 VLOG(1) << "Created " << folder;
102 }
103 PCHECK(result == 0) << ": Error creating " << folder;
Brian Silvermana9f2ec92020-10-06 18:00:53 -0700104 return true;
Austin Schuhfccb2d02020-01-26 16:11:19 -0800105}
106
James Kuszmaulf8178092020-05-10 18:46:45 -0700107bool PathExists(std::string_view path) {
108 struct stat buffer;
109 return stat(path.data(), &buffer) == 0;
110}
111
Austin Schuhe991fe22020-11-18 16:53:39 -0800112void UnlinkRecursive(std::string_view path) {
113 FTS *ftsp = NULL;
114 FTSENT *curr;
115
116 // Cast needed (in C) because fts_open() takes a "char * const *", instead
117 // of a "const char * const *", which is only allowed in C++. fts_open()
118 // does not modify the argument.
119 std::string p(path);
120 char *files[] = {const_cast<char *>(p.c_str()), NULL};
121
122 // FTS_NOCHDIR - Avoid changing cwd, which could cause unexpected behavior
123 // in multithreaded programs
124 // FTS_PHYSICAL - Don't follow symlinks. Prevents deletion of files outside
125 // of the specified directory
126 // FTS_XDEV - Don't cross filesystem boundaries
127 ftsp = fts_open(files, FTS_NOCHDIR | FTS_PHYSICAL | FTS_XDEV, NULL);
128 if (!ftsp) {
129 return;
130 }
131
132 while ((curr = fts_read(ftsp))) {
Austin Schuhe2df81b2021-08-16 10:52:27 -0700133#if __has_feature(memory_sanitizer)
134 // fts_read doesn't have propper msan interceptors. Unpoison it ourselves.
135 if (curr) {
136 __msan_unpoison(curr, sizeof(*curr));
137 __msan_unpoison_string(curr->fts_accpath);
138 __msan_unpoison_string(curr->fts_path);
139 __msan_unpoison_string(curr->fts_name);
140 }
141#endif
Austin Schuhe991fe22020-11-18 16:53:39 -0800142 switch (curr->fts_info) {
143 case FTS_NS:
144 case FTS_DNR:
145 case FTS_ERR:
146 LOG(WARNING) << "Can't read " << curr->fts_accpath;
147 break;
148
149 case FTS_DC:
150 case FTS_DOT:
151 case FTS_NSOK:
152 // Not reached unless FTS_LOGICAL, FTS_SEEDOT, or FTS_NOSTAT were
153 // passed to fts_open()
154 break;
155
156 case FTS_D:
157 // Do nothing. Need depth-first search, so directories are deleted
158 // in FTS_DP
159 break;
160
161 case FTS_DP:
162 case FTS_F:
163 case FTS_SL:
164 case FTS_SLNONE:
165 case FTS_DEFAULT:
166 VLOG(1) << "Removing " << curr->fts_path;
167 if (remove(curr->fts_accpath) < 0) {
168 LOG(WARNING) << curr->fts_path
169 << ": Failed to remove: " << strerror(curr->fts_errno);
170 }
171 break;
172 }
173 }
174
175 if (ftsp) {
176 fts_close(ftsp);
177 }
178}
179
Austin Schuhe4d1a682021-10-01 15:04:50 -0700180std::shared_ptr<absl::Span<uint8_t>> MMapFile(const std::string &path,
181 FileOptions options) {
182 int fd =
183 open(path.c_str(), options == FileOptions::kReadable ? O_RDONLY : O_RDWR);
davidjevans8b9b52f2021-09-17 08:57:30 -0700184 PCHECK(fd != -1) << "Unable to open file " << path;
185 struct stat sb;
186 PCHECK(fstat(fd, &sb) != -1) << ": Unable to get file size of " << path;
Austin Schuhe4d1a682021-10-01 15:04:50 -0700187 uint8_t *start = reinterpret_cast<uint8_t *>(mmap(
188 NULL, sb.st_size,
189 options == FileOptions::kReadable ? PROT_READ : (PROT_READ | PROT_WRITE),
190 MAP_SHARED, fd, 0));
davidjevans8b9b52f2021-09-17 08:57:30 -0700191 CHECK(start != MAP_FAILED) << ": Unable to open mapping to file " << path;
192 std::shared_ptr<absl::Span<uint8_t>> span =
193 std::shared_ptr<absl::Span<uint8_t>>(
194 new absl::Span<uint8_t>(start, sb.st_size),
195 [](absl::Span<uint8_t> *span) {
Austin Schuhe4d1a682021-10-01 15:04:50 -0700196 PCHECK(msync(span->data(), span->size(), MS_SYNC) == 0)
197 << ": Failed to flush data before unmapping.";
davidjevans8b9b52f2021-09-17 08:57:30 -0700198 PCHECK(munmap(span->data(), span->size()) != -1);
199 delete span;
200 });
201 close(fd);
202 return span;
203}
204
Pallavi Madhukar1d391462024-05-13 18:46:48 -0700205FileReader::FileReader(std::string_view filename,
206 FileReaderErrorType error_type)
James Kuszmaul5a88d412023-01-27 15:55:55 -0800207 : file_(open(::std::string(filename).c_str(), O_RDONLY)) {
Pallavi Madhukar1d391462024-05-13 18:46:48 -0700208 if (!is_open()) {
209 PLOG_IF(FATAL, error_type == FileReaderErrorType::kFatal)
210 << ": opening " << filename;
211 PLOG(ERROR) << "opening " << filename;
212 }
James Kuszmaul5a88d412023-01-27 15:55:55 -0800213}
214
Austin Schuh73ff6412023-09-01 14:42:24 -0700215std::optional<absl::Span<char>> FileReader::ReadContents(
216 absl::Span<char> buffer) {
James Kuszmaul5a88d412023-01-27 15:55:55 -0800217 PCHECK(0 == lseek(file_.get(), 0, SEEK_SET));
218 const ssize_t result = read(file_.get(), buffer.data(), buffer.size());
Austin Schuh73ff6412023-09-01 14:42:24 -0700219 if (result < 0) {
220 // Read timeout for an i2c request returns this.
221 if (errno == EREMOTEIO) {
222 return std::nullopt;
223 }
224 }
225
James Kuszmaul5a88d412023-01-27 15:55:55 -0800226 PCHECK(result >= 0);
Austin Schuh73ff6412023-09-01 14:42:24 -0700227 return absl::Span<char>{buffer.data(), static_cast<size_t>(result)};
James Kuszmaul5a88d412023-01-27 15:55:55 -0800228}
229
James Kuszmaulfd43f4e2022-12-16 15:19:35 -0800230FileWriter::FileWriter(std::string_view filename, mode_t permissions)
231 : file_(open(::std::string(filename).c_str(), O_WRONLY | O_CREAT | O_TRUNC,
232 permissions)) {
233 PCHECK(file_.get() != -1) << ": opening " << filename;
234}
235
James Kuszmaul5a88d412023-01-27 15:55:55 -0800236// absl::SimpleAtoi doesn't interpret a leading 0x as hex, which we need here.
237// Instead, we use the flatbufers API, which unfortunately relies on NUL
238// termination.
Austin Schuh73ff6412023-09-01 14:42:24 -0700239std::optional<int32_t> FileReader::ReadInt32() {
James Kuszmaul5a88d412023-01-27 15:55:55 -0800240 // Maximum characters for a 32-bit integer, +1 for the NUL.
241 // Hex is the same size with the leading 0x.
242 std::array<char, 11> buffer;
243 int32_t result;
Austin Schuh73ff6412023-09-01 14:42:24 -0700244 const std::optional<absl::Span<char>> string_span =
James Kuszmaul5a88d412023-01-27 15:55:55 -0800245 ReadContents(absl::Span<char>(buffer.data(), buffer.size())
246 .subspan(0, buffer.size() - 1));
Austin Schuh73ff6412023-09-01 14:42:24 -0700247 if (!string_span.has_value()) {
248 return std::nullopt;
249 }
250
James Kuszmaul5a88d412023-01-27 15:55:55 -0800251 // Verify we found the newline.
Austin Schuh73ff6412023-09-01 14:42:24 -0700252 CHECK_EQ(buffer[string_span->size() - 1], '\n');
James Kuszmaul5a88d412023-01-27 15:55:55 -0800253 // Truncate the newline.
Austin Schuh73ff6412023-09-01 14:42:24 -0700254 buffer[string_span->size() - 1] = '\0';
James Kuszmaul5a88d412023-01-27 15:55:55 -0800255 CHECK(flatbuffers::StringToNumber(buffer.data(), &result))
256 << ": Error parsing string to integer: "
Austin Schuh73ff6412023-09-01 14:42:24 -0700257 << std::string_view(string_span->data(), string_span->size());
James Kuszmaul5a88d412023-01-27 15:55:55 -0800258
259 return result;
260}
261
James Kuszmaulfd43f4e2022-12-16 15:19:35 -0800262FileWriter::WriteResult FileWriter::WriteBytes(
263 absl::Span<const uint8_t> bytes) {
264 size_t size_written = 0;
265 while (size_written != bytes.size()) {
266 const ssize_t result = write(file_.get(), bytes.data() + size_written,
267 bytes.size() - size_written);
268 if (result < 0) {
269 return {size_written, static_cast<int>(result)};
270 }
271 // Not really supposed to happen unless writing zero bytes without an error.
272 // See, e.g.,
273 // https://stackoverflow.com/questions/2176443/is-a-return-value-of-0-from-write2-in-c-an-error
274 if (result == 0) {
275 return {size_written, static_cast<int>(result)};
276 }
277
278 size_written += result;
279 }
280 return {size_written, static_cast<int>(size_written)};
281}
282
283FileWriter::WriteResult FileWriter::WriteBytes(std::string_view bytes) {
284 return WriteBytes(absl::Span<const uint8_t>{
285 reinterpret_cast<const uint8_t *>(bytes.data()), bytes.size()});
286}
287
288void FileWriter::WriteBytesOrDie(std::string_view bytes) {
289 WriteBytesOrDie(absl::Span<const uint8_t>{
290 reinterpret_cast<const uint8_t *>(bytes.data()), bytes.size()});
291}
292
293void FileWriter::WriteBytesOrDie(absl::Span<const uint8_t> bytes) {
294 PCHECK(bytes.size() == WriteBytes(bytes).bytes_written)
295 << ": Failed to write " << bytes.size() << " bytes.";
296}
297
Stephan Pleinesf63bde82024-01-13 15:59:33 -0800298} // namespace aos::util