blob: c2a8f3aacdd732455de35916ac5776c26e47fb74 [file] [log] [blame]
// 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