blob: 591539c8e3431b913bfced48288b459f203ee808 [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
James Kuszmaul3ae42262019-11-08 12:33:41 -080050void WriteStringToFileOrDie(const std::string_view filename,
Austin Schuhe3fc0532021-02-07 22:14:22 -080051 const std::string_view contents,
52 mode_t permissions) {
James Kuszmaulfd43f4e2022-12-16 15:19:35 -080053 FileWriter writer(filename, permissions);
54 writer.WriteBytesOrDie(
55 {reinterpret_cast<const uint8_t *>(contents.data()), contents.size()});
Alex Perrycb7da4b2019-08-28 19:35:56 -070056}
57
Brian Silvermana9f2ec92020-10-06 18:00:53 -070058bool MkdirPIfSpace(std::string_view path, mode_t mode) {
Austin Schuhfccb2d02020-01-26 16:11:19 -080059 auto last_slash_pos = path.find_last_of("/");
60
61 std::string folder(last_slash_pos == std::string_view::npos
62 ? std::string_view("")
63 : path.substr(0, last_slash_pos));
Brian Silvermana9f2ec92020-10-06 18:00:53 -070064 if (folder.empty()) {
65 return true;
66 }
67 if (!MkdirPIfSpace(folder, mode)) {
68 return false;
69 }
Austin Schuhfccb2d02020-01-26 16:11:19 -080070 const int result = mkdir(folder.c_str(), mode);
71 if (result == -1 && errno == EEXIST) {
72 VLOG(2) << folder << " already exists";
Brian Silvermana9f2ec92020-10-06 18:00:53 -070073 return true;
74 } else if (result == -1 && errno == ENOSPC) {
75 VLOG(2) << "Out of space";
76 return false;
Austin Schuhfccb2d02020-01-26 16:11:19 -080077 } else {
78 VLOG(1) << "Created " << folder;
79 }
80 PCHECK(result == 0) << ": Error creating " << folder;
Brian Silvermana9f2ec92020-10-06 18:00:53 -070081 return true;
Austin Schuhfccb2d02020-01-26 16:11:19 -080082}
83
James Kuszmaulf8178092020-05-10 18:46:45 -070084bool PathExists(std::string_view path) {
85 struct stat buffer;
86 return stat(path.data(), &buffer) == 0;
87}
88
Austin Schuhe991fe22020-11-18 16:53:39 -080089void UnlinkRecursive(std::string_view path) {
90 FTS *ftsp = NULL;
91 FTSENT *curr;
92
93 // Cast needed (in C) because fts_open() takes a "char * const *", instead
94 // of a "const char * const *", which is only allowed in C++. fts_open()
95 // does not modify the argument.
96 std::string p(path);
97 char *files[] = {const_cast<char *>(p.c_str()), NULL};
98
99 // FTS_NOCHDIR - Avoid changing cwd, which could cause unexpected behavior
100 // in multithreaded programs
101 // FTS_PHYSICAL - Don't follow symlinks. Prevents deletion of files outside
102 // of the specified directory
103 // FTS_XDEV - Don't cross filesystem boundaries
104 ftsp = fts_open(files, FTS_NOCHDIR | FTS_PHYSICAL | FTS_XDEV, NULL);
105 if (!ftsp) {
106 return;
107 }
108
109 while ((curr = fts_read(ftsp))) {
Austin Schuhe2df81b2021-08-16 10:52:27 -0700110#if __has_feature(memory_sanitizer)
111 // fts_read doesn't have propper msan interceptors. Unpoison it ourselves.
112 if (curr) {
113 __msan_unpoison(curr, sizeof(*curr));
114 __msan_unpoison_string(curr->fts_accpath);
115 __msan_unpoison_string(curr->fts_path);
116 __msan_unpoison_string(curr->fts_name);
117 }
118#endif
Austin Schuhe991fe22020-11-18 16:53:39 -0800119 switch (curr->fts_info) {
120 case FTS_NS:
121 case FTS_DNR:
122 case FTS_ERR:
123 LOG(WARNING) << "Can't read " << curr->fts_accpath;
124 break;
125
126 case FTS_DC:
127 case FTS_DOT:
128 case FTS_NSOK:
129 // Not reached unless FTS_LOGICAL, FTS_SEEDOT, or FTS_NOSTAT were
130 // passed to fts_open()
131 break;
132
133 case FTS_D:
134 // Do nothing. Need depth-first search, so directories are deleted
135 // in FTS_DP
136 break;
137
138 case FTS_DP:
139 case FTS_F:
140 case FTS_SL:
141 case FTS_SLNONE:
142 case FTS_DEFAULT:
143 VLOG(1) << "Removing " << curr->fts_path;
144 if (remove(curr->fts_accpath) < 0) {
145 LOG(WARNING) << curr->fts_path
146 << ": Failed to remove: " << strerror(curr->fts_errno);
147 }
148 break;
149 }
150 }
151
152 if (ftsp) {
153 fts_close(ftsp);
154 }
155}
156
Austin Schuhe4d1a682021-10-01 15:04:50 -0700157std::shared_ptr<absl::Span<uint8_t>> MMapFile(const std::string &path,
158 FileOptions options) {
159 int fd =
160 open(path.c_str(), options == FileOptions::kReadable ? O_RDONLY : O_RDWR);
davidjevans8b9b52f2021-09-17 08:57:30 -0700161 PCHECK(fd != -1) << "Unable to open file " << path;
162 struct stat sb;
163 PCHECK(fstat(fd, &sb) != -1) << ": Unable to get file size of " << path;
Austin Schuhe4d1a682021-10-01 15:04:50 -0700164 uint8_t *start = reinterpret_cast<uint8_t *>(mmap(
165 NULL, sb.st_size,
166 options == FileOptions::kReadable ? PROT_READ : (PROT_READ | PROT_WRITE),
167 MAP_SHARED, fd, 0));
davidjevans8b9b52f2021-09-17 08:57:30 -0700168 CHECK(start != MAP_FAILED) << ": Unable to open mapping to file " << path;
169 std::shared_ptr<absl::Span<uint8_t>> span =
170 std::shared_ptr<absl::Span<uint8_t>>(
171 new absl::Span<uint8_t>(start, sb.st_size),
172 [](absl::Span<uint8_t> *span) {
Austin Schuhe4d1a682021-10-01 15:04:50 -0700173 PCHECK(msync(span->data(), span->size(), MS_SYNC) == 0)
174 << ": Failed to flush data before unmapping.";
davidjevans8b9b52f2021-09-17 08:57:30 -0700175 PCHECK(munmap(span->data(), span->size()) != -1);
176 delete span;
177 });
178 close(fd);
179 return span;
180}
181
James Kuszmaul5a88d412023-01-27 15:55:55 -0800182FileReader::FileReader(std::string_view filename)
183 : file_(open(::std::string(filename).c_str(), O_RDONLY)) {
184 PCHECK(file_.get() != -1) << ": opening " << filename;
185}
186
187absl::Span<char> FileReader::ReadContents(absl::Span<char> buffer) {
188 PCHECK(0 == lseek(file_.get(), 0, SEEK_SET));
189 const ssize_t result = read(file_.get(), buffer.data(), buffer.size());
190 PCHECK(result >= 0);
191 return {buffer.data(), static_cast<size_t>(result)};
192}
193
James Kuszmaulfd43f4e2022-12-16 15:19:35 -0800194FileWriter::FileWriter(std::string_view filename, mode_t permissions)
195 : file_(open(::std::string(filename).c_str(), O_WRONLY | O_CREAT | O_TRUNC,
196 permissions)) {
197 PCHECK(file_.get() != -1) << ": opening " << filename;
198}
199
James Kuszmaul5a88d412023-01-27 15:55:55 -0800200// absl::SimpleAtoi doesn't interpret a leading 0x as hex, which we need here.
201// Instead, we use the flatbufers API, which unfortunately relies on NUL
202// termination.
203int32_t FileReader::ReadInt32() {
204 // Maximum characters for a 32-bit integer, +1 for the NUL.
205 // Hex is the same size with the leading 0x.
206 std::array<char, 11> buffer;
207 int32_t result;
208 const auto string_span =
209 ReadContents(absl::Span<char>(buffer.data(), buffer.size())
210 .subspan(0, buffer.size() - 1));
211 // Verify we found the newline.
212 CHECK_EQ(buffer[string_span.size() - 1], '\n');
213 // Truncate the newline.
214 buffer[string_span.size() - 1] = '\0';
215 CHECK(flatbuffers::StringToNumber(buffer.data(), &result))
216 << ": Error parsing string to integer: "
217 << std::string_view(string_span.data(), string_span.size());
218
219 return result;
220}
221
James Kuszmaulfd43f4e2022-12-16 15:19:35 -0800222FileWriter::WriteResult FileWriter::WriteBytes(
223 absl::Span<const uint8_t> bytes) {
224 size_t size_written = 0;
225 while (size_written != bytes.size()) {
226 const ssize_t result = write(file_.get(), bytes.data() + size_written,
227 bytes.size() - size_written);
228 if (result < 0) {
229 return {size_written, static_cast<int>(result)};
230 }
231 // Not really supposed to happen unless writing zero bytes without an error.
232 // See, e.g.,
233 // https://stackoverflow.com/questions/2176443/is-a-return-value-of-0-from-write2-in-c-an-error
234 if (result == 0) {
235 return {size_written, static_cast<int>(result)};
236 }
237
238 size_written += result;
239 }
240 return {size_written, static_cast<int>(size_written)};
241}
242
243FileWriter::WriteResult FileWriter::WriteBytes(std::string_view bytes) {
244 return WriteBytes(absl::Span<const uint8_t>{
245 reinterpret_cast<const uint8_t *>(bytes.data()), bytes.size()});
246}
247
248void FileWriter::WriteBytesOrDie(std::string_view bytes) {
249 WriteBytesOrDie(absl::Span<const uint8_t>{
250 reinterpret_cast<const uint8_t *>(bytes.data()), bytes.size()});
251}
252
253void FileWriter::WriteBytesOrDie(absl::Span<const uint8_t> bytes) {
254 PCHECK(bytes.size() == WriteBytes(bytes).bytes_written)
255 << ": Failed to write " << bytes.size() << " bytes.";
256}
257
Brian Silverman61175fb2016-03-13 15:35:56 -0400258} // namespace util
259} // namespace aos