libcryfs/src/cryfs/impl/filesystem/fsblobstore/utils/DirEntryList.cpp

264 lines
10 KiB
C++

#include "DirEntryList.h"
#include <limits>
#include <cpp-utils/system/time.h>
//TODO Get rid of that in favor of better error handling
#include <fspp/fs_interface/FuseErrnoException.h>
using cpputils::Data;
using std::string;
using std::vector;
using blockstore::BlockId;
namespace cryfs {
namespace fsblobstore {
DirEntryList::DirEntryList() : _entries() {
}
Data DirEntryList::serialize() const {
Data serialized(_serializedSize());
unsigned int offset = 0;
for (auto iter = _entries.begin(); iter != _entries.end(); ++iter) {
ASSERT(iter == _entries.begin() || std::less<BlockId>()((iter-1)->blockId(), iter->blockId()), "Invariant hurt: Directory entries should be ordered by blockId and not have duplicate blockIds.");
iter->serialize(static_cast<uint8_t*>(serialized.dataOffset(offset)));
offset += iter->serializedSize();
}
return serialized;
}
uint64_t DirEntryList::_serializedSize() const {
uint64_t serializedSize = 0;
for (const auto &entry : _entries) {
serializedSize += entry.serializedSize();
}
return serializedSize;
}
void DirEntryList::deserializeFrom(const void *data, uint64_t size) {
_entries.clear();
const char *pos = static_cast<const char*>(data);
while (pos < static_cast<const char*>(data) + size) {
pos = DirEntry::deserializeAndAddToVector(pos, &_entries);
ASSERT(_entries.size() == 1 || std::less<BlockId>()(_entries[_entries.size()-2].blockId(), _entries[_entries.size()-1].blockId()), "Invariant hurt: Directory entries should be ordered by blockId and not have duplicate blockIds.");
}
}
bool DirEntryList::_hasChild(const string &name) const {
return _entries.end() != _findByName(name);
}
void DirEntryList::add(const string &name, const BlockId &blobId, fspp::Dir::EntryType entryType, fspp::mode_t mode,
fspp::uid_t uid, fspp::gid_t gid, timespec lastAccessTime, timespec lastModificationTime) {
if (_hasChild(name)) {
throw fspp::fuse::FuseErrnoException(EEXIST);
}
_add(name, blobId, entryType, mode, uid, gid, lastAccessTime, lastModificationTime);
}
void DirEntryList::_add(const string &name, const BlockId &blobId, fspp::Dir::EntryType entryType, fspp::mode_t mode,
fspp::uid_t uid, fspp::gid_t gid, timespec lastAccessTime, timespec lastModificationTime) {
auto insert_pos = _findUpperBound(blobId);
_entries.emplace(insert_pos, entryType, name, blobId, mode, uid, gid, lastAccessTime, lastModificationTime, cpputils::time::now());
}
void DirEntryList::addOrOverwrite(const string &name, const BlockId &blobId, fspp::Dir::EntryType entryType, fspp::mode_t mode,
fspp::uid_t uid, fspp::gid_t gid, timespec lastAccessTime, timespec lastModificationTime,
std::function<void (const blockstore::BlockId &blockId)> onOverwritten) {
auto found = _findByName(name);
if (found != _entries.end()) {
onOverwritten(found->blockId());
_overwrite(found, name, blobId, entryType, mode, uid, gid, lastAccessTime, lastModificationTime);
} else {
_add(name, blobId, entryType, mode, uid, gid, lastAccessTime, lastModificationTime);
}
}
void DirEntryList::rename(const blockstore::BlockId &blockId, const std::string &name, std::function<void (const blockstore::BlockId &blockId)> onOverwritten) {
auto foundSameName = _findByName(name);
if (foundSameName != _entries.end() && foundSameName->blockId() != blockId) {
_checkAllowedOverwrite(foundSameName->type(), _findById(blockId)->type());
onOverwritten(foundSameName->blockId());
_entries.erase(foundSameName);
}
_findById(blockId)->setName(name);
}
void DirEntryList::_checkAllowedOverwrite(fspp::Dir::EntryType oldType, fspp::Dir::EntryType newType) {
if (oldType != newType) {
if (oldType == fspp::Dir::EntryType::DIR) {
// new path is an existing directory, but old path is not a directory
throw fspp::fuse::FuseErrnoException(EISDIR);
}
if (newType == fspp::Dir::EntryType::DIR) {
// oldpath is a directory, and newpath exists but is not a directory.
throw fspp::fuse::FuseErrnoException(ENOTDIR);
}
}
}
void DirEntryList::_overwrite(vector<DirEntry>::iterator entry, const string &name, const BlockId &blobId, fspp::Dir::EntryType entryType, fspp::mode_t mode,
fspp::uid_t uid, fspp::gid_t gid, timespec lastAccessTime, timespec lastModificationTime) {
_checkAllowedOverwrite(entry->type(), entryType);
// The new entry has possibly a different blockId, so it has to be in a different list position (list is ordered by blockIds).
// That's why we remove-and-add instead of just modifying the existing entry.
_entries.erase(entry);
_add(name, blobId, entryType, mode, uid, gid, lastAccessTime, lastModificationTime);
}
boost::optional<const DirEntry&> DirEntryList::get(const string &name) const {
auto found = _findByName(name);
if (found == _entries.end()) {
return boost::none;
}
return *found;
}
boost::optional<const DirEntry&> DirEntryList::get(const BlockId &blockId) const {
auto found = _findById(blockId);
if (found == _entries.end()) {
return boost::none;
}
return *found;
}
void DirEntryList::remove(const string &name) {
auto found = _findByName(name);
if (found == _entries.end()) {
throw fspp::fuse::FuseErrnoException(ENOENT);
}
_entries.erase(found);
}
void DirEntryList::remove(const BlockId &blockId) {
auto lowerBound = _findLowerBound(blockId);
auto upperBound = std::find_if(lowerBound, _entries.end(), [&blockId] (const DirEntry &entry) {
return entry.blockId() != blockId;
});
_entries.erase(lowerBound, upperBound);
}
vector<DirEntry>::iterator DirEntryList::_findByName(const string &name) {
return std::find_if(_entries.begin(), _entries.end(), [&name] (const DirEntry &entry) {
return entry.name() == name;
});
}
vector<DirEntry>::const_iterator DirEntryList::_findByName(const string &name) const {
return const_cast<DirEntryList*>(this)->_findByName(name);
}
vector<DirEntry>::iterator DirEntryList::_findById(const BlockId &blockId) {
auto found = _findLowerBound(blockId);
if (found == _entries.end() || found->blockId() != blockId) {
throw fspp::fuse::FuseErrnoException(ENOENT);
}
return found;
}
vector<DirEntry>::iterator DirEntryList::_findLowerBound(const BlockId &blockId) {
return _findFirst(blockId, [&blockId] (const DirEntry &entry) {
return !std::less<BlockId>()(entry.blockId(), blockId);
});
}
vector<DirEntry>::iterator DirEntryList::_findUpperBound(const BlockId &blockId) {
return _findFirst(blockId, [&blockId] (const DirEntry &entry) {
return std::less<BlockId>()(blockId, entry.blockId());
});
}
vector<DirEntry>::iterator DirEntryList::_findFirst(const BlockId &hint, std::function<bool (const DirEntry&)> pred) {
//TODO Factor out a datastructure that keeps a sorted std::vector and allows these _findLowerBound()/_findUpperBound operations using this hinted linear search
if (_entries.size() == 0) {
return _entries.end();
}
double startpos_percent = static_cast<double>(*static_cast<const unsigned char*>(hint.data().data())) / std::numeric_limits<unsigned char>::max();
auto iter = _entries.begin() + static_cast<int>(startpos_percent * static_cast<double>(_entries.size()-1));
ASSERT(iter >= _entries.begin() && iter < _entries.end(), "Startpos out of range");
while(iter != _entries.begin() && pred(*iter)) {
--iter;
}
while(iter != _entries.end() && !pred(*iter)) {
++iter;
}
return iter;
}
vector<DirEntry>::const_iterator DirEntryList::_findById(const BlockId &blockId) const {
return const_cast<DirEntryList*>(this)->_findById(blockId);
}
size_t DirEntryList::size() const {
return _entries.size();
}
DirEntryList::const_iterator DirEntryList::begin() const {
return _entries.begin();
}
DirEntryList::const_iterator DirEntryList::end() const {
return _entries.end();
}
void DirEntryList::setMode(const BlockId &blockId, fspp::mode_t mode) {
auto found = _findById(blockId);
ASSERT ((mode.hasFileFlag() && found->mode().hasFileFlag()) || (mode.hasDirFlag() && found->mode().hasDirFlag()) || (mode.hasSymlinkFlag()), "Unknown mode in entry");
found->setMode(mode);
}
bool DirEntryList::setUidGid(const BlockId &blockId, fspp::uid_t uid, fspp::gid_t gid) {
auto found = _findById(blockId);
bool changed = false;
if (uid != fspp::uid_t(-1)) {
found->setUid(uid);
changed = true;
}
if (gid != fspp::gid_t(-1)) {
found->setGid(gid);
changed = true;
}
return changed;
}
void DirEntryList::setAccessTimes(const blockstore::BlockId &blockId, timespec lastAccessTime, timespec lastModificationTime) {
auto found = _findById(blockId);
found->setLastAccessTime(lastAccessTime);
found->setLastModificationTime(lastModificationTime);
}
bool DirEntryList::updateAccessTimestampForChild(const blockstore::BlockId &blockId, fspp::TimestampUpdateBehavior timestampUpdateBehavior) {
auto found = _findById(blockId);
const timespec lastAccessTime = found->lastAccessTime();
const timespec lastModificationTime = found->lastModificationTime();
const timespec now = cpputils::time::now();
switch (found->type()) {
case fspp::Dir::EntryType::FILE:
// fallthrough
case fspp::Dir::EntryType::SYMLINK:
if (timestampUpdateBehavior->shouldUpdateATimeOnFileRead(lastAccessTime, lastModificationTime, now)) {
found->setLastAccessTime(now);
return true;
}
return false;
case fspp::Dir::EntryType::DIR:
if (timestampUpdateBehavior->shouldUpdateATimeOnDirectoryRead(lastAccessTime, lastModificationTime, now)) {
found->setLastAccessTime(now);
return true;
}
return false;
}
throw std::logic_error("Unhandled case");
}
void DirEntryList::updateModificationTimestampForChild(const blockstore::BlockId &blockId) {
auto found = _findById(blockId);
found->setLastModificationTime(cpputils::time::now());
}
}
}