| // 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 |