mirror of
https://github.com/EnderIce2/Fennix.git
synced 2025-05-25 22:14:34 +00:00
628 lines
14 KiB
C++
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() {}
|
|
}
|