Fennix/Kernel/fs/vfs.cpp

628 lines
14 KiB
C++

/*
This file is part of Fennix Kernel.
Fennix Kernel is free software: you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation, either version 3 of
the License, or (at your option) any later version.
Fennix Kernel is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Fennix Kernel. If not, see <https://www.gnu.org/licenses/>.
*/
#include <fs/vfs.hpp>
#include "../kernel.h"
namespace vfs
{
eNode Virtual::Convert(Inode *inode)
{
Node cache = std::make_shared<NodeCache>();
cache->inode = inode;
return {cache, 0};
}
eNode Virtual::Convert(Node &Parent, Inode *inode)
{
Node cache = std::make_shared<NodeCache>();
cache->inode = inode;
cache->fsi = Parent->fsi;
cache->Parent = Parent;
Parent->Children.push_back(cache);
return {cache, 0};
}
std::string Virtual::NormalizePath(Node &Parent, std::string Path, bool Join)
{
std::string result;
if (Join)
{
size_t len = Path.size() + Parent->Path.size() + 2;
result.reserve(len);
len = cwk_path_join(Parent->Path.c_str(), Path.c_str(), result.data(), result.capacity());
result.resize(len);
return result;
}
size_t len = Path.size() + 2;
result.reserve(len);
len = cwk_path_normalize(Path.c_str(), result.data(), result.capacity());
result.resize(len);
return result;
}
bool Virtual::RootExists(dev_t Index)
{
if (Roots.find(Index) == Roots.end())
return false;
return true;
}
eNode Virtual::GetRoot(dev_t Index)
{
auto it = Roots.find(Index);
if (it == Roots.end())
return {nullptr, ENOENT};
return {it->second, 0};
}
ssize_t Virtual::GetRoot(Node Index)
{
for (auto it = Roots.begin(); it != Roots.end(); ++it)
{
if (it->second == Index)
return it->first;
}
return -ENOENT;
}
int Virtual::AddRoot(dev_t Index, Node Root, bool Replace)
{
assert(Root != nullptr);
auto it = Roots.find(Index);
if (it == Roots.end())
{
Roots[Index] = Root;
return 0;
}
if (Replace)
{
Roots[Index] = Root;
return 0;
}
else
{
debug("Root %ld already exists", Index);
return EEXIST;
}
}
dev_t Virtual::RegisterFileSystem(FileSystemInfo *fsi)
{
assert(fsi != nullptr);
FileSystems.insert({FileSystems.size(), fsi});
return FileSystems.size() - 1;
}
int Virtual::UnregisterFileSystem(dev_t Device)
{
auto it = FileSystems.find(Device);
if (it == FileSystems.end())
return -ENOENT;
FileSystemInfo *fsi = it->second;
/* TODO: unmount */
fixme("Unmounting %d", Device);
if (fsi->SuperOps.Synchronize)
fsi->SuperOps.Synchronize(fsi, nullptr);
if (fsi->SuperOps.Destroy)
fsi->SuperOps.Destroy(fsi);
FileSystems.erase(it);
return 0;
}
eNode Virtual::Lookup(Node &Parent, std::string Path)
{
assert(Parent != nullptr);
debug("looking up \"%s\" in \"%s\"", Path.c_str(), Parent->Path.c_str());
if (Path == ".")
return {Parent, 0};
else if (Path == "..")
return {Parent->Parent ? Parent->Parent : Parent, 0};
Node base = Parent;
bool absolute = PathIsAbsolute(Path);
if (absolute == true)
{
while (base->Parent)
base = base->Parent;
}
debug("base is \"%s\" and path is \"%s\" %d", base->Path.c_str(), Path.c_str(), absolute);
Path = this->NormalizePath(base, Path, !absolute);
debug("after normalizing, path is \"%s\" %d", Path.c_str(), absolute);
struct cwk_segment segment;
if (!cwk_path_get_first_segment(Path.c_str(), &segment))
{
debug("%s no segments; %d", Path.c_str(), absolute);
if (Path == "/")
return {base, 0};
assert(!"Path doesn't have any segments.");
}
Node node = base;
/* We need to go to the root after NormalizePath even if Path is relative */
if (absolute == false)
{
while (node->Parent)
{
debug("current parent \"%s\"", node->Parent->Path.c_str());
node = node->Parent;
debug("new parent \"%s\"", node->Parent ? node->Parent->Path.c_str() : "<null>");
}
}
std::string currentPath = node->Path;
if (currentPath.empty())
currentPath = "/";
do
{
std::string segmentStr(segment.begin, segment.size);
debug("Current segment is \"%s\"", segmentStr.c_str());
eNode ret = node->CachedSearch(segmentStr);
if (ret == false)
{
debug("cache miss for \"%s\"", segmentStr.c_str());
if (node->fsi->Ops.Lookup == nullptr)
return {nullptr, ENOTSUP};
Inode *inode;
int ret = node->fsi->Ops.Lookup(node->inode, segmentStr.c_str(), &inode);
if (ret != 0)
return {nullptr, ret};
if (currentPath == "/")
currentPath += segmentStr;
else
currentPath += "/" + segmentStr;
node = Convert(node, inode);
node->Name = segmentStr;
node->Path = currentPath;
}
else
{
debug("cache hit for \"%s\"", segmentStr.c_str());
node = ret;
if (currentPath == "/")
currentPath += segmentStr;
else
currentPath += "/" + segmentStr;
}
} while (cwk_path_get_next_segment(&segment));
return {node, 0};
}
eNode Virtual::Create(Node &Parent, std::string Name, mode_t Mode, bool ErrorIfExists)
{
eNode exists = this->Lookup(Parent, Name);
if (exists)
{
if (ErrorIfExists)
return {nullptr, EEXIST};
/* I should handle this in a better way */
assert((exists.Value->inode->Mode & S_IFMT) == (Mode & S_IFMT));
debug("File \"%s\" already exists in cache", Name.c_str());
return exists;
}
if (!Parent)
return {nullptr, EINVAL};
if (Parent->fsi->Ops.Create == nullptr)
return {nullptr, ENOTSUP};
Inode *inode;
int ret = Parent->fsi->Ops.Create(Parent->inode, Name.c_str(), Mode, &inode);
if (ret != 0)
return {nullptr, ret};
Node node = Convert(Parent, inode);
node->Name = Name;
std::string unormalized = Parent->Path == "/" ? "/" + Name : Parent->Path + "/" + Name;
node->Path = fs->NormalizePath(Parent, unormalized);
return {node, 0};
}
int Virtual::Remove(Node &Parent, std::string Name)
{
if (!Parent)
return -EINVAL;
if (Parent->fsi->Ops.Remove == nullptr)
return -ENOTSUP;
int ret = Parent->fsi->Ops.Remove(Parent->inode, Name.c_str());
if (ret == 0)
{
for (auto it = Parent->Children.begin(); it != Parent->Children.end(); ++it)
{
if (it->get()->Name != Name)
continue;
Parent->Children.erase(it);
break;
}
}
return ret;
}
int Virtual::Remove(Node &node)
{
if (!node->Parent)
return -EINVAL;
if (node->Parent->fsi->Ops.Remove == nullptr)
return -ENOTSUP;
int ret = node->Parent->fsi->Ops.Remove(node->inode, node->Name.c_str());
if (ret == 0)
{
Node &p = node->Parent;
for (auto it = p->Children.begin(); it != p->Children.end(); ++it)
{
if (it->get() != node.get())
continue;
p->Children.erase(it);
break;
}
}
return ret;
}
int Virtual::Rename(Node &node, std::string NewName)
{
if (node->fsi->Ops.Rename == nullptr)
return -ENOTSUP;
int ret = node->fsi->Ops.Rename(node->inode, node->Name.c_str(), NewName.c_str());
if (ret == 0)
node->Name = NewName;
return ret;
}
ssize_t Virtual::Read(Node &Target, void *Buffer, size_t Size, off_t Offset)
{
if (Target->IsDirectory() || Target->IsMountPoint())
return -EISDIR;
if (Target->IsSymbolicLink())
return -EINVAL;
/* TODO: cache buffer */
return Target->__Read(Buffer, Size, Offset);
}
ssize_t Virtual::Write(Node &Target, const void *Buffer, size_t Size, off_t Offset)
{
if (Target->IsDirectory() || Target->IsMountPoint())
return -EISDIR;
if (Target->IsSymbolicLink())
return -EINVAL;
/* TODO: cache buffer */
return Target->__Write(Buffer, Size, Offset);
}
int Virtual::Truncate(Node &Target, off_t Size)
{
if (Target->IsDirectory() || Target->IsMountPoint())
return -EISDIR;
if (!Target->IsRegularFile())
return -EINVAL;
/* TODO: cache buffer */
return Target->__Truncate(Size);
}
__no_sanitize("alignment") ssize_t Virtual::ReadDirectory(Node &Target, kdirent *Buffer, size_t Size, off_t Offset, off_t Entries)
{
if (!Target->IsDirectory() && !Target->IsMountPoint())
return -ENOTDIR;
ssize_t total = 0;
off_t entryIndex = 0;
std::list<std::string> seen;
uint8_t *bufPtr = reinterpret_cast<uint8_t *>(Buffer);
if (Target->fsi && Target->fsi->Ops.ReadDir)
{
const size_t tempBufSize = 4096;
std::unique_ptr<uint8_t[]> tempBuf(new uint8_t[tempBufSize]);
off_t fsOffset = Offset;
ssize_t read = Target->fsi->Ops.ReadDir(Target->inode, (kdirent *)tempBuf.get(), tempBufSize, fsOffset, Entries);
if (read > 0)
{
ssize_t pos = 0;
while (pos < read)
{
kdirent *ent = (kdirent *)(tempBuf.get() + pos);
if (ent->d_reclen == 0)
break;
size_t reclen = ent->d_reclen;
if (total + reclen > Size)
break;
memcpy(bufPtr, ent, reclen);
seen.push_back(ent->d_name);
bufPtr += reclen;
total += reclen;
pos += reclen;
entryIndex++;
}
}
}
for (const auto &child : Target->Children)
{
if (std::find(seen.begin(), seen.end(), child->Name) != seen.end())
continue;
if (entryIndex < Offset)
{
entryIndex++;
continue;
}
uint16_t reclen = (uint16_t)(offsetof(struct kdirent, d_name) + child->Name.size() + 1);
if (total + reclen > (ssize_t)Size)
break;
kdirent *ent = (kdirent *)bufPtr;
ent->d_ino = child->inode ? child->inode->Index : 0;
ent->d_off = entryIndex++;
ent->d_reclen = reclen;
ent->d_type = child->inode ? IFTODT(child->inode->Mode) : DT_UNKNOWN;
strcpy(ent->d_name, child->Name.c_str());
bufPtr += reclen;
total += reclen;
seen.push_back(child->Name);
}
return total;
}
__no_sanitize("alignment") std::list<Node> Virtual::ReadDirectory(Node &Target)
{
if (!Target->IsDirectory() && !Target->IsMountPoint())
return {};
std::list<Node> ret;
std::list<std::string> seen;
if (Target->fsi && Target->fsi->Ops.ReadDir)
{
const size_t bufSize = 4096;
std::unique_ptr<uint8_t[]> buf(new uint8_t[bufSize]);
off_t offset = 0;
while (true)
{
ssize_t read = Target->fsi->Ops.ReadDir(Target->inode, (kdirent *)buf.get(), bufSize, offset, LONG_MAX);
if (read <= 0)
break;
ssize_t pos = 0;
while (pos < read)
{
kdirent *ent = (kdirent *)(buf.get() + pos);
if (ent->d_reclen == 0)
break;
debug("%s", ent->d_name);
if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0)
{
pos += ent->d_reclen;
continue;
}
seen.push_back(ent->d_name);
auto it = std::find_if(Target->Children.begin(), Target->Children.end(),
[&](const Node &n)
{ return n->Name == ent->d_name; });
if (it != Target->Children.end())
ret.push_back(*it);
else
{
eNode result = Lookup(Target, ent->d_name);
if (result.Error == 0 && result.Value)
{
Target->Children.push_back(result.Value);
result.Value->Parent = Target;
ret.push_back(result.Value);
}
}
pos += ent->d_reclen;
}
offset += read;
}
}
for (const auto &child : Target->Children)
{
if (std::find(seen.begin(), seen.end(), child->Name) != seen.end())
continue;
if (child->Name == "." || child->Name == "..")
continue;
ret.push_back(child);
seen.push_back(child->Name.c_str());
}
return ret;
}
eNode Virtual::CreateLink(Node &Parent, std::string Name, std::string Target)
{
mode_t mode = S_IRWXU |
S_IRWXG |
S_IRWXO |
S_IFLNK;
eNode enode = this->Create(Parent, Name, mode);
if (!enode)
return enode;
Node node = enode;
node->Link = Target;
return {node, 0};
}
int Virtual::Stat(Node &Target, struct kstat *Stat)
{
/* TODO: cache */
return Target->__Stat(Stat);
}
off_t Virtual::Seek(Node &Target, off_t Offset)
{
/* TODO: cache */
return Target->__Seek(Offset);
}
int Virtual::Open(Node &Target, int Flags, mode_t Mode)
{
/* TODO: cache */
return Target->__Open(Flags, Mode);
}
int Virtual::Close(Node &Target)
{
/* TODO: cache */
return Target->__Close();
}
FileSystemInfo *Virtual::Probe(FileSystemDevice *Device)
{
for (auto &&i : FileSystems)
{
if (i.second->SuperOps.Probe == nullptr)
{
debug("%s does not support probing", i.second->Name);
continue;
}
int ret = i.second->SuperOps.Probe(Device);
if (ret == 0)
return i.second;
debug("%s returned %d", i.second->Name, ret);
}
debug("No filesystems matched");
return nullptr;
}
eNode Virtual::Mount(Node &Parent, Inode *inode, std::string Name, FileSystemInfo *fsi)
{
assert(Parent);
assert(inode);
Node ret = this->Convert(inode);
ret->fsi = fsi;
ret->Name = Name;
std::string unormalized = Parent->Path == "/" ? "/" + Name : Parent->Path + "/" + Name;
ret->Path = fs->NormalizePath(Parent, unormalized);
// ret->Link =
ret->Parent = Parent;
Parent->Children.push_back(ret);
return {ret, 0};
}
eNode Virtual::Mount(Node &Parent, std::string Name, FileSystemInfo *fsi, FileSystemDevice *Device)
{
Inode *inode;
int ret = fsi->SuperOps.Mount(fsi, &inode, Device);
if (ret != 0)
return {nullptr, ret};
return this->Mount(Parent, inode, Name, fsi);
// Node node = std::make_shared<NodeCache>();
// node->inode = nullptr; /* FIXME: ??? */
// node->fsi = fsi;
// node->Flags.MountPoint = true;
// node->Name = Name;
// node->Path = fs->NormalizePath(Parent, Parent->Path + "/" + Name);
// node->Parent = Parent;
// Parent->Children.push_back(node);
// return {node, 0};
}
int Virtual::Umount(Node &node)
{
if (!node->Flags.MountPoint)
{
debug("node %s is not a mountpoint", node->Path.c_str());
return -EINVAL;
}
fixme("untested code");
std::shared_ptr<NodeCache> &ptr = node;
ptr.reset();
return 0;
}
int Virtual::Umount(Node &Parent, std::string Name)
{
eNode node = Parent->CachedSearch(Name);
if (!node)
{
debug("mountpoint %s not found: %s", Name.c_str(), node.what());
return -node.Error;
}
return this->Umount(node.Value);
}
void Virtual::Initialize()
{
debug("Initializing virtual file system...");
Node root = this->GetRoot(0);
/* d rwx rwx rwx */
mode_t mode = S_IRWXU |
S_IRWXG |
S_IRWXO |
S_IFDIR;
Node var = this->Create(root, "var", mode, false);
Node log = this->Create(var, "log", mode, false);
}
Virtual::Virtual() {}
Virtual::~Virtual() {}
}