blob: 4971a001e3baecc529d954a45546ef706bd53f7f [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/developer/shell/mirror/wire_format.h"
#include <dirent.h>
#include <lib/syslog/cpp/macros.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <cstddef>
#include <fstream>
#include <iterator>
#include <memory>
#include "src/developer/shell/mirror/common.h"
namespace shell::mirror {
int Files::AddFile(const std::filesystem::path &path, std::unique_ptr<char[]> &&contents,
long length) {
for (const auto &file : files_) {
if (file.Name() == path) {
// already present. Consider replacing?
return 0;
}
}
files_.emplace_back(path, std::move(contents), length);
return 0;
}
int Files::AddFile(const std::filesystem::path &path) {
FILE *f = fopen(path.c_str(), "rb");
if (f == nullptr) {
return -1;
}
int err = fseek(f, 0, SEEK_END);
if (err == -1) {
return err;
}
long file_size = ftell(f);
err = fseek(f, 0, SEEK_SET);
if (err == -1) {
return err;
}
std::unique_ptr<char[]> contents = std::make_unique<char[]>(file_size + 1);
fread(contents.get(), file_size, 1, f);
fclose(f);
contents[file_size] = 0;
return AddFile(path, std::move(contents), file_size);
}
namespace {
template <class T>
void WriteFixedLength(std::vector<char> *sink, T size) {
for (std::size_t i = 0; i < sizeof(T); i++) {
char *size_ch = reinterpret_cast<char *>(&size) + i;
sink->push_back(*size_ch);
}
}
} // namespace
// Dumps the files to a vector, and returns that vector.
// Note that this requires enough memory to hold all of the files.
int Files::DumpFiles(std::vector<char> *sink) {
WriteFixedLength<uint64_t>(sink, files_.size());
std::string root = root_dir_.string();
for (const auto &file : files_) {
// Calculate remote pathname (i.e., remove local root path prefix)
std::string name = file.Name();
if (name.find(root_dir_.string()) == 0) {
name.erase(0, root.length());
while (name[0] == std::filesystem::path::preferred_separator) {
name.erase(0, 1);
}
}
// Write path length
uint64_t path_size = name.length();
WriteFixedLength<uint64_t>(sink, path_size);
sink->reserve(sink->size() + path_size);
// Write path
std::vector<char> name_vec(name.begin(), name.end());
sink->insert(sink->end(), name.begin(), name.end());
std::string_view data_view = file.View();
size_t data_size = data_view.size();
const char *data = data_view.data();
WriteFixedLength<uint64_t>(sink, data_size);
sink->reserve(sink->size() + data_size);
sink->insert(sink->end(), data, data + data_size);
}
return 0;
}
namespace {
// Read like read(2), potentially selecting on the fd first, potentially with a timeout, and
// continuing to read until the requested number of bytes has been read.
Err DoRead(int fd, void *buf, size_t count, struct timeval *timeout) {
struct stat statbuf;
int statres = fstat(fd, &statbuf);
// Fuchsia doesn't support select on regular files.
if (statres == -1 || !S_ISREG(statbuf.st_mode)) {
fd_set set;
struct timeval tv;
FD_ZERO(&set);
FD_SET(fd, &set);
tv.tv_sec = 5;
tv.tv_usec = 0;
if (timeout == nullptr) {
timeout = &tv;
}
int retval = select(fd + 1, &set, nullptr, nullptr, timeout);
if (retval == -1) {
std::string error = "Error in select(): " + std::string(strerror(errno));
return Err(kRead, error);
}
if (retval != 1) {
std::string error = "Timed out waiting for reload server: " + std::to_string(retval);
return Err(kRead, error);
}
}
ssize_t ct;
size_t to_be_counted = count;
while (to_be_counted != 0) {
if ((ct = read(fd, buf, to_be_counted)) == -1) {
std::string error = "Error reading: " + std::string(strerror(errno));
return Err(kRead, error);
}
to_be_counted -= ct;
}
return Err();
}
} // namespace
#define READ_RET(fd, buf, count) \
do { \
void *vbuf = const_cast<void *>(reinterpret_cast<const void *>(buf)); \
Err res = DoRead(fd, vbuf, count, timeout); \
if (res.code != 0) { \
*error = res; \
return Files(); \
} \
} while (0)
Files Files::FilesFromFD(int fd, Err *error, struct timeval *timeout) {
Files files;
uint64_t num_files;
READ_RET(fd, &num_files, sizeof(num_files));
files.files_.reserve(num_files);
for (size_t i = 0; i < num_files; i++) {
uint64_t path_size;
READ_RET(fd, &path_size, sizeof(path_size));
std::unique_ptr<char[]> path_name(new char[path_size + 1]);
READ_RET(fd, path_name.get(), path_size);
path_name.get()[path_size] = '\0';
uint64_t file_size;
READ_RET(fd, &file_size, sizeof(file_size));
std::unique_ptr<char[]> data(new char[file_size]);
READ_RET(fd, data.get(), file_size);
files.files_.emplace_back(path_name.get(), std::move(data), file_size);
}
return files;
}
} // namespace shell::mirror