blob: f1afa0f78c9a70da94905331d34465e2c1aa4467 [file] [log] [blame]
#include "aos/util/file.h"
#include <errno.h>
#include <fcntl.h>
#include <fts.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <algorithm>
#include <iterator>
#include <optional>
#include <ostream>
#include <string_view>
#if __has_feature(memory_sanitizer)
#include <sanitizer/msan_interface.h>
#endif
#include "flatbuffers/util.h"
#include "aos/scoped/scoped_fd.h"
namespace aos::util {
std::string ReadFileToStringOrDie(const std::string_view filename) {
std::optional<std::string> r = MaybeReadFileToString(filename);
PCHECK(r.has_value()) << "Failed to read " << filename << " to string";
return r.value();
}
std::optional<std::string> MaybeReadFileToString(
const std::string_view filename) {
std::string r;
ScopedFD fd(open(::std::string(filename).c_str(), O_RDONLY));
if (fd.get() == -1) {
PLOG(ERROR) << "Failed to open " << filename;
return std::nullopt;
}
while (true) {
char buffer[1024];
const ssize_t result = read(fd.get(), buffer, sizeof(buffer));
if (result < 0) {
PLOG(ERROR) << "Failed to read from " << filename;
return std::nullopt;
}
if (result == 0) {
break;
}
r.append(buffer, result);
}
return r;
}
std::vector<uint8_t> ReadFileToVecOrDie(const std::string_view filename) {
std::vector<uint8_t> r;
ScopedFD fd(open(::std::string(filename).c_str(), O_RDONLY));
PCHECK(fd.get() != -1) << ": opening " << filename;
while (true) {
uint8_t buffer[1024];
const ssize_t result = read(fd.get(), buffer, sizeof(buffer));
PCHECK(result >= 0) << ": reading from " << filename;
if (result == 0) {
break;
}
std::copy(buffer, buffer + result, std::back_inserter(r));
}
return r;
}
void WriteStringToFileOrDie(const std::string_view filename,
const std::string_view contents,
mode_t permissions) {
FileWriter writer(filename, permissions);
writer.WriteBytesOrDie(
{reinterpret_cast<const uint8_t *>(contents.data()), contents.size()});
}
bool MkdirPIfSpace(std::string_view path, mode_t mode) {
auto last_slash_pos = path.find_last_of("/");
std::string folder(last_slash_pos == std::string_view::npos
? std::string_view("")
: path.substr(0, last_slash_pos));
if (folder.empty()) {
return true;
}
if (!MkdirPIfSpace(folder, mode)) {
return false;
}
const int result = mkdir(folder.c_str(), mode);
if (result == -1 && errno == EEXIST) {
VLOG(2) << folder << " already exists";
return true;
} else if (result == -1 && errno == ENOSPC) {
VLOG(2) << "Out of space";
return false;
} else {
VLOG(1) << "Created " << folder;
}
PCHECK(result == 0) << ": Error creating " << folder;
return true;
}
bool PathExists(std::string_view path) {
struct stat buffer;
return stat(path.data(), &buffer) == 0;
}
void UnlinkRecursive(std::string_view path) {
FTS *ftsp = NULL;
FTSENT *curr;
// Cast needed (in C) because fts_open() takes a "char * const *", instead
// of a "const char * const *", which is only allowed in C++. fts_open()
// does not modify the argument.
std::string p(path);
char *files[] = {const_cast<char *>(p.c_str()), NULL};
// FTS_NOCHDIR - Avoid changing cwd, which could cause unexpected behavior
// in multithreaded programs
// FTS_PHYSICAL - Don't follow symlinks. Prevents deletion of files outside
// of the specified directory
// FTS_XDEV - Don't cross filesystem boundaries
ftsp = fts_open(files, FTS_NOCHDIR | FTS_PHYSICAL | FTS_XDEV, NULL);
if (!ftsp) {
return;
}
while ((curr = fts_read(ftsp))) {
#if __has_feature(memory_sanitizer)
// fts_read doesn't have propper msan interceptors. Unpoison it ourselves.
if (curr) {
__msan_unpoison(curr, sizeof(*curr));
__msan_unpoison_string(curr->fts_accpath);
__msan_unpoison_string(curr->fts_path);
__msan_unpoison_string(curr->fts_name);
}
#endif
switch (curr->fts_info) {
case FTS_NS:
case FTS_DNR:
case FTS_ERR:
LOG(WARNING) << "Can't read " << curr->fts_accpath;
break;
case FTS_DC:
case FTS_DOT:
case FTS_NSOK:
// Not reached unless FTS_LOGICAL, FTS_SEEDOT, or FTS_NOSTAT were
// passed to fts_open()
break;
case FTS_D:
// Do nothing. Need depth-first search, so directories are deleted
// in FTS_DP
break;
case FTS_DP:
case FTS_F:
case FTS_SL:
case FTS_SLNONE:
case FTS_DEFAULT:
VLOG(1) << "Removing " << curr->fts_path;
if (remove(curr->fts_accpath) < 0) {
LOG(WARNING) << curr->fts_path
<< ": Failed to remove: " << strerror(curr->fts_errno);
}
break;
}
}
if (ftsp) {
fts_close(ftsp);
}
}
std::shared_ptr<absl::Span<uint8_t>> MMapFile(const std::string &path,
FileOptions options) {
int fd =
open(path.c_str(), options == FileOptions::kReadable ? O_RDONLY : O_RDWR);
PCHECK(fd != -1) << "Unable to open file " << path;
struct stat sb;
PCHECK(fstat(fd, &sb) != -1) << ": Unable to get file size of " << path;
uint8_t *start = reinterpret_cast<uint8_t *>(mmap(
NULL, sb.st_size,
options == FileOptions::kReadable ? PROT_READ : (PROT_READ | PROT_WRITE),
MAP_SHARED, fd, 0));
CHECK(start != MAP_FAILED) << ": Unable to open mapping to file " << path;
std::shared_ptr<absl::Span<uint8_t>> span =
std::shared_ptr<absl::Span<uint8_t>>(
new absl::Span<uint8_t>(start, sb.st_size),
[](absl::Span<uint8_t> *span) {
PCHECK(msync(span->data(), span->size(), MS_SYNC) == 0)
<< ": Failed to flush data before unmapping.";
PCHECK(munmap(span->data(), span->size()) != -1);
delete span;
});
close(fd);
return span;
}
FileReader::FileReader(std::string_view filename,
FileReaderErrorType error_type)
: file_(open(::std::string(filename).c_str(), O_RDONLY)) {
if (!is_open()) {
PLOG_IF(FATAL, error_type == FileReaderErrorType::kFatal)
<< ": opening " << filename;
PLOG(ERROR) << "opening " << filename;
}
}
std::optional<absl::Span<char>> FileReader::ReadContents(
absl::Span<char> buffer) {
PCHECK(0 == lseek(file_.get(), 0, SEEK_SET));
const ssize_t result = read(file_.get(), buffer.data(), buffer.size());
if (result < 0) {
// Read timeout for an i2c request returns this.
if (errno == EREMOTEIO) {
return std::nullopt;
}
}
PCHECK(result >= 0);
return absl::Span<char>{buffer.data(), static_cast<size_t>(result)};
}
FileWriter::FileWriter(std::string_view filename, mode_t permissions)
: file_(open(::std::string(filename).c_str(), O_WRONLY | O_CREAT | O_TRUNC,
permissions)) {
PCHECK(file_.get() != -1) << ": opening " << filename;
}
// absl::SimpleAtoi doesn't interpret a leading 0x as hex, which we need here.
// Instead, we use the flatbufers API, which unfortunately relies on NUL
// termination.
std::optional<int32_t> FileReader::ReadInt32() {
// Maximum characters for a 32-bit integer, +1 for the NUL.
// Hex is the same size with the leading 0x.
std::array<char, 11> buffer;
int32_t result;
const std::optional<absl::Span<char>> string_span =
ReadContents(absl::Span<char>(buffer.data(), buffer.size())
.subspan(0, buffer.size() - 1));
if (!string_span.has_value()) {
return std::nullopt;
}
// Verify we found the newline.
CHECK_EQ(buffer[string_span->size() - 1], '\n');
// Truncate the newline.
buffer[string_span->size() - 1] = '\0';
CHECK(flatbuffers::StringToNumber(buffer.data(), &result))
<< ": Error parsing string to integer: "
<< std::string_view(string_span->data(), string_span->size());
return result;
}
FileWriter::WriteResult FileWriter::WriteBytes(
absl::Span<const uint8_t> bytes) {
size_t size_written = 0;
while (size_written != bytes.size()) {
const ssize_t result = write(file_.get(), bytes.data() + size_written,
bytes.size() - size_written);
if (result < 0) {
return {size_written, static_cast<int>(result)};
}
// Not really supposed to happen unless writing zero bytes without an error.
// See, e.g.,
// https://stackoverflow.com/questions/2176443/is-a-return-value-of-0-from-write2-in-c-an-error
if (result == 0) {
return {size_written, static_cast<int>(result)};
}
size_written += result;
}
return {size_written, static_cast<int>(size_written)};
}
FileWriter::WriteResult FileWriter::WriteBytes(std::string_view bytes) {
return WriteBytes(absl::Span<const uint8_t>{
reinterpret_cast<const uint8_t *>(bytes.data()), bytes.size()});
}
void FileWriter::WriteBytesOrDie(std::string_view bytes) {
WriteBytesOrDie(absl::Span<const uint8_t>{
reinterpret_cast<const uint8_t *>(bytes.data()), bytes.size()});
}
void FileWriter::WriteBytesOrDie(absl::Span<const uint8_t> bytes) {
PCHECK(bytes.size() == WriteBytes(bytes).bytes_written)
<< ": Failed to write " << bytes.size() << " bytes.";
}
} // namespace aos::util