blob: ef958c6f7cffdea1ff39aa5bc8f95abc66713103 [file] [log] [blame]
John Park33858a32018-09-28 23:05:48 -07001#include "aos/util/file.h"
Brian Silverman61175fb2016-03-13 15:35:56 -04002
3#include <fcntl.h>
Austin Schuhe991fe22020-11-18 16:53:39 -08004#include <fts.h>
davidjevans8b9b52f2021-09-17 08:57:30 -07005#include <sys/mman.h>
Austin Schuhfccb2d02020-01-26 16:11:19 -08006#include <sys/stat.h>
7#include <sys/types.h>
Brian Silverman61175fb2016-03-13 15:35:56 -04008#include <unistd.h>
9
payton.rehlf8cb3932023-06-13 11:20:44 -070010#include <optional>
James Kuszmaul3ae42262019-11-08 12:33:41 -080011#include <string_view>
Austin Schuhe2df81b2021-08-16 10:52:27 -070012#if __has_feature(memory_sanitizer)
13#include <sanitizer/msan_interface.h>
14#endif
James Kuszmaul3ae42262019-11-08 12:33:41 -080015
John Park33858a32018-09-28 23:05:48 -070016#include "aos/scoped/scoped_fd.h"
Brian Silverman61175fb2016-03-13 15:35:56 -040017
Stephan Pleinesf63bde82024-01-13 15:59:33 -080018namespace aos::util {
Brian Silverman61175fb2016-03-13 15:35:56 -040019
payton.rehlf8cb3932023-06-13 11:20:44 -070020std::string ReadFileToStringOrDie(const std::string_view filename) {
21 std::optional<std::string> r = MaybeReadFileToString(filename);
22 PCHECK(r.has_value()) << "Failed to read " << filename << " to string";
23 return r.value();
24}
25
26std::optional<std::string> MaybeReadFileToString(
27 const std::string_view filename) {
28 std::string r;
Austin Schuhcb108412019-10-13 16:09:54 -070029 ScopedFD fd(open(::std::string(filename).c_str(), O_RDONLY));
payton.rehlf8cb3932023-06-13 11:20:44 -070030 if (fd.get() == -1) {
31 PLOG(ERROR) << "Failed to open " << filename;
32 return std::nullopt;
33 }
Brian Silverman61175fb2016-03-13 15:35:56 -040034 while (true) {
35 char buffer[1024];
36 const ssize_t result = read(fd.get(), buffer, sizeof(buffer));
payton.rehlf8cb3932023-06-13 11:20:44 -070037 if (result < 0) {
38 PLOG(ERROR) << "Failed to read from " << filename;
39 return std::nullopt;
40 }
Alex Perrycb7da4b2019-08-28 19:35:56 -070041 if (result == 0) {
Brian Silverman61175fb2016-03-13 15:35:56 -040042 break;
43 }
44 r.append(buffer, result);
45 }
46 return r;
47}
48
Adam Snaider96a0f4b2023-05-18 20:41:19 -070049std::vector<uint8_t> ReadFileToVecOrDie(const std::string_view filename) {
50 std::vector<uint8_t> r;
51 ScopedFD fd(open(::std::string(filename).c_str(), O_RDONLY));
52 PCHECK(fd.get() != -1) << ": opening " << filename;
53 while (true) {
54 uint8_t buffer[1024];
55 const ssize_t result = read(fd.get(), buffer, sizeof(buffer));
56 PCHECK(result >= 0) << ": reading from " << filename;
57 if (result == 0) {
58 break;
59 }
60 std::copy(buffer, buffer + result, std::back_inserter(r));
61 }
62 return r;
63}
64
James Kuszmaul3ae42262019-11-08 12:33:41 -080065void WriteStringToFileOrDie(const std::string_view filename,
Austin Schuhe3fc0532021-02-07 22:14:22 -080066 const std::string_view contents,
67 mode_t permissions) {
James Kuszmaulfd43f4e2022-12-16 15:19:35 -080068 FileWriter writer(filename, permissions);
69 writer.WriteBytesOrDie(
70 {reinterpret_cast<const uint8_t *>(contents.data()), contents.size()});
Alex Perrycb7da4b2019-08-28 19:35:56 -070071}
72
Brian Silvermana9f2ec92020-10-06 18:00:53 -070073bool MkdirPIfSpace(std::string_view path, mode_t mode) {
Austin Schuhfccb2d02020-01-26 16:11:19 -080074 auto last_slash_pos = path.find_last_of("/");
75
76 std::string folder(last_slash_pos == std::string_view::npos
77 ? std::string_view("")
78 : path.substr(0, last_slash_pos));
Brian Silvermana9f2ec92020-10-06 18:00:53 -070079 if (folder.empty()) {
80 return true;
81 }
82 if (!MkdirPIfSpace(folder, mode)) {
83 return false;
84 }
Austin Schuhfccb2d02020-01-26 16:11:19 -080085 const int result = mkdir(folder.c_str(), mode);
86 if (result == -1 && errno == EEXIST) {
87 VLOG(2) << folder << " already exists";
Brian Silvermana9f2ec92020-10-06 18:00:53 -070088 return true;
89 } else if (result == -1 && errno == ENOSPC) {
90 VLOG(2) << "Out of space";
91 return false;
Austin Schuhfccb2d02020-01-26 16:11:19 -080092 } else {
93 VLOG(1) << "Created " << folder;
94 }
95 PCHECK(result == 0) << ": Error creating " << folder;
Brian Silvermana9f2ec92020-10-06 18:00:53 -070096 return true;
Austin Schuhfccb2d02020-01-26 16:11:19 -080097}
98
James Kuszmaulf8178092020-05-10 18:46:45 -070099bool PathExists(std::string_view path) {
100 struct stat buffer;
101 return stat(path.data(), &buffer) == 0;
102}
103
Austin Schuhe991fe22020-11-18 16:53:39 -0800104void UnlinkRecursive(std::string_view path) {
105 FTS *ftsp = NULL;
106 FTSENT *curr;
107
108 // Cast needed (in C) because fts_open() takes a "char * const *", instead
109 // of a "const char * const *", which is only allowed in C++. fts_open()
110 // does not modify the argument.
111 std::string p(path);
112 char *files[] = {const_cast<char *>(p.c_str()), NULL};
113
114 // FTS_NOCHDIR - Avoid changing cwd, which could cause unexpected behavior
115 // in multithreaded programs
116 // FTS_PHYSICAL - Don't follow symlinks. Prevents deletion of files outside
117 // of the specified directory
118 // FTS_XDEV - Don't cross filesystem boundaries
119 ftsp = fts_open(files, FTS_NOCHDIR | FTS_PHYSICAL | FTS_XDEV, NULL);
120 if (!ftsp) {
121 return;
122 }
123
124 while ((curr = fts_read(ftsp))) {
Austin Schuhe2df81b2021-08-16 10:52:27 -0700125#if __has_feature(memory_sanitizer)
126 // fts_read doesn't have propper msan interceptors. Unpoison it ourselves.
127 if (curr) {
128 __msan_unpoison(curr, sizeof(*curr));
129 __msan_unpoison_string(curr->fts_accpath);
130 __msan_unpoison_string(curr->fts_path);
131 __msan_unpoison_string(curr->fts_name);
132 }
133#endif
Austin Schuhe991fe22020-11-18 16:53:39 -0800134 switch (curr->fts_info) {
135 case FTS_NS:
136 case FTS_DNR:
137 case FTS_ERR:
138 LOG(WARNING) << "Can't read " << curr->fts_accpath;
139 break;
140
141 case FTS_DC:
142 case FTS_DOT:
143 case FTS_NSOK:
144 // Not reached unless FTS_LOGICAL, FTS_SEEDOT, or FTS_NOSTAT were
145 // passed to fts_open()
146 break;
147
148 case FTS_D:
149 // Do nothing. Need depth-first search, so directories are deleted
150 // in FTS_DP
151 break;
152
153 case FTS_DP:
154 case FTS_F:
155 case FTS_SL:
156 case FTS_SLNONE:
157 case FTS_DEFAULT:
158 VLOG(1) << "Removing " << curr->fts_path;
159 if (remove(curr->fts_accpath) < 0) {
160 LOG(WARNING) << curr->fts_path
161 << ": Failed to remove: " << strerror(curr->fts_errno);
162 }
163 break;
164 }
165 }
166
167 if (ftsp) {
168 fts_close(ftsp);
169 }
170}
171
Austin Schuhe4d1a682021-10-01 15:04:50 -0700172std::shared_ptr<absl::Span<uint8_t>> MMapFile(const std::string &path,
173 FileOptions options) {
174 int fd =
175 open(path.c_str(), options == FileOptions::kReadable ? O_RDONLY : O_RDWR);
davidjevans8b9b52f2021-09-17 08:57:30 -0700176 PCHECK(fd != -1) << "Unable to open file " << path;
177 struct stat sb;
178 PCHECK(fstat(fd, &sb) != -1) << ": Unable to get file size of " << path;
Austin Schuhe4d1a682021-10-01 15:04:50 -0700179 uint8_t *start = reinterpret_cast<uint8_t *>(mmap(
180 NULL, sb.st_size,
181 options == FileOptions::kReadable ? PROT_READ : (PROT_READ | PROT_WRITE),
182 MAP_SHARED, fd, 0));
davidjevans8b9b52f2021-09-17 08:57:30 -0700183 CHECK(start != MAP_FAILED) << ": Unable to open mapping to file " << path;
184 std::shared_ptr<absl::Span<uint8_t>> span =
185 std::shared_ptr<absl::Span<uint8_t>>(
186 new absl::Span<uint8_t>(start, sb.st_size),
187 [](absl::Span<uint8_t> *span) {
Austin Schuhe4d1a682021-10-01 15:04:50 -0700188 PCHECK(msync(span->data(), span->size(), MS_SYNC) == 0)
189 << ": Failed to flush data before unmapping.";
davidjevans8b9b52f2021-09-17 08:57:30 -0700190 PCHECK(munmap(span->data(), span->size()) != -1);
191 delete span;
192 });
193 close(fd);
194 return span;
195}
196
James Kuszmaul5a88d412023-01-27 15:55:55 -0800197FileReader::FileReader(std::string_view filename)
198 : file_(open(::std::string(filename).c_str(), O_RDONLY)) {
199 PCHECK(file_.get() != -1) << ": opening " << filename;
200}
201
Austin Schuh73ff6412023-09-01 14:42:24 -0700202std::optional<absl::Span<char>> FileReader::ReadContents(
203 absl::Span<char> buffer) {
James Kuszmaul5a88d412023-01-27 15:55:55 -0800204 PCHECK(0 == lseek(file_.get(), 0, SEEK_SET));
205 const ssize_t result = read(file_.get(), buffer.data(), buffer.size());
Austin Schuh73ff6412023-09-01 14:42:24 -0700206 if (result < 0) {
207 // Read timeout for an i2c request returns this.
208 if (errno == EREMOTEIO) {
209 return std::nullopt;
210 }
211 }
212
James Kuszmaul5a88d412023-01-27 15:55:55 -0800213 PCHECK(result >= 0);
Austin Schuh73ff6412023-09-01 14:42:24 -0700214 return absl::Span<char>{buffer.data(), static_cast<size_t>(result)};
James Kuszmaul5a88d412023-01-27 15:55:55 -0800215}
216
James Kuszmaulfd43f4e2022-12-16 15:19:35 -0800217FileWriter::FileWriter(std::string_view filename, mode_t permissions)
218 : file_(open(::std::string(filename).c_str(), O_WRONLY | O_CREAT | O_TRUNC,
219 permissions)) {
220 PCHECK(file_.get() != -1) << ": opening " << filename;
221}
222
James Kuszmaul5a88d412023-01-27 15:55:55 -0800223// absl::SimpleAtoi doesn't interpret a leading 0x as hex, which we need here.
224// Instead, we use the flatbufers API, which unfortunately relies on NUL
225// termination.
Austin Schuh73ff6412023-09-01 14:42:24 -0700226std::optional<int32_t> FileReader::ReadInt32() {
James Kuszmaul5a88d412023-01-27 15:55:55 -0800227 // Maximum characters for a 32-bit integer, +1 for the NUL.
228 // Hex is the same size with the leading 0x.
229 std::array<char, 11> buffer;
230 int32_t result;
Austin Schuh73ff6412023-09-01 14:42:24 -0700231 const std::optional<absl::Span<char>> string_span =
James Kuszmaul5a88d412023-01-27 15:55:55 -0800232 ReadContents(absl::Span<char>(buffer.data(), buffer.size())
233 .subspan(0, buffer.size() - 1));
Austin Schuh73ff6412023-09-01 14:42:24 -0700234 if (!string_span.has_value()) {
235 return std::nullopt;
236 }
237
James Kuszmaul5a88d412023-01-27 15:55:55 -0800238 // Verify we found the newline.
Austin Schuh73ff6412023-09-01 14:42:24 -0700239 CHECK_EQ(buffer[string_span->size() - 1], '\n');
James Kuszmaul5a88d412023-01-27 15:55:55 -0800240 // Truncate the newline.
Austin Schuh73ff6412023-09-01 14:42:24 -0700241 buffer[string_span->size() - 1] = '\0';
James Kuszmaul5a88d412023-01-27 15:55:55 -0800242 CHECK(flatbuffers::StringToNumber(buffer.data(), &result))
243 << ": Error parsing string to integer: "
Austin Schuh73ff6412023-09-01 14:42:24 -0700244 << std::string_view(string_span->data(), string_span->size());
James Kuszmaul5a88d412023-01-27 15:55:55 -0800245
246 return result;
247}
248
James Kuszmaulfd43f4e2022-12-16 15:19:35 -0800249FileWriter::WriteResult FileWriter::WriteBytes(
250 absl::Span<const uint8_t> bytes) {
251 size_t size_written = 0;
252 while (size_written != bytes.size()) {
253 const ssize_t result = write(file_.get(), bytes.data() + size_written,
254 bytes.size() - size_written);
255 if (result < 0) {
256 return {size_written, static_cast<int>(result)};
257 }
258 // Not really supposed to happen unless writing zero bytes without an error.
259 // See, e.g.,
260 // https://stackoverflow.com/questions/2176443/is-a-return-value-of-0-from-write2-in-c-an-error
261 if (result == 0) {
262 return {size_written, static_cast<int>(result)};
263 }
264
265 size_written += result;
266 }
267 return {size_written, static_cast<int>(size_written)};
268}
269
270FileWriter::WriteResult FileWriter::WriteBytes(std::string_view bytes) {
271 return WriteBytes(absl::Span<const uint8_t>{
272 reinterpret_cast<const uint8_t *>(bytes.data()), bytes.size()});
273}
274
275void FileWriter::WriteBytesOrDie(std::string_view bytes) {
276 WriteBytesOrDie(absl::Span<const uint8_t>{
277 reinterpret_cast<const uint8_t *>(bytes.data()), bytes.size()});
278}
279
280void FileWriter::WriteBytesOrDie(absl::Span<const uint8_t> bytes) {
281 PCHECK(bytes.size() == WriteBytes(bytes).bytes_written)
282 << ": Failed to write " << bytes.size() << " bytes.";
283}
284
Stephan Pleinesf63bde82024-01-13 15:59:33 -0800285} // namespace aos::util