blob: 973ab3c425378156c4f3a40eca1d7054cd8845f1 [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
18namespace aos {
19namespace util {
20
payton.rehlf8cb3932023-06-13 11:20:44 -070021std::string ReadFileToStringOrDie(const std::string_view filename) {
22 std::optional<std::string> r = MaybeReadFileToString(filename);
23 PCHECK(r.has_value()) << "Failed to read " << filename << " to string";
24 return r.value();
25}
26
27std::optional<std::string> MaybeReadFileToString(
28 const std::string_view filename) {
29 std::string r;
Austin Schuhcb108412019-10-13 16:09:54 -070030 ScopedFD fd(open(::std::string(filename).c_str(), O_RDONLY));
payton.rehlf8cb3932023-06-13 11:20:44 -070031 if (fd.get() == -1) {
32 PLOG(ERROR) << "Failed to open " << filename;
33 return std::nullopt;
34 }
Brian Silverman61175fb2016-03-13 15:35:56 -040035 while (true) {
36 char buffer[1024];
37 const ssize_t result = read(fd.get(), buffer, sizeof(buffer));
payton.rehlf8cb3932023-06-13 11:20:44 -070038 if (result < 0) {
39 PLOG(ERROR) << "Failed to read from " << filename;
40 return std::nullopt;
41 }
Alex Perrycb7da4b2019-08-28 19:35:56 -070042 if (result == 0) {
Brian Silverman61175fb2016-03-13 15:35:56 -040043 break;
44 }
45 r.append(buffer, result);
46 }
47 return r;
48}
49
Adam Snaider96a0f4b2023-05-18 20:41:19 -070050std::vector<uint8_t> ReadFileToVecOrDie(const std::string_view filename) {
51 std::vector<uint8_t> r;
52 ScopedFD fd(open(::std::string(filename).c_str(), O_RDONLY));
53 PCHECK(fd.get() != -1) << ": opening " << filename;
54 while (true) {
55 uint8_t buffer[1024];
56 const ssize_t result = read(fd.get(), buffer, sizeof(buffer));
57 PCHECK(result >= 0) << ": reading from " << filename;
58 if (result == 0) {
59 break;
60 }
61 std::copy(buffer, buffer + result, std::back_inserter(r));
62 }
63 return r;
64}
65
James Kuszmaul3ae42262019-11-08 12:33:41 -080066void WriteStringToFileOrDie(const std::string_view filename,
Austin Schuhe3fc0532021-02-07 22:14:22 -080067 const std::string_view contents,
68 mode_t permissions) {
James Kuszmaulfd43f4e2022-12-16 15:19:35 -080069 FileWriter writer(filename, permissions);
70 writer.WriteBytesOrDie(
71 {reinterpret_cast<const uint8_t *>(contents.data()), contents.size()});
Alex Perrycb7da4b2019-08-28 19:35:56 -070072}
73
Brian Silvermana9f2ec92020-10-06 18:00:53 -070074bool MkdirPIfSpace(std::string_view path, mode_t mode) {
Austin Schuhfccb2d02020-01-26 16:11:19 -080075 auto last_slash_pos = path.find_last_of("/");
76
77 std::string folder(last_slash_pos == std::string_view::npos
78 ? std::string_view("")
79 : path.substr(0, last_slash_pos));
Brian Silvermana9f2ec92020-10-06 18:00:53 -070080 if (folder.empty()) {
81 return true;
82 }
83 if (!MkdirPIfSpace(folder, mode)) {
84 return false;
85 }
Austin Schuhfccb2d02020-01-26 16:11:19 -080086 const int result = mkdir(folder.c_str(), mode);
87 if (result == -1 && errno == EEXIST) {
88 VLOG(2) << folder << " already exists";
Brian Silvermana9f2ec92020-10-06 18:00:53 -070089 return true;
90 } else if (result == -1 && errno == ENOSPC) {
91 VLOG(2) << "Out of space";
92 return false;
Austin Schuhfccb2d02020-01-26 16:11:19 -080093 } else {
94 VLOG(1) << "Created " << folder;
95 }
96 PCHECK(result == 0) << ": Error creating " << folder;
Brian Silvermana9f2ec92020-10-06 18:00:53 -070097 return true;
Austin Schuhfccb2d02020-01-26 16:11:19 -080098}
99
James Kuszmaulf8178092020-05-10 18:46:45 -0700100bool PathExists(std::string_view path) {
101 struct stat buffer;
102 return stat(path.data(), &buffer) == 0;
103}
104
Austin Schuhe991fe22020-11-18 16:53:39 -0800105void UnlinkRecursive(std::string_view path) {
106 FTS *ftsp = NULL;
107 FTSENT *curr;
108
109 // Cast needed (in C) because fts_open() takes a "char * const *", instead
110 // of a "const char * const *", which is only allowed in C++. fts_open()
111 // does not modify the argument.
112 std::string p(path);
113 char *files[] = {const_cast<char *>(p.c_str()), NULL};
114
115 // FTS_NOCHDIR - Avoid changing cwd, which could cause unexpected behavior
116 // in multithreaded programs
117 // FTS_PHYSICAL - Don't follow symlinks. Prevents deletion of files outside
118 // of the specified directory
119 // FTS_XDEV - Don't cross filesystem boundaries
120 ftsp = fts_open(files, FTS_NOCHDIR | FTS_PHYSICAL | FTS_XDEV, NULL);
121 if (!ftsp) {
122 return;
123 }
124
125 while ((curr = fts_read(ftsp))) {
Austin Schuhe2df81b2021-08-16 10:52:27 -0700126#if __has_feature(memory_sanitizer)
127 // fts_read doesn't have propper msan interceptors. Unpoison it ourselves.
128 if (curr) {
129 __msan_unpoison(curr, sizeof(*curr));
130 __msan_unpoison_string(curr->fts_accpath);
131 __msan_unpoison_string(curr->fts_path);
132 __msan_unpoison_string(curr->fts_name);
133 }
134#endif
Austin Schuhe991fe22020-11-18 16:53:39 -0800135 switch (curr->fts_info) {
136 case FTS_NS:
137 case FTS_DNR:
138 case FTS_ERR:
139 LOG(WARNING) << "Can't read " << curr->fts_accpath;
140 break;
141
142 case FTS_DC:
143 case FTS_DOT:
144 case FTS_NSOK:
145 // Not reached unless FTS_LOGICAL, FTS_SEEDOT, or FTS_NOSTAT were
146 // passed to fts_open()
147 break;
148
149 case FTS_D:
150 // Do nothing. Need depth-first search, so directories are deleted
151 // in FTS_DP
152 break;
153
154 case FTS_DP:
155 case FTS_F:
156 case FTS_SL:
157 case FTS_SLNONE:
158 case FTS_DEFAULT:
159 VLOG(1) << "Removing " << curr->fts_path;
160 if (remove(curr->fts_accpath) < 0) {
161 LOG(WARNING) << curr->fts_path
162 << ": Failed to remove: " << strerror(curr->fts_errno);
163 }
164 break;
165 }
166 }
167
168 if (ftsp) {
169 fts_close(ftsp);
170 }
171}
172
Austin Schuhe4d1a682021-10-01 15:04:50 -0700173std::shared_ptr<absl::Span<uint8_t>> MMapFile(const std::string &path,
174 FileOptions options) {
175 int fd =
176 open(path.c_str(), options == FileOptions::kReadable ? O_RDONLY : O_RDWR);
davidjevans8b9b52f2021-09-17 08:57:30 -0700177 PCHECK(fd != -1) << "Unable to open file " << path;
178 struct stat sb;
179 PCHECK(fstat(fd, &sb) != -1) << ": Unable to get file size of " << path;
Austin Schuhe4d1a682021-10-01 15:04:50 -0700180 uint8_t *start = reinterpret_cast<uint8_t *>(mmap(
181 NULL, sb.st_size,
182 options == FileOptions::kReadable ? PROT_READ : (PROT_READ | PROT_WRITE),
183 MAP_SHARED, fd, 0));
davidjevans8b9b52f2021-09-17 08:57:30 -0700184 CHECK(start != MAP_FAILED) << ": Unable to open mapping to file " << path;
185 std::shared_ptr<absl::Span<uint8_t>> span =
186 std::shared_ptr<absl::Span<uint8_t>>(
187 new absl::Span<uint8_t>(start, sb.st_size),
188 [](absl::Span<uint8_t> *span) {
Austin Schuhe4d1a682021-10-01 15:04:50 -0700189 PCHECK(msync(span->data(), span->size(), MS_SYNC) == 0)
190 << ": Failed to flush data before unmapping.";
davidjevans8b9b52f2021-09-17 08:57:30 -0700191 PCHECK(munmap(span->data(), span->size()) != -1);
192 delete span;
193 });
194 close(fd);
195 return span;
196}
197
James Kuszmaul5a88d412023-01-27 15:55:55 -0800198FileReader::FileReader(std::string_view filename)
199 : file_(open(::std::string(filename).c_str(), O_RDONLY)) {
200 PCHECK(file_.get() != -1) << ": opening " << filename;
201}
202
Austin Schuh73ff6412023-09-01 14:42:24 -0700203std::optional<absl::Span<char>> FileReader::ReadContents(
204 absl::Span<char> buffer) {
James Kuszmaul5a88d412023-01-27 15:55:55 -0800205 PCHECK(0 == lseek(file_.get(), 0, SEEK_SET));
206 const ssize_t result = read(file_.get(), buffer.data(), buffer.size());
Austin Schuh73ff6412023-09-01 14:42:24 -0700207 if (result < 0) {
208 // Read timeout for an i2c request returns this.
209 if (errno == EREMOTEIO) {
210 return std::nullopt;
211 }
212 }
213
James Kuszmaul5a88d412023-01-27 15:55:55 -0800214 PCHECK(result >= 0);
Austin Schuh73ff6412023-09-01 14:42:24 -0700215 return absl::Span<char>{buffer.data(), static_cast<size_t>(result)};
James Kuszmaul5a88d412023-01-27 15:55:55 -0800216}
217
James Kuszmaulfd43f4e2022-12-16 15:19:35 -0800218FileWriter::FileWriter(std::string_view filename, mode_t permissions)
219 : file_(open(::std::string(filename).c_str(), O_WRONLY | O_CREAT | O_TRUNC,
220 permissions)) {
221 PCHECK(file_.get() != -1) << ": opening " << filename;
222}
223
James Kuszmaul5a88d412023-01-27 15:55:55 -0800224// absl::SimpleAtoi doesn't interpret a leading 0x as hex, which we need here.
225// Instead, we use the flatbufers API, which unfortunately relies on NUL
226// termination.
Austin Schuh73ff6412023-09-01 14:42:24 -0700227std::optional<int32_t> FileReader::ReadInt32() {
James Kuszmaul5a88d412023-01-27 15:55:55 -0800228 // Maximum characters for a 32-bit integer, +1 for the NUL.
229 // Hex is the same size with the leading 0x.
230 std::array<char, 11> buffer;
231 int32_t result;
Austin Schuh73ff6412023-09-01 14:42:24 -0700232 const std::optional<absl::Span<char>> string_span =
James Kuszmaul5a88d412023-01-27 15:55:55 -0800233 ReadContents(absl::Span<char>(buffer.data(), buffer.size())
234 .subspan(0, buffer.size() - 1));
Austin Schuh73ff6412023-09-01 14:42:24 -0700235 if (!string_span.has_value()) {
236 return std::nullopt;
237 }
238
James Kuszmaul5a88d412023-01-27 15:55:55 -0800239 // Verify we found the newline.
Austin Schuh73ff6412023-09-01 14:42:24 -0700240 CHECK_EQ(buffer[string_span->size() - 1], '\n');
James Kuszmaul5a88d412023-01-27 15:55:55 -0800241 // Truncate the newline.
Austin Schuh73ff6412023-09-01 14:42:24 -0700242 buffer[string_span->size() - 1] = '\0';
James Kuszmaul5a88d412023-01-27 15:55:55 -0800243 CHECK(flatbuffers::StringToNumber(buffer.data(), &result))
244 << ": Error parsing string to integer: "
Austin Schuh73ff6412023-09-01 14:42:24 -0700245 << std::string_view(string_span->data(), string_span->size());
James Kuszmaul5a88d412023-01-27 15:55:55 -0800246
247 return result;
248}
249
James Kuszmaulfd43f4e2022-12-16 15:19:35 -0800250FileWriter::WriteResult FileWriter::WriteBytes(
251 absl::Span<const uint8_t> bytes) {
252 size_t size_written = 0;
253 while (size_written != bytes.size()) {
254 const ssize_t result = write(file_.get(), bytes.data() + size_written,
255 bytes.size() - size_written);
256 if (result < 0) {
257 return {size_written, static_cast<int>(result)};
258 }
259 // Not really supposed to happen unless writing zero bytes without an error.
260 // See, e.g.,
261 // https://stackoverflow.com/questions/2176443/is-a-return-value-of-0-from-write2-in-c-an-error
262 if (result == 0) {
263 return {size_written, static_cast<int>(result)};
264 }
265
266 size_written += result;
267 }
268 return {size_written, static_cast<int>(size_written)};
269}
270
271FileWriter::WriteResult FileWriter::WriteBytes(std::string_view bytes) {
272 return WriteBytes(absl::Span<const uint8_t>{
273 reinterpret_cast<const uint8_t *>(bytes.data()), bytes.size()});
274}
275
276void FileWriter::WriteBytesOrDie(std::string_view bytes) {
277 WriteBytesOrDie(absl::Span<const uint8_t>{
278 reinterpret_cast<const uint8_t *>(bytes.data()), bytes.size()});
279}
280
281void FileWriter::WriteBytesOrDie(absl::Span<const uint8_t> bytes) {
282 PCHECK(bytes.size() == WriteBytes(bytes).bytes_written)
283 << ": Failed to write " << bytes.size() << " bytes.";
284}
285
Brian Silverman61175fb2016-03-13 15:35:56 -0400286} // namespace util
287} // namespace aos