| // Copyright 2024 Google LLC |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "mtd_util.hpp" |
| |
| #include "fs.hpp" |
| #include "sys.hpp" |
| |
| #include <fcntl.h> |
| #include <mtd/mtd-user.h> |
| |
| #include <algorithm> |
| #include <cstring> |
| #include <format> |
| #include <stdexcept> |
| #include <string> |
| |
| namespace google |
| { |
| namespace hoth |
| { |
| namespace internal |
| { |
| |
| // Sysfs directory containing info about mtd devices |
| auto constexpr sysfsMtd = "/sys/class/mtd"; |
| |
| std::string MtdImpl::findPartition(const std::string& name) |
| { |
| auto dirs = listDir(sysfsMtd); |
| |
| for (const auto& p : dirs) |
| { |
| std::string line; |
| |
| try |
| { |
| line = getFirstLine(p / "name"); |
| } |
| catch (const std::ios_base::failure& e) |
| { |
| // If we fail to read a name, just try the next one |
| continue; |
| } |
| |
| if (line == name) |
| { |
| return p.root_path() / "dev" / p.filename(); |
| } |
| } |
| |
| throw std::runtime_error( |
| std::format("Unable to find partition with name \"{}\"", name)); |
| } |
| |
| // TODO: choose the best block size |
| constexpr size_t blockSize = 4 * 1024; |
| |
| void MtdImpl::flashCopy(const Sys* sys, const std::vector<uint8_t>& data, |
| const std::string& device) |
| { |
| int fd = sys->open(device.c_str(), O_RDWR | O_SYNC); |
| |
| if (fd < 0) |
| { |
| throw errnoException(std::format("Failed to open device {}", device)); |
| } |
| |
| mtd_info_t mtd; |
| if (sys->ioctl(fd, MEMGETINFO, &mtd) < 0) |
| { |
| sys->close(fd); |
| |
| throw errnoException(std::format("Failed to get info on {}", device)); |
| } |
| |
| if (data.size() > mtd.size) |
| { |
| sys->close(fd); |
| |
| throw std::runtime_error(std::format( |
| "Data size {} too large for device {}", data.size(), device)); |
| } |
| |
| erase_info_t erase; |
| erase.start = 0; |
| erase.length = mtd.size; |
| if (sys->ioctl(fd, MEMERASE, &erase) < 0) |
| { |
| sys->close(fd); |
| |
| throw errnoException( |
| std::format("Failed to erase mtd device {}", device)); |
| } |
| |
| size_t count = 0; |
| while (count < data.size()) |
| { |
| size_t writeSize = std::min(data.size() - count, blockSize); |
| auto ret = sys->write(fd, data.data() + count, writeSize); |
| |
| if (ret < 0) |
| { |
| if (errno == EINTR) |
| { |
| continue; // interrupted; try again |
| } |
| sys->close(fd); |
| |
| throw errnoException( |
| std::format("Failed to write to mtd device {}", device)); |
| } |
| if (ret == 0) |
| { |
| sys->close(fd); |
| |
| throw std::runtime_error(std::format( |
| "Unexpected EOF while writing to mtd device {}", device)); |
| } // ret > 0 |
| count += ret; |
| } |
| |
| // Validate written data |
| std::vector<uint8_t> readBuffer(blockSize); |
| |
| // Reset the file descriptor offset back to 0 before reading |
| sys->lseek(fd, 0, SEEK_SET); |
| |
| count = 0; |
| while (count < data.size()) |
| { |
| size_t readSize = std::min(data.size() - count, blockSize); |
| auto ret = sys->read(fd, readBuffer.data(), readSize); |
| |
| if (ret < 0) |
| { |
| if (errno == EINTR) |
| { |
| continue; // interrupted; try again |
| } |
| sys->close(fd); |
| |
| throw errnoException( |
| std::format("Failed to read from mtd device {}", device)); |
| } |
| if (ret == 0) |
| { |
| sys->close(fd); |
| |
| throw std::runtime_error(std::format( |
| "Unexpected EOF while reading from mtd device {}", device)); |
| } // ret > 0 |
| int rc = std::memcmp(data.data() + count, readBuffer.data(), ret); |
| if (rc != 0) |
| { |
| sys->close(fd); |
| |
| throw std::runtime_error( |
| std::format("Written data failed validation at {} bytes", count)); |
| } |
| count += ret; |
| } |
| |
| auto retClose = sys->close(fd); |
| if (retClose < 0) |
| { |
| throw errnoException(std::format("Failed to close device {}", device)); |
| } |
| } |
| |
| MtdImpl mtdImpl; |
| |
| } // namespace internal |
| |
| } // namespace hoth |
| |
| } // namespace google |