/* 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 . */ #include #include #include #include #include "../../kernel.h" #define TMAGIC "ustar" #define TMAGLEN 6 #define TVERSION "00" #define TVERSLEN 2 namespace vfs { int USTAR::Lookup(struct Inode *_Parent, const char *Name, struct Inode **Result) { auto Parent = (USTARInode *)_Parent; const char *basename; size_t length; cwk_path_get_basename(Name, &basename, &length); if (basename == NULL) { if (strcmp(Name, "/") == 0) { auto &it = Files.at(0); *Result = &it->Node; return 0; } error("Invalid name %s", Name); return -EINVAL; } if (_Parent) { for (const auto &child : Parent->Children) { if (child->Deleted || strcmp(child->Name.c_str(), basename) != 0) continue; *Result = &child->Node; return 0; } return -ENOENT; } auto fileItr = Files.begin(); while (fileItr != Files.end()) { USTARInode *node = fileItr->second; if (node->Deleted || strcmp(node->Name.c_str(), basename) != 0) { fileItr++; continue; } *Result = &fileItr->second->Node; return 0; } return -ENOENT; } int USTAR::Create(struct Inode *_Parent, const char *Name, mode_t Mode, struct Inode **Result) { USTARInode *Parent = (USTARInode *)_Parent; Inode inode{}; inode.Mode = Mode; inode.Device = this->DeviceID; inode.RawDevice = 0; inode.Index = NextInode; inode.Offset = 0; inode.PrivateData = this; inode.Flags = I_FLAG_CACHE_KEEP; const char *basename; size_t length; cwk_path_get_basename(Name, &basename, &length); auto SetMode = [&](mode_t &Mode, FileHeader *header) { if (Mode & S_IFREG) header->typeflag[0] = REGTYPE; else if (Mode & S_IFLNK) header->typeflag[0] = SYMTYPE; else if (Mode & S_IFCHR) header->typeflag[0] = CHRTYPE; else if (Mode & S_IFBLK) header->typeflag[0] = BLKTYPE; else if (Mode & S_IFDIR) header->typeflag[0] = DIRTYPE; else if (Mode & S_IFIFO) header->typeflag[0] = FIFOTYPE; mode_t final = 0; if (Mode & S_ISUID) final |= TSUID; else if (Mode & S_ISGID) final |= TSGID; else if (Mode & S_ISVTX) final |= TSVTX; else if (Mode & S_IRUSR) final |= TUREAD; else if (Mode & S_IWUSR) final |= TUWRITE; else if (Mode & S_IXUSR) final |= TUEXEC; else if (Mode & S_IRGRP) final |= TGREAD; else if (Mode & S_IWGRP) final |= TGWRITE; else if (Mode & S_IXGRP) final |= TGEXEC; else if (Mode & S_IROTH) final |= TOREAD; else if (Mode & S_IWOTH) final |= TOWRITE; else if (Mode & S_IXOTH) final |= TOEXEC; snprintf(header->mode, sizeof(header->mode), "%07o", final); }; FileHeader *hdr = new FileHeader{}; SetMode(inode.Mode, hdr); strncpy(hdr->name, basename, sizeof(hdr->name)); strncpy(hdr->signature, TMAGIC, TMAGLEN); strncpy(hdr->version, TVERSION, TVERSLEN); USTARInode *node = new USTARInode{.Node = inode, .Header = hdr, .Parent = Parent, .Name{}, .Path{}, .Children{}, .Deleted = false, .Checksum = INODE_CHECKSUM}; node->Name.assign(basename, length); node->Path.assign(Name, strlen(Name)); Files.insert(std::make_pair(NextInode, node)); *Result = &Files.at(NextInode)->Node; if (Parent) Parent->Children.push_back(Files.at(NextInode)); NextInode++; return 0; } ssize_t USTAR::Read(struct Inode *Node, void *Buffer, size_t Size, off_t Offset) { auto fileItr = Files.find(Node->Index); assert(fileItr != Files.end()); if (fileItr->second->Deleted) return -ENOENT; USTARInode *node = fileItr->second; size_t fileSize = GetSize(node->Header->size); if (Size <= 0) { debug("Size is less than or equal to 0"); Size = fileSize; } if ((size_t)Offset > fileSize) { debug("Offset %d is greater than file size %d", Offset, fileSize); return 0; } if ((fileSize - Offset) == 0) { debug("Offset %d is equal to file size %d", Offset, fileSize); return 0; /* EOF */ } if ((size_t)Offset + Size > fileSize) { debug("Offset %d + Size %d is greater than file size %d", Offset, Size, fileSize); Size = fileSize; } memcpy(Buffer, (uint8_t *)((uintptr_t)node->Header + sizeof(FileHeader) + Offset), Size); // debug("Read %d bytes from %d[%d]", Size, Node->Index, Offset); return Size; } __no_sanitize("alignment") ssize_t USTAR::ReadDir(struct Inode *_Node, struct kdirent *Buffer, size_t Size, off_t Offset, off_t Entries) { /* FIXME: FIX ALIGNMENT FOR DIRENT! */ auto Node = (USTARInode *)_Node; off_t realOffset = Offset; size_t totalSize = 0; uint16_t reclen = 0; struct kdirent *ent = nullptr; if (Offset == 0) { reclen = (uint16_t)(offsetof(struct kdirent, d_name) + strlen(".") + 1); if (totalSize + reclen >= Size) return -EINVAL; ent = (struct kdirent *)((uintptr_t)Buffer + totalSize); ent->d_ino = Node->Node.Index; ent->d_off = Offset++; ent->d_reclen = reclen; ent->d_type = DT_DIR; strcpy(ent->d_name, "."); totalSize += reclen; } if (Offset <= 1) { reclen = (uint16_t)(offsetof(struct kdirent, d_name) + strlen("..") + 1); if (totalSize + reclen >= Size) { if (realOffset == 1) return -EINVAL; return totalSize; } ent = (struct kdirent *)((uintptr_t)Buffer + totalSize); if (Node->Parent) ent->d_ino = Node->Parent->Node.Index; else { warn("Parent is null for %s", Node->Name.c_str()); ent->d_ino = Node->Node.Index; } ent->d_off = Offset++; ent->d_reclen = reclen; ent->d_type = DT_DIR; strcpy(ent->d_name, ".."); totalSize += reclen; } // off_t entriesSkipped = 0; // auto fileItr = Files.begin(); // while (fileItr != Files.end()) // { // if (fileItr->second->Deleted) // continue; // reclen = (uint16_t)(offsetof(struct kdirent, d_name) + strlen(fileItr->second->Name.c_str()) + 1); // if (Offset > entriesSkipped) // { // entriesSkipped++; // continue; // } // if (totalSize + reclen >= Size) // break; // ent = (struct kdirent *)((uintptr_t)Buffer + totalSize); // ent->d_ino = fileItr->first; // ent->d_off = Offset++; // ent->d_reclen = reclen; // ent->d_type = IFTODT(StringToInt(fileItr->second->Header->mode)); // strncpy(ent->d_name, // fileItr->second->Name.c_str(), // strlen(fileItr->second->Name.c_str())); // totalSize += reclen; // fileItr++; // } if (!S_ISDIR(Node->Node.Mode)) return -ENOTDIR; if ((Offset >= 2 ? (Offset - 2) : Offset) > (off_t)Node->Children.size()) return -EINVAL; off_t entries = 0; for (const auto &var : Node->Children) { if (var->Node.Offset < Offset) continue; if (entries >= Entries) break; if (var->Deleted) continue; reclen = (uint16_t)(offsetof(struct kdirent, d_name) + strlen(var->Name.c_str()) + 1); if (totalSize + reclen >= Size) break; ent = (struct kdirent *)((uintptr_t)Buffer + totalSize); ent->d_ino = var->Node.Index; ent->d_off = var->Node.Offset; ent->d_reclen = reclen; switch (var->Header->typeflag[0]) { case AREGTYPE: case REGTYPE: ent->d_type = DT_REG; break; case LNKTYPE: fixme("Hard link not implemented for %s", var->Header->name); ent->d_type = DT_LNK; break; case SYMTYPE: ent->d_type = DT_LNK; break; case CHRTYPE: ent->d_type = DT_CHR; break; case BLKTYPE: ent->d_type = DT_BLK; break; case DIRTYPE: ent->d_type = DT_DIR; break; case FIFOTYPE: ent->d_type = DT_FIFO; break; case CONTTYPE: default: ent->d_type = 0; break; } strncpy(ent->d_name, var->Name.c_str(), strlen(var->Name.c_str())); totalSize += reclen; entries++; } if (totalSize + sizeof(struct kdirent) >= Size) return totalSize; ent = (struct kdirent *)((uintptr_t)Buffer + totalSize); ent->d_ino = 0; ent->d_off = 0; ent->d_reclen = 0; ent->d_type = DT_UNKNOWN; ent->d_name[0] = '\0'; return totalSize; } int USTAR::SymLink(struct Inode *Node, const char *Name, const char *Target, struct Inode **Result) { int ret = this->Create(Node, Name, S_IFLNK, Result); if (ret < 0) return ret; USTARInode *node = (USTARInode *)*Result; FileHeader *hdr = node->Header; strncpy(hdr->link, Target, MIN(sizeof(hdr->link) - 1, strlen(Target))); return 0; } ssize_t USTAR::ReadLink(struct Inode *Node, char *Buffer, size_t Size) { auto fileItr = Files.find(Node->Index); assert(fileItr != Files.end()); if (fileItr->second->Deleted) return -ENOENT; USTARInode *node = fileItr->second; if (strlen(node->Header->link) > Size) Size = strlen(node->Header->link); strncpy(Buffer, node->Header->link, Size); debug("Read %d bytes from %d", Size, Node->Index); return Size; } int USTAR::Stat(struct Inode *Node, struct kstat *Stat) { auto fileItr = Files.find(Node->Index); assert(fileItr != Files.end()); if (fileItr->second->Deleted) return -ENOENT; USTARInode *node = fileItr->second; size_t fileSize = GetSize(node->Header->size); debug("Header: \"%.*s\"", sizeof(struct FileHeader), node->Header); Stat->Device = this->DeviceID; Stat->Index = Node->Index; Stat->HardLinks = 1; Stat->UserID = GetSize(node->Header->uid); Stat->GroupID = GetSize(node->Header->gid); Stat->RawDevice = Stat->MakeDevice(GetSize(node->Header->dev_maj), GetSize(node->Header->dev_min)); Stat->Size = fileSize; Stat->AccessTime = GetSize(node->Header->mtime); Stat->ModifyTime = GetSize(node->Header->mtime); Stat->ChangeTime = GetSize(node->Header->mtime); Stat->BlockSize = 512; Stat->Blocks = (fileSize + 511) / 512; Stat->Attribute = 0; mode_t hdrMode = StringToInt(node->Header->mode); if (hdrMode & TSUID) Stat->Mode |= S_ISUID; else if (hdrMode & TSGID) Stat->Mode |= S_ISGID; else if (hdrMode & TSVTX) Stat->Mode |= S_ISVTX; else if (hdrMode & TUREAD) Stat->Mode |= S_IRUSR; else if (hdrMode & TUWRITE) Stat->Mode |= S_IWUSR; else if (hdrMode & TUEXEC) Stat->Mode |= S_IXUSR; else if (hdrMode & TGREAD) Stat->Mode |= S_IRGRP; else if (hdrMode & TGWRITE) Stat->Mode |= S_IWGRP; else if (hdrMode & TGEXEC) Stat->Mode |= S_IXGRP; else if (hdrMode & TOREAD) Stat->Mode |= S_IROTH; else if (hdrMode & TOWRITE) Stat->Mode |= S_IWOTH; else if (hdrMode & TOEXEC) Stat->Mode |= S_IXOTH; switch (node->Header->typeflag[0]) { case AREGTYPE: case REGTYPE: Stat->Mode |= S_IFREG; break; case LNKTYPE: fixme("Hard link not implemented for %s", node->Header->name); Stat->Mode |= S_IFLNK; break; case SYMTYPE: Stat->Mode |= S_IFLNK; break; case CHRTYPE: Stat->Mode |= S_IFCHR; break; case BLKTYPE: Stat->Mode |= S_IFBLK; break; case DIRTYPE: Stat->Mode |= S_IFDIR; break; case FIFOTYPE: Stat->Mode |= S_IFIFO; break; case CONTTYPE: warn("Reserved type for %s", node->Header->name); __fallthrough; default: error("Unknown type: %d for %s", node->Header->typeflag[0], node->Header->name); break; } return 0; } bool USTAR::TestArchive(uintptr_t Address) { if (!Memory::Virtual().Check((void *)Address)) { error("Address %#lx is not mapped!", Address); return false; } FileHeader *header = (FileHeader *)Address; if (strncmp(header->signature, TMAGIC, TMAGLEN) != 0) { error("Invalid signature!"); return false; } return true; } void USTAR::ReadArchive(uintptr_t Address, size_t Size) { trace("Initializing USTAR with address %#lx and size %d", Address, Size); auto SetMode = [&](Inode &uNode, FileHeader *header) { mode_t hdrMode = StringToInt(header->mode); if (hdrMode & TSUID) uNode.Mode |= S_ISUID; else if (hdrMode & TSGID) uNode.Mode |= S_ISGID; else if (hdrMode & TSVTX) uNode.Mode |= S_ISVTX; else if (hdrMode & TUREAD) uNode.Mode |= S_IRUSR; else if (hdrMode & TUWRITE) uNode.Mode |= S_IWUSR; else if (hdrMode & TUEXEC) uNode.Mode |= S_IXUSR; else if (hdrMode & TGREAD) uNode.Mode |= S_IRGRP; else if (hdrMode & TGWRITE) uNode.Mode |= S_IWGRP; else if (hdrMode & TGEXEC) uNode.Mode |= S_IXGRP; else if (hdrMode & TOREAD) uNode.Mode |= S_IROTH; else if (hdrMode & TOWRITE) uNode.Mode |= S_IWOTH; else if (hdrMode & TOEXEC) uNode.Mode |= S_IXOTH; switch (header->typeflag[0]) { case AREGTYPE: case REGTYPE: uNode.Mode |= S_IFREG; break; case LNKTYPE: uNode.Mode |= S_IFLNK; break; case SYMTYPE: uNode.Mode |= S_IFLNK; break; case CHRTYPE: uNode.Mode |= S_IFCHR; break; case BLKTYPE: uNode.Mode |= S_IFBLK; break; case DIRTYPE: uNode.Mode |= S_IFDIR; break; case FIFOTYPE: uNode.Mode |= S_IFIFO; break; case CONTTYPE: warn("Reserved type for %s", header->name); __fallthrough; default: error("Unknown type: %d for %s", header->typeflag[0], header->name); break; } }; FileHeader *header = (FileHeader *)Address; debug("USTAR signature valid! Name:%s Signature:%s Mode:%d Size:%lu", header->name, header->signature, StringToInt(header->mode), header->size); Memory::Virtual vmm; std::vector tmpNodes; /* FIXME: bug in unordered_map for iterators */ for (size_t i = 0;; i++) { if (!vmm.Check((void *)header)) { error("Address %#lx is not mapped!", header); return; } if (strncmp(header->signature, TMAGIC, TMAGLEN) != 0) break; // debug("\"%s\"", header->name); /* This removes the "." at the beginning of the file name "./foo/bar" > "/foo/bar" */ if (header->name[0] == '.' && header->name[1] == '/') memmove(header->name, header->name + 1, strlen(header->name)); if (isempty((char *)header->name)) fixme("Ignoring empty file name \"%.*s\"", sizeof(struct FileHeader), header); struct Inode uNode; uNode.Device = this->DeviceID; uNode.RawDevice = 0; uNode.Index = NextInode; SetMode(uNode, header); uNode.Flags = I_FLAG_CACHE_KEEP; uNode.Offset = 0; uNode.PrivateData = this; const char *basename; size_t length; cwk_path_get_basename(header->name, &basename, &length); USTARInode *node = new USTARInode{.Node = uNode, .Header = header, .Parent = nullptr, .Name{}, .Path{}, .Children{}, .Deleted = false, .Checksum = INODE_CHECKSUM}; if (basename) node->Name.assign(basename, length); else node->Name.assign((const char *)header->name, strlen(header->name)); node->Path.assign((const char *)header->name, strlen(header->name)); Files.insert(std::make_pair(NextInode, node)); tmpNodes.push_back(node); size_t size = GetSize(header->size); Address += ((size / 512) + 1) * 512; if (size % 512) Address += 512; header = (FileHeader *)Address; NextInode++; } /* TODO: This code can be significantly optimized but good luck understanding it */ USTARInode *parent = nullptr; std::vector parentStack; std::vector pathStack; for (auto &file : tmpNodes) { if (file->Path == "/") /* This is root / */ { parentStack.push_back(file); pathStack.push_back(&file->Path); // debug("root / generated"); continue; } /* pathStack is never empty */ if (file->Path.back() == '/') /* This is a directory */ { const char *path = file->Path.c_str(); size_t length; /* This converts /one/two/path.txt to /one/two/ */ cwk_path_get_dirname(path, &length); std::string dirName(path, length); /* Check if the directory is at the same level as the current directory */ if (dirName == *pathStack.back()) { parent = parentStack.back(); parentStack.push_back(file); pathStack.push_back(&file->Path); parent->Children.push_back(file); file->Parent = parent; // debug("adding \"%s\" to \"%s\"", file->Path.c_str(), parent->Path.c_str()); continue; } else { /* Check if the directory is at a higher level */ for (size_t i = 0; i < parentStack.size(); i++) { if (dirName != *pathStack[i]) continue; /* Adjust vectors */ while (!parentStack.empty()) { if (dirName == *pathStack.back()) break; // debug("popping \"%s\"", pathStack.back()->c_str()); parentStack.pop_back(); pathStack.pop_back(); } parent = parentStack.back(); parentStack.push_back(file); pathStack.push_back(&file->Path); parent->Children.push_back(file); file->Parent = parent; // debug("adding \"%s\" to \"%s\"", file->Path.c_str(), parent->Path.c_str()); goto foundEnd; } // This is a new directory level parentStack.pop_back(); pathStack.pop_back(); parent = parentStack.back(); parentStack.push_back(file); pathStack.push_back(&file->Path); parent->Children.push_back(file); file->Parent = parent; // debug("adding \"%s\" to \"%s\"", file->Path.c_str(), parent->Path.c_str()); foundEnd: continue; } } /* From here, it's a file */ const char *path = file->Path.c_str(); size_t length; /* This converts /one/two/path.txt to /one/two/ */ cwk_path_get_dirname(path, &length); std::string dirName(path, length); /* Check if the file is at the same level as the current directory */ if (dirName == *pathStack.back()) { parent = parentStack.back(); parent->Children.push_back(file); file->Parent = parent; // debug("adding \"%s\" to \"%s\"", file->Path.c_str(), parent->Path.c_str()); continue; } /* Check if the file is at a higher level */ for (size_t i = 0; i < parentStack.size(); i++) { if (dirName != *pathStack[i]) continue; /* Adjust vectors */ while (!parentStack.empty()) { if (dirName == *pathStack.back()) break; // debug("popping \"%s\"", pathStack.back()->c_str()); parentStack.pop_back(); pathStack.pop_back(); } parent = parentStack.back(); parent->Children.push_back(file); file->Parent = parent; // debug("adding \"%s\" to \"%s\"", file->Path.c_str(), parent->Path.c_str()); } } std::function ustarTree = [&](vfs::USTAR::USTARInode *node, int level, const std::string &prefix) { debug("%*s\"%s\"%ld", level * 4, prefix.c_str(), node->Name.c_str(), node->Node.Offset); off_t offset = 2; /* 0 . | 1 .. */ for (auto &child : node->Children) { #ifdef DEBUG if (offset <= 2) { if (offset == 2) offset = 0; /* Pseudo directories . and .. */ USTARInode pseudoDot{}; pseudoDot.Node = node->Node; pseudoDot.Name = "."; pseudoDot.Node.Offset = offset++; USTARInode pseudoDDot{}; pseudoDDot.Node = node->Parent ? node->Parent->Node : node->Node; pseudoDDot.Name = ".."; pseudoDDot.Node.Offset = offset++; ustarTree(&pseudoDot, level + 1, "|-- "); ustarTree(&pseudoDDot, level + 1, "|-- "); } #endif child->Node.Offset = offset++; ustarTree(child, level + 1, child == node->Children.back() ? "`-- " : "|-- "); } }; ustarTree(tmpNodes[0], 0, ""); } } O2 int __ustar_Lookup(struct Inode *Parent, const char *Name, struct Inode **Result) { return ((vfs::USTAR *)Parent->PrivateData)->Lookup(Parent, Name, Result); } O2 int __ustar_Create(struct Inode *Parent, const char *Name, mode_t Mode, struct Inode **Result) { return ((vfs::USTAR *)Parent->PrivateData)->Create(Parent, Name, Mode, Result); } O2 ssize_t __ustar_Read(struct Inode *Node, void *Buffer, size_t Size, off_t Offset) { return ((vfs::USTAR *)Node->PrivateData)->Read(Node, Buffer, Size, Offset); } O2 ssize_t __ustar_Readdir(struct Inode *Node, struct kdirent *Buffer, size_t Size, off_t Offset, off_t Entries) { return ((vfs::USTAR *)Node->PrivateData)->ReadDir(Node, Buffer, Size, Offset, Entries); } O2 int __ustar_SymLink(Inode *Parent, const char *Name, const char *Target, Inode **Result) { return ((vfs::USTAR *)Parent->PrivateData)->SymLink(Parent, Name, Target, Result); } O2 ssize_t __ustar_ReadLink(Inode *Node, char *Buffer, size_t Size) { return ((vfs::USTAR *)Node->PrivateData)->ReadLink(Node, Buffer, Size); } O2 int __ustar_Stat(struct Inode *Node, kstat *Stat) { return ((vfs::USTAR *)Node->PrivateData)->Stat(Node, Stat); } int __ustar_DestroyInode(FileSystemInfo *Info, Inode *Node) { ((vfs::USTAR::USTARInode *)Node)->Deleted = true; return 0; } int __ustar_Destroy(FileSystemInfo *fsi) { assert(fsi->PrivateData); delete (vfs::USTAR *)fsi->PrivateData; delete fsi; return 0; } bool TestAndInitializeUSTAR(uintptr_t Address, size_t Size) { vfs::USTAR *ustar = new vfs::USTAR(); if (!ustar->TestArchive(Address)) { delete ustar; return false; } ustar->DeviceID = fs->EarlyReserveDevice(); ustar->ReadArchive(Address, Size); Inode *initrd = nullptr; ustar->Lookup(nullptr, "/", &initrd); assert(initrd != nullptr); FileSystemInfo *fsi = new FileSystemInfo; fsi->Name = "ustar"; fsi->RootName = "/"; fsi->Flags = I_FLAG_ROOT | I_FLAG_MOUNTPOINT | I_FLAG_CACHE_KEEP; fsi->SuperOps.DeleteInode = __ustar_DestroyInode; fsi->SuperOps.Destroy = __ustar_Destroy; fsi->Ops.Lookup = __ustar_Lookup; fsi->Ops.Create = __ustar_Create; fsi->Ops.Read = __ustar_Read; fsi->Ops.ReadDir = __ustar_Readdir; fsi->Ops.SymLink = __ustar_SymLink; fsi->Ops.ReadLink = __ustar_ReadLink; fsi->Ops.Stat = __ustar_Stat; fsi->PrivateData = ustar; fs->LateRegisterFileSystem(ustar->DeviceID, fsi, initrd); fs->AddRoot(initrd); return true; }