blob: 4e2d1cde6ed8fa7995e0b14cfd80c795fa6b45d7 [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
James Kuszmaul3ae42262019-11-08 12:33:41 -080010#include <string_view>
Austin Schuhe2df81b2021-08-16 10:52:27 -070011#if __has_feature(memory_sanitizer)
12#include <sanitizer/msan_interface.h>
13#endif
James Kuszmaul3ae42262019-11-08 12:33:41 -080014
John Park33858a32018-09-28 23:05:48 -070015#include "aos/scoped/scoped_fd.h"
Brian Silverman61175fb2016-03-13 15:35:56 -040016
17namespace aos {
18namespace util {
19
James Kuszmaul3ae42262019-11-08 12:33:41 -080020::std::string ReadFileToStringOrDie(const std::string_view filename) {
Brian Silverman61175fb2016-03-13 15:35:56 -040021 ::std::string r;
Austin Schuhcb108412019-10-13 16:09:54 -070022 ScopedFD fd(open(::std::string(filename).c_str(), O_RDONLY));
Alex Perrycb7da4b2019-08-28 19:35:56 -070023 PCHECK(fd.get() != -1) << ": opening " << filename;
Brian Silverman61175fb2016-03-13 15:35:56 -040024 while (true) {
25 char buffer[1024];
26 const ssize_t result = read(fd.get(), buffer, sizeof(buffer));
Alex Perrycb7da4b2019-08-28 19:35:56 -070027 PCHECK(result >= 0) << ": reading from " << filename;
28 if (result == 0) {
Brian Silverman61175fb2016-03-13 15:35:56 -040029 break;
30 }
31 r.append(buffer, result);
32 }
33 return r;
34}
35
James Kuszmaul3ae42262019-11-08 12:33:41 -080036void WriteStringToFileOrDie(const std::string_view filename,
Austin Schuhe3fc0532021-02-07 22:14:22 -080037 const std::string_view contents,
38 mode_t permissions) {
James Kuszmaulfd43f4e2022-12-16 15:19:35 -080039 FileWriter writer(filename, permissions);
40 writer.WriteBytesOrDie(
41 {reinterpret_cast<const uint8_t *>(contents.data()), contents.size()});
Alex Perrycb7da4b2019-08-28 19:35:56 -070042}
43
Brian Silvermana9f2ec92020-10-06 18:00:53 -070044bool MkdirPIfSpace(std::string_view path, mode_t mode) {
Austin Schuhfccb2d02020-01-26 16:11:19 -080045 auto last_slash_pos = path.find_last_of("/");
46
47 std::string folder(last_slash_pos == std::string_view::npos
48 ? std::string_view("")
49 : path.substr(0, last_slash_pos));
Brian Silvermana9f2ec92020-10-06 18:00:53 -070050 if (folder.empty()) {
51 return true;
52 }
53 if (!MkdirPIfSpace(folder, mode)) {
54 return false;
55 }
Austin Schuhfccb2d02020-01-26 16:11:19 -080056 const int result = mkdir(folder.c_str(), mode);
57 if (result == -1 && errno == EEXIST) {
58 VLOG(2) << folder << " already exists";
Brian Silvermana9f2ec92020-10-06 18:00:53 -070059 return true;
60 } else if (result == -1 && errno == ENOSPC) {
61 VLOG(2) << "Out of space";
62 return false;
Austin Schuhfccb2d02020-01-26 16:11:19 -080063 } else {
64 VLOG(1) << "Created " << folder;
65 }
66 PCHECK(result == 0) << ": Error creating " << folder;
Brian Silvermana9f2ec92020-10-06 18:00:53 -070067 return true;
Austin Schuhfccb2d02020-01-26 16:11:19 -080068}
69
James Kuszmaulf8178092020-05-10 18:46:45 -070070bool PathExists(std::string_view path) {
71 struct stat buffer;
72 return stat(path.data(), &buffer) == 0;
73}
74
Austin Schuhe991fe22020-11-18 16:53:39 -080075void UnlinkRecursive(std::string_view path) {
76 FTS *ftsp = NULL;
77 FTSENT *curr;
78
79 // Cast needed (in C) because fts_open() takes a "char * const *", instead
80 // of a "const char * const *", which is only allowed in C++. fts_open()
81 // does not modify the argument.
82 std::string p(path);
83 char *files[] = {const_cast<char *>(p.c_str()), NULL};
84
85 // FTS_NOCHDIR - Avoid changing cwd, which could cause unexpected behavior
86 // in multithreaded programs
87 // FTS_PHYSICAL - Don't follow symlinks. Prevents deletion of files outside
88 // of the specified directory
89 // FTS_XDEV - Don't cross filesystem boundaries
90 ftsp = fts_open(files, FTS_NOCHDIR | FTS_PHYSICAL | FTS_XDEV, NULL);
91 if (!ftsp) {
92 return;
93 }
94
95 while ((curr = fts_read(ftsp))) {
Austin Schuhe2df81b2021-08-16 10:52:27 -070096#if __has_feature(memory_sanitizer)
97 // fts_read doesn't have propper msan interceptors. Unpoison it ourselves.
98 if (curr) {
99 __msan_unpoison(curr, sizeof(*curr));
100 __msan_unpoison_string(curr->fts_accpath);
101 __msan_unpoison_string(curr->fts_path);
102 __msan_unpoison_string(curr->fts_name);
103 }
104#endif
Austin Schuhe991fe22020-11-18 16:53:39 -0800105 switch (curr->fts_info) {
106 case FTS_NS:
107 case FTS_DNR:
108 case FTS_ERR:
109 LOG(WARNING) << "Can't read " << curr->fts_accpath;
110 break;
111
112 case FTS_DC:
113 case FTS_DOT:
114 case FTS_NSOK:
115 // Not reached unless FTS_LOGICAL, FTS_SEEDOT, or FTS_NOSTAT were
116 // passed to fts_open()
117 break;
118
119 case FTS_D:
120 // Do nothing. Need depth-first search, so directories are deleted
121 // in FTS_DP
122 break;
123
124 case FTS_DP:
125 case FTS_F:
126 case FTS_SL:
127 case FTS_SLNONE:
128 case FTS_DEFAULT:
129 VLOG(1) << "Removing " << curr->fts_path;
130 if (remove(curr->fts_accpath) < 0) {
131 LOG(WARNING) << curr->fts_path
132 << ": Failed to remove: " << strerror(curr->fts_errno);
133 }
134 break;
135 }
136 }
137
138 if (ftsp) {
139 fts_close(ftsp);
140 }
141}
142
Austin Schuhe4d1a682021-10-01 15:04:50 -0700143std::shared_ptr<absl::Span<uint8_t>> MMapFile(const std::string &path,
144 FileOptions options) {
145 int fd =
146 open(path.c_str(), options == FileOptions::kReadable ? O_RDONLY : O_RDWR);
davidjevans8b9b52f2021-09-17 08:57:30 -0700147 PCHECK(fd != -1) << "Unable to open file " << path;
148 struct stat sb;
149 PCHECK(fstat(fd, &sb) != -1) << ": Unable to get file size of " << path;
Austin Schuhe4d1a682021-10-01 15:04:50 -0700150 uint8_t *start = reinterpret_cast<uint8_t *>(mmap(
151 NULL, sb.st_size,
152 options == FileOptions::kReadable ? PROT_READ : (PROT_READ | PROT_WRITE),
153 MAP_SHARED, fd, 0));
davidjevans8b9b52f2021-09-17 08:57:30 -0700154 CHECK(start != MAP_FAILED) << ": Unable to open mapping to file " << path;
155 std::shared_ptr<absl::Span<uint8_t>> span =
156 std::shared_ptr<absl::Span<uint8_t>>(
157 new absl::Span<uint8_t>(start, sb.st_size),
158 [](absl::Span<uint8_t> *span) {
Austin Schuhe4d1a682021-10-01 15:04:50 -0700159 PCHECK(msync(span->data(), span->size(), MS_SYNC) == 0)
160 << ": Failed to flush data before unmapping.";
davidjevans8b9b52f2021-09-17 08:57:30 -0700161 PCHECK(munmap(span->data(), span->size()) != -1);
162 delete span;
163 });
164 close(fd);
165 return span;
166}
167
James Kuszmaul5a88d412023-01-27 15:55:55 -0800168FileReader::FileReader(std::string_view filename)
169 : file_(open(::std::string(filename).c_str(), O_RDONLY)) {
170 PCHECK(file_.get() != -1) << ": opening " << filename;
171}
172
173absl::Span<char> FileReader::ReadContents(absl::Span<char> buffer) {
174 PCHECK(0 == lseek(file_.get(), 0, SEEK_SET));
175 const ssize_t result = read(file_.get(), buffer.data(), buffer.size());
176 PCHECK(result >= 0);
177 return {buffer.data(), static_cast<size_t>(result)};
178}
179
James Kuszmaulfd43f4e2022-12-16 15:19:35 -0800180FileWriter::FileWriter(std::string_view filename, mode_t permissions)
181 : file_(open(::std::string(filename).c_str(), O_WRONLY | O_CREAT | O_TRUNC,
182 permissions)) {
183 PCHECK(file_.get() != -1) << ": opening " << filename;
184}
185
James Kuszmaul5a88d412023-01-27 15:55:55 -0800186// absl::SimpleAtoi doesn't interpret a leading 0x as hex, which we need here.
187// Instead, we use the flatbufers API, which unfortunately relies on NUL
188// termination.
189int32_t FileReader::ReadInt32() {
190 // Maximum characters for a 32-bit integer, +1 for the NUL.
191 // Hex is the same size with the leading 0x.
192 std::array<char, 11> buffer;
193 int32_t result;
194 const auto string_span =
195 ReadContents(absl::Span<char>(buffer.data(), buffer.size())
196 .subspan(0, buffer.size() - 1));
197 // Verify we found the newline.
198 CHECK_EQ(buffer[string_span.size() - 1], '\n');
199 // Truncate the newline.
200 buffer[string_span.size() - 1] = '\0';
201 CHECK(flatbuffers::StringToNumber(buffer.data(), &result))
202 << ": Error parsing string to integer: "
203 << std::string_view(string_span.data(), string_span.size());
204
205 return result;
206}
207
James Kuszmaulfd43f4e2022-12-16 15:19:35 -0800208FileWriter::WriteResult FileWriter::WriteBytes(
209 absl::Span<const uint8_t> bytes) {
210 size_t size_written = 0;
211 while (size_written != bytes.size()) {
212 const ssize_t result = write(file_.get(), bytes.data() + size_written,
213 bytes.size() - size_written);
214 if (result < 0) {
215 return {size_written, static_cast<int>(result)};
216 }
217 // Not really supposed to happen unless writing zero bytes without an error.
218 // See, e.g.,
219 // https://stackoverflow.com/questions/2176443/is-a-return-value-of-0-from-write2-in-c-an-error
220 if (result == 0) {
221 return {size_written, static_cast<int>(result)};
222 }
223
224 size_written += result;
225 }
226 return {size_written, static_cast<int>(size_written)};
227}
228
229FileWriter::WriteResult FileWriter::WriteBytes(std::string_view bytes) {
230 return WriteBytes(absl::Span<const uint8_t>{
231 reinterpret_cast<const uint8_t *>(bytes.data()), bytes.size()});
232}
233
234void FileWriter::WriteBytesOrDie(std::string_view bytes) {
235 WriteBytesOrDie(absl::Span<const uint8_t>{
236 reinterpret_cast<const uint8_t *>(bytes.data()), bytes.size()});
237}
238
239void FileWriter::WriteBytesOrDie(absl::Span<const uint8_t> bytes) {
240 PCHECK(bytes.size() == WriteBytes(bytes).bytes_written)
241 << ": Failed to write " << bytes.size() << " bytes.";
242}
243
Brian Silverman61175fb2016-03-13 15:35:56 -0400244} // namespace util
245} // namespace aos