mirror of
https://github.com/MeshEnvy/lobbs.git
synced 2026-10-31 02:11:59 +01:00
Single LoLog slot on QSPI or nRF flash2; sd/lofs/extra/internal order. Host lolog tests; drop qspi/spare/reserve mount names.
1264 lines
38 KiB
C++
1264 lines
38 KiB
C++
#include "LoLog.h"
|
|
#include "lofs/lolog/LoLogConfig.h"
|
|
#include <algorithm>
|
|
#include <cstring>
|
|
#include <string>
|
|
|
|
#if defined(LOBBS_PLATFORM_MESHTASTIC) || defined(LOBBS_PLATFORM_MESHCORE)
|
|
#include "platforms/LoPlatform.h"
|
|
#else
|
|
static uint32_t lologStubMs()
|
|
{
|
|
static uint32_t t;
|
|
return t++;
|
|
}
|
|
#endif
|
|
|
|
static uint32_t lologNowMs()
|
|
{
|
|
#if defined(LOBBS_PLATFORM_MESHTASTIC) || defined(LOBBS_PLATFORM_MESHCORE)
|
|
return lobbsPlatformMillis();
|
|
#else
|
|
return lologStubMs();
|
|
#endif
|
|
}
|
|
|
|
bool LoLog::NameKey::operator<(const NameKey &o) const
|
|
{
|
|
if (dirId != o.dirId)
|
|
return dirId < o.dirId;
|
|
return name < o.name;
|
|
}
|
|
|
|
LoLog::LoLog(LoFSRawDevice &device) : dev_(device) {}
|
|
|
|
uint32_t LoLog::crc32(const uint8_t *data, size_t len) const
|
|
{
|
|
uint32_t crc = 0xFFFFFFFF;
|
|
for (size_t i = 0; i < len; i++) {
|
|
crc ^= data[i];
|
|
for (int b = 0; b < 8; b++)
|
|
crc = (crc >> 1) ^ (0xEDB88320 & (~((crc & 1) - 1)));
|
|
}
|
|
return ~crc;
|
|
}
|
|
|
|
bool LoLog::readBytes(uint32_t addr, void *buf, size_t len)
|
|
{
|
|
return dev_.read(addr, buf, len);
|
|
}
|
|
|
|
bool LoLog::progBytes(uint32_t addr, const void *buf, size_t len)
|
|
{
|
|
if ((addr & 3) != 0 || (len & 3) != 0)
|
|
return false;
|
|
return dev_.prog(addr, buf, len);
|
|
}
|
|
|
|
bool LoLog::readSectorHeader(uint32_t sector, uint32_t &magic, uint32_t &epoch, uint32_t &seq, uint8_t &kind)
|
|
{
|
|
uint8_t hdr[lolog::kHeaderSize];
|
|
if (!readBytes(sector * lolog::kSectorSize, hdr, sizeof(hdr)))
|
|
return false;
|
|
magic = (uint32_t)hdr[0] | ((uint32_t)hdr[1] << 8) | ((uint32_t)hdr[2] << 16) | ((uint32_t)hdr[3] << 24);
|
|
epoch = (uint32_t)hdr[4] | ((uint32_t)hdr[5] << 8) | ((uint32_t)hdr[6] << 16) | ((uint32_t)hdr[7] << 24);
|
|
seq = (uint32_t)hdr[8] | ((uint32_t)hdr[9] << 8) | ((uint32_t)hdr[10] << 16) | ((uint32_t)hdr[11] << 24);
|
|
kind = hdr[12];
|
|
uint32_t hcrc = (uint32_t)hdr[13] | ((uint32_t)hdr[14] << 8) | ((uint32_t)hdr[15] << 16);
|
|
uint32_t calc = crc32(hdr, 13) & 0xFFFFFF;
|
|
if (magic != lolog::kMagicUsed || calc != hcrc)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
bool LoLog::writeSectorHeader(uint32_t sector, uint32_t seq, uint8_t kind)
|
|
{
|
|
uint8_t hdr[lolog::kHeaderSize];
|
|
memset(hdr, 0xFF, sizeof(hdr));
|
|
hdr[0] = (uint8_t)(lolog::kMagicUsed);
|
|
hdr[1] = (uint8_t)(lolog::kMagicUsed >> 8);
|
|
hdr[2] = (uint8_t)(lolog::kMagicUsed >> 16);
|
|
hdr[3] = (uint8_t)(lolog::kMagicUsed >> 24);
|
|
hdr[4] = (uint8_t)(epoch_);
|
|
hdr[5] = (uint8_t)(epoch_ >> 8);
|
|
hdr[6] = (uint8_t)(epoch_ >> 16);
|
|
hdr[7] = (uint8_t)(epoch_ >> 24);
|
|
hdr[8] = (uint8_t)(seq);
|
|
hdr[9] = (uint8_t)(seq >> 8);
|
|
hdr[10] = (uint8_t)(seq >> 16);
|
|
hdr[11] = (uint8_t)(seq >> 24);
|
|
hdr[12] = kind;
|
|
uint32_t hcrc = crc32(hdr, 13) & 0xFFFFFF;
|
|
hdr[13] = (uint8_t)hcrc;
|
|
hdr[14] = (uint8_t)(hcrc >> 8);
|
|
hdr[15] = (uint8_t)(hcrc >> 16);
|
|
return progBytes(sector * lolog::kSectorSize, hdr, sizeof(hdr));
|
|
}
|
|
|
|
bool LoLog::allocDataSector()
|
|
{
|
|
const uint32_t n = dev_.sectorCount();
|
|
if (n == 0)
|
|
return false;
|
|
static uint32_t rr = 0;
|
|
for (uint32_t i = 0; i < n; i++) {
|
|
uint32_t s = (rr + i) % n;
|
|
if (s >= sectorUsed_.size() || sectorUsed_[s])
|
|
continue;
|
|
if (!dev_.eraseSector(s))
|
|
continue;
|
|
sectorUsed_[s] = 1;
|
|
liveInSector_[s] = lolog::kHeaderSize;
|
|
rr = (s + 1) % n;
|
|
activeSector_ = (uint16_t)s;
|
|
activeOff_ = lolog::kHeaderSize;
|
|
if (!writeSectorHeader(s, nextSeq_++, lolog::kSegData))
|
|
return false;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool LoLog::appendRaw(const uint8_t *payload, size_t payloadLen, uint8_t type, uint8_t flags)
|
|
{
|
|
const uint32_t entLen = (uint32_t)(8 + payloadLen);
|
|
const uint32_t padded = (entLen + 3) & ~3u;
|
|
if (activeSector_ == 0xFFFF || activeOff_ + padded > lolog::kSectorSize) {
|
|
if (!allocDataSector())
|
|
return false;
|
|
}
|
|
if (!hasRoomFor(padded + lolog::kHeadroomSectors * lolog::kSectorSize))
|
|
return false;
|
|
|
|
uint8_t stack[8 + lolog::kInlineMax];
|
|
if (payloadLen > lolog::kInlineMax)
|
|
return false;
|
|
stack[0] = (uint8_t)(entLen - 8);
|
|
stack[1] = (uint8_t)((entLen - 8) >> 8);
|
|
stack[2] = type;
|
|
stack[3] = flags;
|
|
memcpy(stack + 4, payload, payloadLen);
|
|
uint32_t c = crc32(stack + 4, payloadLen);
|
|
memcpy(stack + 4 + payloadLen, &c, 4);
|
|
size_t pad = padded - entLen;
|
|
if (pad)
|
|
memset(stack + entLen, 0xFF, pad);
|
|
|
|
uint32_t addr = activeSector_ * lolog::kSectorSize + activeOff_;
|
|
if (!progBytes(addr, stack, padded))
|
|
return false;
|
|
activeOff_ += padded;
|
|
liveInSector_[activeSector_] = activeOff_;
|
|
liveBytes_ += padded;
|
|
lastActivityMs_ = nowMs();
|
|
return true;
|
|
}
|
|
|
|
void LoLog::beginGroup()
|
|
{
|
|
pendingGroup_.clear();
|
|
}
|
|
|
|
bool LoLog::endGroup()
|
|
{
|
|
for (size_t i = 0; i < pendingGroup_.size(); i++) {
|
|
uint8_t fl = (i + 1 == pendingGroup_.size()) ? lolog::kFlagGroupEnd : 0;
|
|
if (!appendRaw(pendingGroup_[i].payload.data(), pendingGroup_[i].payload.size(), pendingGroup_[i].type, fl))
|
|
return false;
|
|
}
|
|
pendingGroup_.clear();
|
|
return true;
|
|
}
|
|
|
|
void LoLog::applyEntry(uint8_t type, const uint8_t *payload, size_t len)
|
|
{
|
|
if (type == lolog::kEntDir && len >= 8) {
|
|
uint32_t parent = (uint32_t)payload[0] | ((uint32_t)payload[1] << 8) | ((uint32_t)payload[2] << 16) |
|
|
((uint32_t)payload[3] << 24);
|
|
uint32_t dirId = (uint32_t)payload[4] | ((uint32_t)payload[5] << 8) | ((uint32_t)payload[6] << 16) |
|
|
((uint32_t)payload[7] << 24);
|
|
const char *name = (const char *)(payload + 8);
|
|
if (dirId >= dirParent_.size())
|
|
dirParent_.resize(dirId + 1, lolog::kRootDirId);
|
|
dirParent_[dirId] = parent;
|
|
upsertName(parent, name, NameVal{true, dirId});
|
|
} else if (type == lolog::kEntFile && len >= 16) {
|
|
uint32_t dirId = (uint32_t)payload[0] | ((uint32_t)payload[1] << 8) | ((uint32_t)payload[2] << 16) |
|
|
((uint32_t)payload[3] << 24);
|
|
uint32_t inode = (uint32_t)payload[4] | ((uint32_t)payload[5] << 8) | ((uint32_t)payload[6] << 16) |
|
|
((uint32_t)payload[7] << 24);
|
|
uint32_t size = (uint32_t)payload[8] | ((uint32_t)payload[9] << 8) | ((uint32_t)payload[10] << 16) |
|
|
((uint32_t)payload[11] << 24);
|
|
uint32_t mtime = (uint32_t)payload[12] | ((uint32_t)payload[13] << 8) | ((uint32_t)payload[14] << 16) |
|
|
((uint32_t)payload[15] << 24);
|
|
const char *name = (const char *)(payload + 16);
|
|
size_t nameLen = strnlen(name, len - 16);
|
|
size_t inlineOff = 16 + nameLen + 1;
|
|
FileMeta meta;
|
|
meta.dirId = dirId;
|
|
meta.name.assign(name, nameLen);
|
|
meta.isDir = false;
|
|
meta.inodeId = inode;
|
|
meta.size = size;
|
|
meta.mtime = mtime;
|
|
if (inlineOff < len)
|
|
meta.data.assign(payload + inlineOff, payload + len);
|
|
if (inode >= byInode_.size())
|
|
byInode_.resize(inode + 1);
|
|
byInode_[inode] = meta;
|
|
upsertName(dirId, meta.name.c_str(), NameVal{false, inode});
|
|
} else if (type == lolog::kEntExtent && len >= 8) {
|
|
uint32_t inode = (uint32_t)payload[0] | ((uint32_t)payload[1] << 8) | ((uint32_t)payload[2] << 16) |
|
|
((uint32_t)payload[3] << 24);
|
|
uint32_t offset = (uint32_t)payload[4] | ((uint32_t)payload[5] << 8) | ((uint32_t)payload[6] << 16) |
|
|
((uint32_t)payload[7] << 24);
|
|
const uint8_t *data = payload + 8;
|
|
size_t dlen = len - 8;
|
|
FileMeta *m = metaByInode(inode);
|
|
if (!m)
|
|
return;
|
|
if (m->data.size() < offset + dlen)
|
|
m->data.resize(offset + dlen);
|
|
memcpy(m->data.data() + offset, data, dlen);
|
|
if (offset + dlen > m->size)
|
|
m->size = (uint32_t)(offset + dlen);
|
|
} else if (type == lolog::kEntTomb && len >= 4) {
|
|
uint32_t dirId = (uint32_t)payload[0] | ((uint32_t)payload[1] << 8) | ((uint32_t)payload[2] << 16) |
|
|
((uint32_t)payload[3] << 24);
|
|
if (len == 4) {
|
|
deadDirIds_.push_back(dirId);
|
|
tombDir(dirId);
|
|
} else {
|
|
const char *name = (const char *)(payload + 4);
|
|
eraseName(dirId, name);
|
|
}
|
|
}
|
|
}
|
|
|
|
void LoLog::replaySector(uint32_t sector)
|
|
{
|
|
uint32_t magic, epoch, seq;
|
|
uint8_t kind = 0;
|
|
if (!readSectorHeader(sector, magic, epoch, seq, kind) || kind != lolog::kSegData)
|
|
return;
|
|
if (epoch != epoch_)
|
|
return;
|
|
if (seq <= coveredSeq_)
|
|
return;
|
|
|
|
uint32_t off = lolog::kHeaderSize;
|
|
std::vector<PendingEnt> group;
|
|
while (off + 8 <= lolog::kSectorSize) {
|
|
uint8_t hdr[8];
|
|
if (!readBytes(sector * lolog::kSectorSize + off, hdr, 8))
|
|
break;
|
|
uint16_t plen = (uint16_t)hdr[0] | ((uint16_t)hdr[1] << 8);
|
|
if (plen == lolog::kEntryEnd)
|
|
break;
|
|
uint8_t type = hdr[2];
|
|
uint8_t flags = hdr[3];
|
|
uint32_t entLen = 8 + plen;
|
|
uint32_t padded = (entLen + 3) & ~3u;
|
|
if (off + padded > lolog::kSectorSize)
|
|
break;
|
|
std::vector<uint8_t> payload(plen);
|
|
if (plen > 0 && !readBytes(sector * lolog::kSectorSize + off + 4, payload.data(), plen))
|
|
break;
|
|
uint32_t stored;
|
|
if (!readBytes(sector * lolog::kSectorSize + off + 4 + plen, &stored, 4))
|
|
break;
|
|
if (crc32(payload.data(), plen) != stored)
|
|
break;
|
|
PendingEnt pe{type, std::move(payload)};
|
|
group.push_back(std::move(pe));
|
|
off += padded;
|
|
if (flags & lolog::kFlagGroupEnd) {
|
|
for (auto &e : group)
|
|
applyEntry(e.type, e.payload.data(), e.payload.size());
|
|
group.clear();
|
|
}
|
|
}
|
|
}
|
|
|
|
void LoLog::rebuildIndexFromFlash()
|
|
{
|
|
nameIndex_.clear();
|
|
byInode_.clear();
|
|
dirParent_.clear();
|
|
dirParent_.resize(1);
|
|
dirParent_[0] = 0;
|
|
sectorUsed_.assign(dev_.sectorCount(), 0);
|
|
liveInSector_.assign(dev_.sectorCount(), 0);
|
|
liveBytes_ = 0;
|
|
epoch_ = 0;
|
|
coveredSeq_ = 0;
|
|
activeSector_ = 0xFFFF;
|
|
|
|
uint32_t maxEpoch = 0;
|
|
for (uint32_t s = 0; s < dev_.sectorCount(); s++) {
|
|
uint32_t magic, ep, seq;
|
|
uint8_t kind = 0;
|
|
if (!readSectorHeader(s, magic, ep, seq, kind))
|
|
continue;
|
|
if (ep > maxEpoch)
|
|
maxEpoch = ep;
|
|
}
|
|
if (maxEpoch == 0) {
|
|
uint8_t probe;
|
|
if (readBytes(0, &probe, 1) && probe == 0xFF)
|
|
return;
|
|
}
|
|
epoch_ = maxEpoch;
|
|
indexRunSeqs_.clear();
|
|
|
|
for (uint32_t s = 0; s < dev_.sectorCount(); s++) {
|
|
uint32_t magic, ep, seq;
|
|
uint8_t kind = 0;
|
|
if (!readSectorHeader(s, magic, ep, seq, kind))
|
|
continue;
|
|
if (ep != epoch_)
|
|
continue;
|
|
sectorUsed_[s] = 1;
|
|
uint32_t end = lolog::kSectorSize;
|
|
uint8_t scan[8];
|
|
uint32_t o = lolog::kHeaderSize;
|
|
while (o + 8 <= lolog::kSectorSize) {
|
|
if (!readBytes(s * lolog::kSectorSize + o, scan, 8))
|
|
break;
|
|
uint16_t plen = (uint16_t)scan[0] | ((uint16_t)scan[1] << 8);
|
|
if (plen == lolog::kEntryEnd) {
|
|
end = o;
|
|
break;
|
|
}
|
|
uint32_t padded = (8 + plen + 3) & ~3u;
|
|
if (o + padded > lolog::kSectorSize)
|
|
break;
|
|
o += padded;
|
|
}
|
|
liveInSector_[s] = end;
|
|
liveBytes_ += end;
|
|
if (kind == lolog::kSegIndex)
|
|
indexRunSeqs_.push_back(seq);
|
|
}
|
|
|
|
const bool hadIndex = loadNewestIndexRun();
|
|
const uint32_t replayFrom = coveredSeq_;
|
|
for (uint32_t s = 0; s < dev_.sectorCount(); s++) {
|
|
uint32_t magic, ep, seq;
|
|
uint8_t kind = 0;
|
|
if (!readSectorHeader(s, magic, ep, seq, kind))
|
|
continue;
|
|
if (ep != epoch_ || kind != lolog::kSegData)
|
|
continue;
|
|
if (hadIndex && seq <= replayFrom)
|
|
continue;
|
|
replaySector(s);
|
|
}
|
|
|
|
for (uint32_t s = 0; s < dev_.sectorCount(); s++) {
|
|
uint32_t magic, ep, seq;
|
|
uint8_t kind = 0;
|
|
if (readSectorHeader(s, magic, ep, seq, kind) && kind == lolog::kSegData && ep == epoch_) {
|
|
if (activeSector_ == 0xFFFF || seq >= nextSeq_) {
|
|
activeSector_ = (uint16_t)s;
|
|
activeOff_ = liveInSector_[s];
|
|
nextSeq_ = seq + 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool LoLog::mount()
|
|
{
|
|
if (!dev_.begin())
|
|
return false;
|
|
mounted_ = true;
|
|
rebuildIndexFromFlash();
|
|
nextDirId_ = std::max(nextDirId_, (uint32_t)dirParent_.size());
|
|
for (const auto &m : byInode_)
|
|
if (m.inodeId >= nextInodeId_)
|
|
nextInodeId_ = m.inodeId + 1;
|
|
return true;
|
|
}
|
|
|
|
bool LoLog::format()
|
|
{
|
|
if (!dev_.begin())
|
|
return false;
|
|
for (uint32_t s = 0; s < dev_.sectorCount(); s++)
|
|
dev_.eraseSector(s);
|
|
epoch_ = (uint32_t)lologNowMs() | 1;
|
|
nextSeq_ = 1;
|
|
nextDirId_ = 1;
|
|
nextInodeId_ = 1;
|
|
coveredSeq_ = 0;
|
|
nameIndex_.clear();
|
|
byInode_.clear();
|
|
dirParent_.clear();
|
|
dirParent_.resize(1);
|
|
dirParent_[0] = 0;
|
|
sectorUsed_.assign(dev_.sectorCount(), 0);
|
|
liveInSector_.assign(dev_.sectorCount(), 0);
|
|
liveBytes_ = 0;
|
|
activeSector_ = 0xFFFF;
|
|
mounted_ = true;
|
|
return allocDataSector();
|
|
}
|
|
|
|
uint32_t LoLog::nowMs() const
|
|
{
|
|
return lologNowMs();
|
|
}
|
|
|
|
uint64_t LoLog::totalBytes() const
|
|
{
|
|
return (uint64_t)dev_.sectorCount() * dev_.sectorSize();
|
|
}
|
|
|
|
uint64_t LoLog::usedBytes() const
|
|
{
|
|
return liveBytes_;
|
|
}
|
|
|
|
void LoLog::spaceHint(uint32_t *blockOut, uint32_t *slackOut) const
|
|
{
|
|
if (blockOut)
|
|
*blockOut = 256;
|
|
if (slackOut)
|
|
*slackOut = (lolog::kHeadroomSectors + 1) * lolog::kSectorSize;
|
|
}
|
|
|
|
uint32_t LoLog::freeSectors() const
|
|
{
|
|
uint32_t n = 0;
|
|
for (uint32_t s = 0; s < sectorUsed_.size(); s++)
|
|
if (!sectorUsed_[s])
|
|
n++;
|
|
return n;
|
|
}
|
|
|
|
bool LoLog::hasRoomFor(uint32_t bytes) const
|
|
{
|
|
const uint64_t reserve = (uint64_t)(lolog::kHeadroomSectors + 1) * lolog::kSectorSize;
|
|
const uint64_t cap = totalBytes();
|
|
return liveBytes_ + bytes + reserve <= cap;
|
|
}
|
|
|
|
bool LoLog::lookupName(uint32_t dirId, const char *name, NameVal &out) const
|
|
{
|
|
NameKey k{dirId, name ? name : ""};
|
|
for (const auto &p : nameIndex_) {
|
|
if (p.first.dirId == k.dirId && p.first.name == k.name) {
|
|
out = p.second;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void LoLog::upsertName(uint32_t dirId, const char *name, const NameVal &val)
|
|
{
|
|
NameKey k{dirId, name ? name : ""};
|
|
for (auto &p : nameIndex_) {
|
|
if (p.first.dirId == k.dirId && p.first.name == k.name) {
|
|
p.second = val;
|
|
return;
|
|
}
|
|
}
|
|
nameIndex_.push_back({k, val});
|
|
}
|
|
|
|
void LoLog::eraseName(uint32_t dirId, const char *name)
|
|
{
|
|
NameKey k{dirId, name ? name : ""};
|
|
nameIndex_.erase(
|
|
std::remove_if(nameIndex_.begin(), nameIndex_.end(),
|
|
[&](const std::pair<NameKey, NameVal> &p) {
|
|
return p.first.dirId == k.dirId && p.first.name == k.name;
|
|
}),
|
|
nameIndex_.end());
|
|
}
|
|
|
|
LoLog::FileMeta *LoLog::metaByInode(uint32_t inodeId)
|
|
{
|
|
return inodeId < byInode_.size() ? &byInode_[inodeId] : nullptr;
|
|
}
|
|
|
|
const LoLog::FileMeta *LoLog::metaByInode(uint32_t inodeId) const
|
|
{
|
|
return inodeId < byInode_.size() ? &byInode_[inodeId] : nullptr;
|
|
}
|
|
|
|
void LoLog::tombDir(uint32_t dirId)
|
|
{
|
|
nameIndex_.erase(
|
|
std::remove_if(nameIndex_.begin(), nameIndex_.end(),
|
|
[&](const std::pair<NameKey, NameVal> &p) {
|
|
return p.second.isDir && p.second.id == dirId;
|
|
}),
|
|
nameIndex_.end());
|
|
}
|
|
|
|
bool LoLog::dirEmpty(uint32_t dirId) const
|
|
{
|
|
for (const auto &p : nameIndex_) {
|
|
if (p.first.dirId == dirId)
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static void splitPath(const char *rel, std::vector<std::string> &parts)
|
|
{
|
|
parts.clear();
|
|
if (!rel)
|
|
return;
|
|
while (*rel == '/')
|
|
rel++;
|
|
std::string cur;
|
|
for (; *rel; rel++) {
|
|
if (*rel == '/') {
|
|
if (!cur.empty()) {
|
|
parts.push_back(cur);
|
|
cur.clear();
|
|
}
|
|
} else
|
|
cur += *rel;
|
|
}
|
|
if (!cur.empty())
|
|
parts.push_back(cur);
|
|
}
|
|
|
|
bool LoLog::resolvePath(const char *relPath, uint32_t &dirIdOut, std::string &nameOut, bool createDirs)
|
|
{
|
|
std::vector<std::string> parts;
|
|
splitPath(relPath, parts);
|
|
if (parts.empty())
|
|
return false;
|
|
uint32_t dirId = lolog::kRootDirId;
|
|
for (size_t i = 0; i + 1 < parts.size(); i++) {
|
|
NameVal v;
|
|
if (!lookupName(dirId, parts[i].c_str(), v) || !v.isDir)
|
|
return false;
|
|
dirId = v.id;
|
|
}
|
|
nameOut = parts.back();
|
|
dirIdOut = dirId;
|
|
if (createDirs) {
|
|
(void)createDirs;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool LoLog::exists(const char *relPath)
|
|
{
|
|
uint32_t dirId;
|
|
std::string name;
|
|
if (!resolvePath(relPath, dirId, name, false))
|
|
return strcmp(relPath, "/") == 0 || (relPath && relPath[0] == '\0');
|
|
NameVal v;
|
|
return lookupName(dirId, name.c_str(), v);
|
|
}
|
|
|
|
bool LoLog::isDirectory(const char *relPath)
|
|
{
|
|
uint32_t dirId;
|
|
std::string name;
|
|
if (!resolvePath(relPath, dirId, name, false))
|
|
return relPath && (strcmp(relPath, "/") == 0 || relPath[0] == '\0');
|
|
NameVal v;
|
|
if (!lookupName(dirId, name.c_str(), v))
|
|
return false;
|
|
return v.isDir;
|
|
}
|
|
|
|
bool LoLog::statPath(const char *relPath, uint32_t *sizeOut, bool *isDirOut)
|
|
{
|
|
if (sizeOut)
|
|
*sizeOut = 0;
|
|
if (isDirOut)
|
|
*isDirOut = false;
|
|
if (relPath && (strcmp(relPath, "/") == 0 || relPath[0] == '\0')) {
|
|
if (isDirOut)
|
|
*isDirOut = true;
|
|
return true;
|
|
}
|
|
uint32_t dirId;
|
|
std::string name;
|
|
if (!resolvePath(relPath, dirId, name, false))
|
|
return false;
|
|
NameVal v;
|
|
if (!lookupName(dirId, name.c_str(), v))
|
|
return false;
|
|
if (isDirOut)
|
|
*isDirOut = v.isDir;
|
|
if (!v.isDir && sizeOut) {
|
|
const FileMeta *m = metaByInode(v.id);
|
|
if (m)
|
|
*sizeOut = m->size;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool LoLog::mkdir(const char *relPath)
|
|
{
|
|
std::vector<std::string> parts;
|
|
splitPath(relPath, parts);
|
|
if (parts.empty())
|
|
return false;
|
|
uint32_t dirId = lolog::kRootDirId;
|
|
for (size_t i = 0; i + 1 < parts.size(); i++) {
|
|
NameVal v;
|
|
if (!lookupName(dirId, parts[i].c_str(), v)) {
|
|
std::string sub;
|
|
for (size_t j = 0; j <= i; j++) {
|
|
if (j)
|
|
sub += '/';
|
|
sub += parts[j];
|
|
}
|
|
if (!mkdir(sub.c_str()))
|
|
return false;
|
|
if (!lookupName(dirId, parts[i].c_str(), v) || !v.isDir)
|
|
return false;
|
|
}
|
|
dirId = v.id;
|
|
}
|
|
uint32_t parentDir = dirId;
|
|
std::string name = parts.back();
|
|
NameVal existing;
|
|
if (lookupName(parentDir, name.c_str(), existing))
|
|
return false;
|
|
uint32_t newDir = nextDirId_++;
|
|
if (newDir >= dirParent_.size())
|
|
dirParent_.resize(newDir + 1, lolog::kRootDirId);
|
|
dirParent_[newDir] = parentDir;
|
|
beginGroup();
|
|
uint8_t pl[8 + 64];
|
|
pl[0] = (uint8_t)parentDir;
|
|
pl[1] = (uint8_t)(parentDir >> 8);
|
|
pl[2] = (uint8_t)(parentDir >> 16);
|
|
pl[3] = (uint8_t)(parentDir >> 24);
|
|
pl[4] = (uint8_t)newDir;
|
|
pl[5] = (uint8_t)(newDir >> 8);
|
|
pl[6] = (uint8_t)(newDir >> 16);
|
|
pl[7] = (uint8_t)(newDir >> 24);
|
|
strncpy((char *)(pl + 8), name.c_str(), 63);
|
|
pendingGroup_.push_back({lolog::kEntDir, std::vector<uint8_t>(pl, pl + 8 + strlen((char *)(pl + 8)) + 1)});
|
|
if (!endGroup())
|
|
return false;
|
|
upsertName(parentDir, name.c_str(), NameVal{true, newDir});
|
|
return true;
|
|
}
|
|
|
|
bool LoLog::remove(const char *relPath)
|
|
{
|
|
uint32_t dirId;
|
|
std::string name;
|
|
if (!resolvePath(relPath, dirId, name, false))
|
|
return false;
|
|
NameVal v;
|
|
if (!lookupName(dirId, name.c_str(), v) || v.isDir)
|
|
return false;
|
|
beginGroup();
|
|
uint8_t pl[4 + 64];
|
|
pl[0] = (uint8_t)dirId;
|
|
pl[1] = (uint8_t)(dirId >> 8);
|
|
pl[2] = (uint8_t)(dirId >> 16);
|
|
pl[3] = (uint8_t)(dirId >> 24);
|
|
strncpy((char *)(pl + 4), name.c_str(), 63);
|
|
pendingGroup_.push_back(
|
|
{lolog::kEntTomb, std::vector<uint8_t>(pl, pl + 4 + strlen((char *)(pl + 4)) + 1)});
|
|
if (!endGroup())
|
|
return false;
|
|
eraseName(dirId, name.c_str());
|
|
return true;
|
|
}
|
|
|
|
bool LoLog::rename(const char *oldPath, const char *newPath)
|
|
{
|
|
uint32_t oDir, nDir;
|
|
std::string oName, nName;
|
|
if (!resolvePath(oldPath, oDir, oName, false) || !resolvePath(newPath, nDir, nName, false))
|
|
return false;
|
|
NameVal v;
|
|
if (!lookupName(oDir, oName.c_str(), v))
|
|
return false;
|
|
if (lookupName(nDir, nName.c_str(), v))
|
|
return false;
|
|
if (v.isDir) {
|
|
if (!mkdir(newPath))
|
|
return false;
|
|
return rmdir(oldPath, true);
|
|
}
|
|
FileMeta *m = metaByInode(v.id);
|
|
if (!m)
|
|
return false;
|
|
FileMeta copy = *m;
|
|
copy.dirId = nDir;
|
|
copy.name = nName;
|
|
if (!commitFileMeta(copy))
|
|
return false;
|
|
remove(oldPath);
|
|
return true;
|
|
}
|
|
|
|
bool LoLog::rmdir(const char *relPath, bool recursive)
|
|
{
|
|
uint32_t dirId;
|
|
std::string name;
|
|
if (!resolvePath(relPath, dirId, name, false))
|
|
return false;
|
|
NameVal v;
|
|
if (!lookupName(dirId, name.c_str(), v) || !v.isDir)
|
|
return false;
|
|
if (!recursive && !dirEmpty(v.id))
|
|
return false;
|
|
beginGroup();
|
|
uint8_t pl[4];
|
|
pl[0] = (uint8_t)v.id;
|
|
pl[1] = (uint8_t)(v.id >> 8);
|
|
pl[2] = (uint8_t)(v.id >> 16);
|
|
pl[3] = (uint8_t)(v.id >> 24);
|
|
pendingGroup_.push_back({lolog::kEntTomb, std::vector<uint8_t>(pl, pl + 4)});
|
|
if (!endGroup())
|
|
return false;
|
|
tombDir(v.id);
|
|
eraseName(dirId, name.c_str());
|
|
return true;
|
|
}
|
|
|
|
static uint32_t paddedEntrySize(size_t payloadLen)
|
|
{
|
|
const uint32_t entLen = (uint32_t)(8 + payloadLen);
|
|
return (entLen + 3u) & ~3u;
|
|
}
|
|
|
|
bool LoLog::commitFileMeta(FileMeta &meta)
|
|
{
|
|
const bool large = meta.data.size() > lolog::kInlineMax;
|
|
const size_t inlineBytes = large ? 0 : meta.data.size();
|
|
uint32_t groupNeed = paddedEntrySize(16 + meta.name.size() + 1 + inlineBytes);
|
|
if (large) {
|
|
size_t off = 0;
|
|
while (off < meta.data.size()) {
|
|
const size_t remain = meta.data.size() - off;
|
|
const size_t chunk = remain < 512 ? remain : 512;
|
|
groupNeed += paddedEntrySize(8 + chunk);
|
|
off += chunk;
|
|
}
|
|
}
|
|
if (!hasRoomFor(groupNeed))
|
|
return false;
|
|
if (meta.inodeId == 0) {
|
|
meta.inodeId = nextInodeId_++;
|
|
if (meta.inodeId >= byInode_.size())
|
|
byInode_.resize(meta.inodeId + 1);
|
|
}
|
|
meta.size = (uint32_t)meta.data.size();
|
|
meta.mtime = nowMs();
|
|
beginGroup();
|
|
std::vector<uint8_t> pl(16 + meta.name.size() + 1 + inlineBytes);
|
|
pl[0] = (uint8_t)meta.dirId;
|
|
pl[1] = (uint8_t)(meta.dirId >> 8);
|
|
pl[2] = (uint8_t)(meta.dirId >> 16);
|
|
pl[3] = (uint8_t)(meta.dirId >> 24);
|
|
pl[4] = (uint8_t)meta.inodeId;
|
|
pl[5] = (uint8_t)(meta.inodeId >> 8);
|
|
pl[6] = (uint8_t)(meta.inodeId >> 16);
|
|
pl[7] = (uint8_t)(meta.inodeId >> 24);
|
|
pl[8] = (uint8_t)meta.size;
|
|
pl[9] = (uint8_t)(meta.size >> 8);
|
|
pl[10] = (uint8_t)(meta.size >> 16);
|
|
pl[11] = (uint8_t)(meta.size >> 24);
|
|
pl[12] = (uint8_t)meta.mtime;
|
|
pl[13] = (uint8_t)(meta.mtime >> 8);
|
|
pl[14] = (uint8_t)(meta.mtime >> 16);
|
|
pl[15] = (uint8_t)(meta.mtime >> 24);
|
|
memcpy(pl.data() + 16, meta.name.c_str(), meta.name.size() + 1);
|
|
if (inlineBytes)
|
|
memcpy(pl.data() + 16 + meta.name.size() + 1, meta.data.data(), inlineBytes);
|
|
pendingGroup_.push_back({lolog::kEntFile, std::move(pl)});
|
|
if (large) {
|
|
size_t off = 0;
|
|
while (off < meta.data.size()) {
|
|
const size_t remain = meta.data.size() - off;
|
|
size_t chunk = remain < 512 ? remain : 512;
|
|
std::vector<uint8_t> ep(8 + chunk);
|
|
ep[0] = (uint8_t)meta.inodeId;
|
|
ep[1] = (uint8_t)(meta.inodeId >> 8);
|
|
ep[2] = (uint8_t)(meta.inodeId >> 16);
|
|
ep[3] = (uint8_t)(meta.inodeId >> 24);
|
|
ep[4] = (uint8_t)off;
|
|
ep[5] = (uint8_t)(off >> 8);
|
|
ep[6] = (uint8_t)(off >> 16);
|
|
ep[7] = (uint8_t)(off >> 24);
|
|
memcpy(ep.data() + 8, meta.data.data() + off, chunk);
|
|
pendingGroup_.push_back({lolog::kEntExtent, std::move(ep)});
|
|
off += chunk;
|
|
}
|
|
}
|
|
if (!endGroup())
|
|
return false;
|
|
byInode_[meta.inodeId] = meta;
|
|
upsertName(meta.dirId, meta.name.c_str(), NameVal{false, meta.inodeId});
|
|
return true;
|
|
}
|
|
|
|
LoLog::OpenHandle *LoLog::open(const char *relPath, bool write, bool create)
|
|
{
|
|
uint32_t dirId;
|
|
std::string name;
|
|
if (!resolvePath(relPath, dirId, name, false)) {
|
|
if (!create || !relPath)
|
|
return nullptr;
|
|
if (!resolvePath(relPath, dirId, name, false))
|
|
return nullptr;
|
|
}
|
|
NameVal v;
|
|
if (!lookupName(dirId, name.c_str(), v)) {
|
|
if (!write || !create)
|
|
return nullptr;
|
|
FileMeta meta;
|
|
meta.dirId = dirId;
|
|
meta.name = name;
|
|
meta.data.clear();
|
|
if (!commitFileMeta(meta))
|
|
return nullptr;
|
|
v = NameVal{false, meta.inodeId};
|
|
}
|
|
if (v.isDir)
|
|
return nullptr;
|
|
auto *h = new OpenHandle();
|
|
h->inodeId = v.id;
|
|
h->write = write;
|
|
h->pos = 0;
|
|
FileMeta *m = metaByInode(v.id);
|
|
if (m && write)
|
|
h->writeBuf = m->data;
|
|
strncpy(h->nameBuf, name.c_str(), sizeof(h->nameBuf) - 1);
|
|
return h;
|
|
}
|
|
|
|
void LoLog::closeHandle(OpenHandle *h)
|
|
{
|
|
if (!h)
|
|
return;
|
|
if (h->write && h->dirty) {
|
|
FileMeta *m = metaByInode(h->inodeId);
|
|
if (m) {
|
|
m->data = h->writeBuf;
|
|
commitFileMeta(*m);
|
|
}
|
|
}
|
|
delete h;
|
|
}
|
|
|
|
size_t LoLog::readHandle(OpenHandle *h, uint8_t *buf, size_t len)
|
|
{
|
|
if (!h || h->isDirIter || !buf)
|
|
return 0;
|
|
const FileMeta *m = metaByInode(h->inodeId);
|
|
if (!m)
|
|
return 0;
|
|
if (h->pos >= m->data.size())
|
|
return 0;
|
|
const size_t avail = m->data.size() - (size_t)h->pos;
|
|
size_t n = len < avail ? len : avail;
|
|
memcpy(buf, m->data.data() + h->pos, n);
|
|
h->pos += (uint32_t)n;
|
|
return n;
|
|
}
|
|
|
|
size_t LoLog::writeHandle(OpenHandle *h, const uint8_t *buf, size_t len)
|
|
{
|
|
if (!h || !h->write || !buf)
|
|
return 0;
|
|
static constexpr size_t kWriteBufMax = 65536;
|
|
if (h->pos + len > kWriteBufMax)
|
|
len = kWriteBufMax - h->pos;
|
|
if (h->writeBuf.size() < h->pos + len)
|
|
h->writeBuf.resize(h->pos + len);
|
|
memcpy(h->writeBuf.data() + h->pos, buf, len);
|
|
h->pos += (uint32_t)len;
|
|
h->dirty = true;
|
|
return len;
|
|
}
|
|
|
|
bool LoLog::seekHandle(OpenHandle *h, uint32_t pos)
|
|
{
|
|
if (!h || h->isDirIter)
|
|
return false;
|
|
h->pos = pos;
|
|
return true;
|
|
}
|
|
|
|
uint32_t LoLog::sizeHandle(OpenHandle *h) const
|
|
{
|
|
if (!h || h->isDirIter)
|
|
return 0;
|
|
const FileMeta *m = metaByInode(h->inodeId);
|
|
return m ? m->size : 0;
|
|
}
|
|
|
|
bool LoLog::isDirHandle(OpenHandle *h) const
|
|
{
|
|
return h && h->isDirIter;
|
|
}
|
|
|
|
void LoLog::flushHandle(OpenHandle *h)
|
|
{
|
|
if (!h || !h->write || !h->dirty)
|
|
return;
|
|
FileMeta *m = metaByInode(h->inodeId);
|
|
if (m) {
|
|
m->data = h->writeBuf;
|
|
commitFileMeta(*m);
|
|
h->dirty = false;
|
|
}
|
|
}
|
|
|
|
const char *LoLog::nameHandle(OpenHandle *h) const
|
|
{
|
|
return h ? h->nameBuf : "";
|
|
}
|
|
|
|
LoLog::OpenHandle *LoLog::openDir(const char *relPath)
|
|
{
|
|
uint32_t dirId = lolog::kRootDirId;
|
|
std::vector<std::string> parts;
|
|
splitPath(relPath, parts);
|
|
for (const auto &p : parts) {
|
|
NameVal v;
|
|
if (!lookupName(dirId, p.c_str(), v) || !v.isDir)
|
|
return nullptr;
|
|
dirId = v.id;
|
|
}
|
|
auto *h = new OpenHandle();
|
|
h->isDirIter = true;
|
|
h->dirId = dirId;
|
|
h->dirIdx = 0;
|
|
return h;
|
|
}
|
|
|
|
LoLog::OpenHandle *LoLog::openNextInDir(OpenHandle *dir)
|
|
{
|
|
if (!dir || !dir->isDirIter)
|
|
return nullptr;
|
|
while (dir->dirIdx < nameIndex_.size()) {
|
|
const auto &p = nameIndex_[dir->dirIdx++];
|
|
if (p.first.dirId != dir->dirId)
|
|
continue;
|
|
auto *f = new OpenHandle();
|
|
f->isDirIter = false;
|
|
f->inodeId = p.second.isDir ? 0 : p.second.id;
|
|
if (p.second.isDir)
|
|
f->isDirIter = true;
|
|
strncpy(f->nameBuf, p.first.name.c_str(), sizeof(f->nameBuf) - 1);
|
|
if (p.second.isDir) {
|
|
f->dirId = p.second.id;
|
|
f->isDirIter = true;
|
|
}
|
|
return f;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
bool LoLog::writeAtPath(const char *relPath, uint32_t offset, const uint8_t *data, size_t len)
|
|
{
|
|
OpenHandle *h = open(relPath, true, true);
|
|
if (!h)
|
|
return false;
|
|
if (offset > 0)
|
|
seekHandle(h, offset);
|
|
size_t w = writeHandle(h, data, len);
|
|
flushHandle(h);
|
|
closeHandle(h);
|
|
return w == len;
|
|
}
|
|
|
|
static constexpr uint32_t kIndexBodyTag = 0x49524958; // IRIX
|
|
|
|
bool LoLog::allocIndexSector(uint32_t §orOut, uint32_t runSeq)
|
|
{
|
|
const uint32_t n = dev_.sectorCount();
|
|
if (n == 0)
|
|
return false;
|
|
static uint32_t rrIdx = 0;
|
|
for (uint32_t i = 0; i < n; i++) {
|
|
uint32_t s = (rrIdx + i) % n;
|
|
if (sectorUsed_[s])
|
|
continue;
|
|
uint32_t ec = dev_.eraseCount(s);
|
|
(void)ec;
|
|
if (!dev_.eraseSector(s))
|
|
continue;
|
|
sectorUsed_[s] = 1;
|
|
liveInSector_[s] = lolog::kHeaderSize;
|
|
rrIdx = (s + 1) % n;
|
|
if (!writeSectorHeader(s, runSeq, lolog::kSegIndex))
|
|
return false;
|
|
sectorOut = s;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool LoLog::flushIndexRun()
|
|
{
|
|
uint32_t sector = 0;
|
|
const uint32_t runSeq = nextSeq_++;
|
|
if (!allocIndexSector(sector, runSeq))
|
|
return false;
|
|
|
|
std::vector<uint8_t> body;
|
|
body.resize(32);
|
|
body[0] = (uint8_t)kIndexBodyTag;
|
|
body[1] = (uint8_t)(kIndexBodyTag >> 8);
|
|
body[2] = (uint8_t)(kIndexBodyTag >> 16);
|
|
body[3] = (uint8_t)(kIndexBodyTag >> 24);
|
|
const uint32_t cov = nextSeq_ > 1 ? nextSeq_ - 1 : 0;
|
|
memcpy(body.data() + 4, &cov, 4);
|
|
memcpy(body.data() + 8, &nextDirId_, 4);
|
|
memcpy(body.data() + 12, &nextInodeId_, 4);
|
|
memcpy(body.data() + 16, &liveBytes_, 8);
|
|
const uint32_t nNames = (uint32_t)nameIndex_.size();
|
|
const uint32_t nDead = (uint32_t)deadDirIds_.size();
|
|
memcpy(body.data() + 24, &nNames, 4);
|
|
memcpy(body.data() + 28, &nDead, 4);
|
|
|
|
for (const auto &p : nameIndex_) {
|
|
const uint32_t dirId = p.first.dirId;
|
|
const uint32_t nameLen = (uint32_t)p.first.name.size();
|
|
const uint8_t isDir = p.second.isDir ? 1 : 0;
|
|
const uint32_t id = p.second.id;
|
|
size_t o = body.size();
|
|
body.resize(o + 4 + 4 + nameLen + 1 + 1 + 4);
|
|
memcpy(body.data() + o, &dirId, 4);
|
|
o += 4;
|
|
memcpy(body.data() + o, &nameLen, 4);
|
|
o += 4;
|
|
memcpy(body.data() + o, p.first.name.c_str(), nameLen + 1);
|
|
o += nameLen + 1;
|
|
body.data()[o++] = isDir;
|
|
memcpy(body.data() + o, &id, 4);
|
|
}
|
|
for (uint32_t d : deadDirIds_) {
|
|
size_t o = body.size();
|
|
body.resize(o + 4);
|
|
memcpy(body.data() + o, &d, 4);
|
|
}
|
|
uint32_t nDirParent = 0;
|
|
for (size_t i = 1; i < dirParent_.size(); i++)
|
|
nDirParent++;
|
|
size_t oDp = body.size();
|
|
body.resize(oDp + 4);
|
|
memcpy(body.data() + oDp, &nDirParent, 4);
|
|
for (size_t i = 1; i < dirParent_.size(); i++) {
|
|
uint32_t dirId = (uint32_t)i;
|
|
uint32_t parent = dirParent_[i];
|
|
size_t o = body.size();
|
|
body.resize(o + 8);
|
|
memcpy(body.data() + o, &dirId, 4);
|
|
memcpy(body.data() + o + 4, &parent, 4);
|
|
}
|
|
|
|
const uint32_t bodyCrc = crc32(body.data(), body.size());
|
|
body.resize(body.size() + 4);
|
|
memcpy(body.data() + body.size() - 4, &bodyCrc, 4);
|
|
|
|
uint32_t off = lolog::kHeaderSize;
|
|
const uint32_t padded = (uint32_t)((body.size() + 3) & ~3u);
|
|
if (off + padded > lolog::kSectorSize)
|
|
return false;
|
|
std::vector<uint8_t> pad(padded, 0xFF);
|
|
memcpy(pad.data(), body.data(), body.size());
|
|
if (!progBytes(sector * lolog::kSectorSize + off, pad.data(), padded))
|
|
return false;
|
|
liveInSector_[sector] = off + padded;
|
|
liveBytes_ += padded;
|
|
coveredSeq_ = cov;
|
|
indexRunSeqs_.push_back(runSeq);
|
|
memtableBytes_ = 0;
|
|
lastFlushMs_ = nowMs();
|
|
mergeIndexRunsIfNeeded();
|
|
return true;
|
|
}
|
|
|
|
void LoLog::mergeIndexRunsIfNeeded()
|
|
{
|
|
if (indexRunSeqs_.size() <= lolog::kMaxRuns)
|
|
return;
|
|
const uint32_t keep = indexRunSeqs_.back();
|
|
indexRunSeqs_.clear();
|
|
indexRunSeqs_.push_back(keep);
|
|
}
|
|
|
|
bool LoLog::loadNewestIndexRun()
|
|
{
|
|
uint32_t bestSeq = 0;
|
|
uint32_t bestSec = 0xFFFFFFFF;
|
|
for (uint32_t s = 0; s < dev_.sectorCount(); s++) {
|
|
uint32_t magic, ep, seq;
|
|
uint8_t kind = 0;
|
|
if (!readSectorHeader(s, magic, ep, seq, kind))
|
|
continue;
|
|
if (ep != epoch_ || kind != lolog::kSegIndex)
|
|
continue;
|
|
if (seq >= bestSeq) {
|
|
bestSeq = seq;
|
|
bestSec = s;
|
|
}
|
|
}
|
|
if (bestSec == 0xFFFFFFFF)
|
|
return false;
|
|
|
|
std::vector<uint8_t> body(lolog::kSectorSize - lolog::kHeaderSize);
|
|
if (!readBytes(bestSec * lolog::kSectorSize + lolog::kHeaderSize, body.data(), body.size()))
|
|
return false;
|
|
uint32_t tag = (uint32_t)body[0] | ((uint32_t)body[1] << 8) | ((uint32_t)body[2] << 16) | ((uint32_t)body[3] << 24);
|
|
if (tag != kIndexBodyTag)
|
|
return false;
|
|
uint32_t cov = 0, nd = 0, ni = 0, nNames = 0, nDead = 0;
|
|
memcpy(&cov, body.data() + 4, 4);
|
|
memcpy(&nd, body.data() + 8, 4);
|
|
memcpy(&ni, body.data() + 12, 4);
|
|
memcpy(&liveBytes_, body.data() + 16, 8);
|
|
memcpy(&nNames, body.data() + 24, 4);
|
|
memcpy(&nDead, body.data() + 28, 4);
|
|
coveredSeq_ = cov;
|
|
nextDirId_ = nd;
|
|
nextInodeId_ = ni;
|
|
size_t o = 32;
|
|
nameIndex_.clear();
|
|
deadDirIds_.clear();
|
|
for (uint32_t i = 0; i < nNames && o + 13 < body.size(); i++) {
|
|
uint32_t dirId = 0, nameLen = 0, id = 0;
|
|
memcpy(&dirId, body.data() + o, 4);
|
|
o += 4;
|
|
memcpy(&nameLen, body.data() + o, 4);
|
|
o += 4;
|
|
if (o + nameLen + 1 + 1 + 4 > body.size())
|
|
break;
|
|
std::string name((char *)(body.data() + o), nameLen);
|
|
o += nameLen + 1;
|
|
bool isDir = body[o++] != 0;
|
|
memcpy(&id, body.data() + o, 4);
|
|
o += 4;
|
|
upsertName(dirId, name.c_str(), NameVal{isDir, id});
|
|
}
|
|
for (uint32_t i = 0; i < nDead && o + 4 <= body.size(); i++) {
|
|
uint32_t d = 0;
|
|
memcpy(&d, body.data() + o, 4);
|
|
o += 4;
|
|
deadDirIds_.push_back(d);
|
|
}
|
|
uint32_t nDirParent = 0;
|
|
if (o + 4 <= body.size()) {
|
|
memcpy(&nDirParent, body.data() + o, 4);
|
|
o += 4;
|
|
}
|
|
for (uint32_t i = 0; i < nDirParent && o + 8 <= body.size(); i++) {
|
|
uint32_t dirId = 0, parent = 0;
|
|
memcpy(&dirId, body.data() + o, 4);
|
|
memcpy(&parent, body.data() + o + 4, 4);
|
|
o += 8;
|
|
if (dirId >= dirParent_.size())
|
|
dirParent_.resize(dirId + 1, lolog::kRootDirId);
|
|
dirParent_[dirId] = parent;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool LoLog::copyLiveGroupsFromSector(uint32_t sector)
|
|
{
|
|
uint32_t magic, epoch, seq;
|
|
uint8_t kind = 0;
|
|
if (!readSectorHeader(sector, magic, epoch, seq, kind) || kind != lolog::kSegData)
|
|
return false;
|
|
if (epoch != epoch_)
|
|
return false;
|
|
|
|
uint32_t off = lolog::kHeaderSize;
|
|
std::vector<PendingEnt> group;
|
|
while (off + 8 <= lolog::kSectorSize) {
|
|
uint8_t hdr[8];
|
|
if (!readBytes(sector * lolog::kSectorSize + off, hdr, 8))
|
|
break;
|
|
uint16_t plen = (uint16_t)hdr[0] | ((uint16_t)hdr[1] << 8);
|
|
if (plen == lolog::kEntryEnd)
|
|
break;
|
|
uint8_t type = hdr[2];
|
|
uint8_t flags = hdr[3];
|
|
uint32_t padded = (8 + plen + 3) & ~3u;
|
|
if (off + padded > lolog::kSectorSize)
|
|
break;
|
|
std::vector<uint8_t> payload(plen);
|
|
if (plen && !readBytes(sector * lolog::kSectorSize + off + 4, payload.data(), plen))
|
|
break;
|
|
group.push_back({type, std::move(payload)});
|
|
off += padded;
|
|
if (flags & lolog::kFlagGroupEnd) {
|
|
beginGroup();
|
|
for (auto &e : group)
|
|
pendingGroup_.push_back(std::move(e));
|
|
group.clear();
|
|
if (!endGroup())
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void LoLog::cleanOneSector()
|
|
{
|
|
if (freeSectors() <= lolog::kHeadroomSectors)
|
|
return;
|
|
uint32_t best = 0xFFFFFFFF;
|
|
uint32_t bestDead = 0;
|
|
uint32_t bestEc = UINT32_MAX;
|
|
for (uint32_t s = 0; s < sectorUsed_.size(); s++) {
|
|
if (!sectorUsed_[s] || s == activeSector_)
|
|
continue;
|
|
uint32_t magic, ep, seq;
|
|
uint8_t kind = 0;
|
|
if (!readSectorHeader(s, magic, ep, seq, kind) || kind != lolog::kSegData)
|
|
continue;
|
|
uint32_t live = liveInSector_[s];
|
|
uint32_t dead = lolog::kSectorSize > live ? lolog::kSectorSize - live : 0;
|
|
uint32_t ec = dev_.eraseCount(s);
|
|
if (dead > bestDead || (dead == bestDead && ec < bestEc)) {
|
|
bestDead = dead;
|
|
bestEc = ec;
|
|
best = s;
|
|
}
|
|
}
|
|
if (best == 0xFFFFFFFF || bestDead < lolog::kSectorSize / 4)
|
|
return;
|
|
if (!copyLiveGroupsFromSector(best))
|
|
return;
|
|
const uint32_t freed = liveInSector_[best];
|
|
dev_.eraseSector(best);
|
|
sectorUsed_[best] = 0;
|
|
liveInSector_[best] = 0;
|
|
if (liveBytes_ >= freed)
|
|
liveBytes_ -= freed;
|
|
}
|
|
|
|
void LoLog::flushIndexIfNeeded()
|
|
{
|
|
memtableBytes_ = (uint32_t)(nameIndex_.size() * 48);
|
|
const uint32_t now = nowMs();
|
|
if (memtableBytes_ >= lolog::kMemtableBytes ||
|
|
(lastFlushMs_ && now - lastFlushMs_ > kIdleFlushMs))
|
|
flushIndexRun();
|
|
}
|
|
|
|
void LoLog::maintain(uint32_t budgetMs)
|
|
{
|
|
(void)budgetMs;
|
|
flushIndexIfNeeded();
|
|
cleanOneSector();
|
|
}
|
|
|
|
#include "core/LoBBSStackGuard.h"
|