mirror of
https://github.com/Fennix-Project/Kernel.git
synced 2025-07-11 07:19:20 +00:00
Updated directory name
This commit is contained in:
117
FileSystem/FS/ustar.cpp
Normal file
117
FileSystem/FS/ustar.cpp
Normal file
@ -0,0 +1,117 @@
|
||||
#include <filesystem/ustar.hpp>
|
||||
|
||||
#include <memory.hpp>
|
||||
#include <debug.h>
|
||||
|
||||
#include "../../kernel.h"
|
||||
|
||||
namespace FileSystem
|
||||
{
|
||||
ReadFSFunction(USTAR_Read)
|
||||
{
|
||||
if (!Size)
|
||||
Size = Node->Length;
|
||||
if (Offset > Node->Length)
|
||||
return 0;
|
||||
if (Offset + Size > Node->Length)
|
||||
Size = Node->Length - Offset;
|
||||
memcpy(Buffer, (uint8_t *)(Node->Address + Offset), Size);
|
||||
return Size;
|
||||
}
|
||||
|
||||
FileSystemOpeations ustar = {
|
||||
.Name = "ustar",
|
||||
.Read = USTAR_Read,
|
||||
};
|
||||
|
||||
USTAR::USTAR(uint64_t Address, Virtual *vfs)
|
||||
{
|
||||
trace("Initializing USTAR with address %#llx", Address);
|
||||
|
||||
if (memcmp(((FileHeader *)Address)->signature, "ustar", 5) != 0)
|
||||
{
|
||||
error("ustar signature invalid!");
|
||||
return;
|
||||
}
|
||||
debug("USTAR signature valid! Name:%s Signature:%s Mode:%c Size:%lu",
|
||||
((FileHeader *)Address)->name,
|
||||
((FileHeader *)Address)->signature,
|
||||
string2int(((FileHeader *)Address)->mode),
|
||||
((FileHeader *)Address)->size);
|
||||
|
||||
vfs->CreateRoot(&ustar, "/");
|
||||
|
||||
uint64_t errorsallowed = 20;
|
||||
|
||||
for (uint64_t i = 0;; i++)
|
||||
{
|
||||
FileHeader *header = (FileHeader *)Address;
|
||||
if (memcmp(((FileHeader *)Address)->signature, "ustar", 5) != 0)
|
||||
break;
|
||||
memmove(header->name, header->name + 1, strlen(header->name));
|
||||
if (header->name[strlen(header->name) - 1] == '/')
|
||||
header->name[strlen(header->name) - 1] = 0;
|
||||
uint64_t size = getsize(header->size);
|
||||
FileSystemNode *node = nullptr;
|
||||
|
||||
if (!isempty((char *)header->name))
|
||||
KPrint("Adding file \e88AACC%s\eCCCCCC (\e88AACC%lu \eCCCCCCbytes)", header->name, size);
|
||||
else
|
||||
goto NextFileAddress;
|
||||
|
||||
node = vfs->Create(nullptr, header->name);
|
||||
debug("Added node: %s", node->Name);
|
||||
if (node == nullptr)
|
||||
{
|
||||
if (errorsallowed > 0)
|
||||
{
|
||||
errorsallowed--;
|
||||
goto NextFileAddress;
|
||||
}
|
||||
else
|
||||
{
|
||||
error("Adding USTAR files failed because too many files were corrputed or invalid.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
trace("%s %dKB Type:%c", header->name, TO_KB(size), header->typeflag[0]);
|
||||
node->Mode = string2int(header->mode);
|
||||
node->Address = (Address + 512);
|
||||
node->Length = size;
|
||||
node->GroupIdentifier = getsize(header->gid);
|
||||
node->UserIdentifier = getsize(header->uid);
|
||||
node->IndexNode = i;
|
||||
|
||||
switch (header->typeflag[0])
|
||||
{
|
||||
case REGULAR_FILE:
|
||||
node->Flags = NodeFlags::FS_FILE;
|
||||
break;
|
||||
case SYMLINK:
|
||||
node->Flags = NodeFlags::FS_SYMLINK;
|
||||
break;
|
||||
case DIRECTORY:
|
||||
node->Flags = NodeFlags::FS_DIRECTORY;
|
||||
break;
|
||||
case CHARDEV:
|
||||
node->Flags = NodeFlags::FS_CHARDEVICE;
|
||||
break;
|
||||
case BLOCKDEV:
|
||||
node->Flags = NodeFlags::FS_BLOCKDEVICE;
|
||||
break;
|
||||
default:
|
||||
warn("Unknown type: %d", header->typeflag[0]);
|
||||
break;
|
||||
}
|
||||
NextFileAddress:
|
||||
Address += ((size / 512) + 1) * 512;
|
||||
if (size % 512)
|
||||
Address += 512;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
USTAR::~USTAR() { warn("Destroyed"); }
|
||||
}
|
488
FileSystem/Filesystem.cpp
Normal file
488
FileSystem/Filesystem.cpp
Normal file
@ -0,0 +1,488 @@
|
||||
#include <filesystem.hpp>
|
||||
|
||||
#include <smartptr.hpp>
|
||||
#include <convert.h>
|
||||
#include <printf.h>
|
||||
#include <lock.hpp>
|
||||
#include <cwalk.h>
|
||||
#include <sys.h>
|
||||
|
||||
#include "../kernel.h"
|
||||
|
||||
NewLock(VFSLock);
|
||||
|
||||
namespace FileSystem
|
||||
{
|
||||
char *Virtual::GetPathFromNode(FileSystemNode *Node)
|
||||
{
|
||||
vfsdbg("GetPathFromNode( Node: \"%s\" )", Node->Name);
|
||||
FileSystemNode *Parent = Node;
|
||||
Vector<char *> Path;
|
||||
size_t Size = 1;
|
||||
|
||||
while (Parent != FileSystemRoot && Parent != nullptr)
|
||||
{
|
||||
foreach (auto var in FileSystemRoot->Children)
|
||||
if (var == Parent)
|
||||
goto PathFromNodeContinue;
|
||||
Path.push_back(Parent->Name);
|
||||
Path.push_back((char *)"/");
|
||||
Parent = Parent->Parent;
|
||||
}
|
||||
|
||||
PathFromNodeContinue:
|
||||
Path.reverse();
|
||||
|
||||
foreach (auto var in Path)
|
||||
Size += strlen(var);
|
||||
|
||||
char *FinalPath = new char[Size];
|
||||
|
||||
foreach (auto var in Path)
|
||||
strcat(FinalPath, var);
|
||||
|
||||
// for (size_t i = 0; i < Path.size(); i++)
|
||||
// strcat(FinalPath, Path[i]);
|
||||
|
||||
// for (size_t i = 0; i < Path.size(); i++)
|
||||
// Size += strlen(Path[i]);
|
||||
vfsdbg("GetPathFromNode()->\"%s\"", FinalPath);
|
||||
return FinalPath;
|
||||
}
|
||||
|
||||
FileSystemNode *Virtual::GetNodeFromPath(FileSystemNode *Parent, const char *Path)
|
||||
{
|
||||
vfsdbg("GetNodeFromPath( Parent: \"%s\" Path: \"%s\" )", Parent->Name, Path);
|
||||
|
||||
if (Parent == nullptr)
|
||||
Parent = FileSystemRoot;
|
||||
|
||||
if (strcmp(Parent->Name, Path))
|
||||
{
|
||||
cwk_segment segment;
|
||||
if (!cwk_path_get_first_segment(Path, &segment))
|
||||
{
|
||||
error("Path doesn't have any segments.");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
char *SegmentName = new char[segment.end - segment.begin + 1];
|
||||
memcpy(SegmentName, segment.begin, segment.end - segment.begin);
|
||||
GetNodeFromPathNextParent:
|
||||
foreach (auto var in Parent->Children)
|
||||
{
|
||||
if (!strcmp(var->Name, SegmentName))
|
||||
{
|
||||
Parent = var;
|
||||
goto GetNodeFromPathNextParent;
|
||||
}
|
||||
}
|
||||
delete[] SegmentName;
|
||||
} while (cwk_path_get_next_segment(&segment));
|
||||
const char *basename;
|
||||
cwk_path_get_basename(Path, &basename, nullptr);
|
||||
if (!strcmp(basename, Parent->Name))
|
||||
{
|
||||
vfsdbg("GetNodeFromPath()->\"%s\"", Parent->Name);
|
||||
return Parent;
|
||||
}
|
||||
|
||||
vfsdbg("GetNodeFromPath()->\"%s\"", nullptr);
|
||||
return nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
vfsdbg("GetNodeFromPath()->\"%s\"", Parent->Name);
|
||||
return Parent;
|
||||
}
|
||||
}
|
||||
|
||||
FileSystemNode *AddNewChild(FileSystemNode *Parent, const char *Name)
|
||||
{
|
||||
vfsdbg("AddNewChild( Parent: \"%s\" Name: \"%s\" )", Parent->Name, Name);
|
||||
FileSystemNode *newNode = new FileSystemNode;
|
||||
newNode->Parent = Parent;
|
||||
strcpy(newNode->Name, Name);
|
||||
if (Parent)
|
||||
newNode->Operator = Parent->Operator;
|
||||
else
|
||||
newNode->Operator = nullptr;
|
||||
|
||||
if (Parent)
|
||||
Parent->Children.push_back(newNode);
|
||||
vfsdbg("AddNewChild()->\"%s\"", newNode->Name);
|
||||
return newNode;
|
||||
}
|
||||
|
||||
FileSystemNode *GetChild(FileSystemNode *Parent, const char *Name)
|
||||
{
|
||||
vfsdbg("GetChild( Parent: \"%s\" Name: \"%s\" )", Parent->Name, Name);
|
||||
if (Parent)
|
||||
foreach (auto var in Parent->Children)
|
||||
if (strcmp(var->Name, Name) == 0)
|
||||
{
|
||||
vfsdbg("GetChild()->\"%s\"", var->Name);
|
||||
return var;
|
||||
}
|
||||
vfsdbg("GetChild()->nullptr");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
FileStatus RemoveChild(FileSystemNode *Parent, const char *Name)
|
||||
{
|
||||
vfsdbg("RemoveChild( Parent: \"%s\" Name: \"%s\" )", Parent->Name, Name);
|
||||
for (uint64_t i = 0; i < Parent->Children.size(); i++)
|
||||
if (strcmp(Parent->Children[i]->Name, Name) == 0)
|
||||
{
|
||||
Parent->Children.remove(i);
|
||||
vfsdbg("RemoveChild()->OK");
|
||||
return FileStatus::OK;
|
||||
}
|
||||
vfsdbg("RemoveChild()->NOT_FOUND");
|
||||
return FileStatus::NOT_FOUND;
|
||||
}
|
||||
|
||||
char *Virtual::NormalizePath(FileSystemNode *Parent, const char *Path)
|
||||
{
|
||||
vfsdbg("NormalizePath( Parent: \"%s\" Path: \"%s\" )", Parent->Name, Path);
|
||||
char *NormalizedPath = new char[strlen((char *)Path) + 1];
|
||||
char *RelativePath = nullptr;
|
||||
|
||||
cwk_path_normalize(Path, NormalizedPath, strlen((char *)Path) + 1);
|
||||
|
||||
if (cwk_path_is_relative(NormalizedPath))
|
||||
{
|
||||
char *ParentPath = GetPathFromNode(Parent);
|
||||
size_t PathSize = cwk_path_get_absolute(ParentPath, NormalizedPath, nullptr, 0);
|
||||
RelativePath = new char[PathSize + 1];
|
||||
cwk_path_get_absolute(ParentPath, NormalizedPath, RelativePath, PathSize + 1);
|
||||
delete[] ParentPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
RelativePath = new char[strlen(NormalizedPath) + 1];
|
||||
strcpy(RelativePath, NormalizedPath);
|
||||
}
|
||||
delete[] NormalizedPath;
|
||||
vfsdbg("NormalizePath()->\"%s\"", RelativePath);
|
||||
return RelativePath;
|
||||
}
|
||||
|
||||
FileStatus Virtual::FileExists(FileSystemNode *Parent, const char *Path)
|
||||
{
|
||||
vfsdbg("FileExists( Parent: \"%s\" Path: \"%s\" )", Parent->Name, Path);
|
||||
if (isempty((char *)Path))
|
||||
return FileStatus::INVALID_PATH;
|
||||
if (Parent == nullptr)
|
||||
Parent = FileSystemRoot;
|
||||
|
||||
char *NormalizedPath = NormalizePath(Parent, Path);
|
||||
FileSystemNode *Node = GetNodeFromPath(Parent, NormalizedPath);
|
||||
|
||||
if (Node == nullptr)
|
||||
{
|
||||
vfsdbg("FileExists()->NOT_FOUND");
|
||||
return FileStatus::NOT_FOUND;
|
||||
}
|
||||
else
|
||||
{
|
||||
vfsdbg("FileExists()->OK");
|
||||
return FileStatus::OK;
|
||||
}
|
||||
}
|
||||
|
||||
FileSystemNode *Virtual::Create(FileSystemNode *Parent, const char *Path)
|
||||
{
|
||||
SmartLock(VFSLock);
|
||||
|
||||
if (isempty((char *)Path))
|
||||
return nullptr;
|
||||
|
||||
vfsdbg("Virtual::Create( Parent: \"%s\" Path: \"%s\" )", Parent->Name, Path);
|
||||
|
||||
FileSystemNode *CurrentParent = nullptr;
|
||||
|
||||
if (Parent == nullptr)
|
||||
{
|
||||
if (FileSystemRoot->Children.size() >= 1)
|
||||
{
|
||||
if (FileSystemRoot->Children[0] == nullptr)
|
||||
panic("Root node is null!");
|
||||
|
||||
CurrentParent = FileSystemRoot->Children[0]; // 0 - filesystem root
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: check if here is a bug or something...
|
||||
const char *PathCopy;
|
||||
size_t length;
|
||||
PathCopy = (char *)Path;
|
||||
cwk_path_get_root(PathCopy, &length); // not working?
|
||||
foreach (auto var in FileSystemRoot->Children)
|
||||
if (!strcmp(var->Name, PathCopy))
|
||||
{
|
||||
CurrentParent = var;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
CurrentParent = Parent;
|
||||
|
||||
char *CleanPath = NormalizePath(CurrentParent, Path);
|
||||
|
||||
if (FileExists(CurrentParent, CleanPath) != FileStatus::NOT_FOUND)
|
||||
{
|
||||
error("File %s already exists.", CleanPath);
|
||||
goto CreatePathError;
|
||||
}
|
||||
|
||||
cwk_segment segment;
|
||||
if (!cwk_path_get_first_segment(CleanPath, &segment))
|
||||
{
|
||||
error("Path doesn't have any segments.");
|
||||
goto CreatePathError;
|
||||
}
|
||||
|
||||
warn("Virtual::Create( ) is not working properly.");
|
||||
do
|
||||
{
|
||||
char *SegmentName = new char[segment.end - segment.begin + 1];
|
||||
memcpy(SegmentName, segment.begin, segment.end - segment.begin);
|
||||
|
||||
if (GetChild(CurrentParent, SegmentName) == nullptr)
|
||||
CurrentParent = AddNewChild(CurrentParent, SegmentName);
|
||||
else
|
||||
CurrentParent = GetChild(CurrentParent, SegmentName);
|
||||
|
||||
delete[] SegmentName;
|
||||
} while (cwk_path_get_next_segment(&segment));
|
||||
|
||||
delete CleanPath;
|
||||
vfsdbg("Virtual::Create()->\"%s\"", CurrentParent->Name);
|
||||
return CurrentParent;
|
||||
|
||||
CreatePathError:
|
||||
vfsdbg("Virtual::Create()->nullptr");
|
||||
delete CleanPath;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
FileSystemNode *Virtual::CreateRoot(FileSystemOpeations *Operator, const char *RootName)
|
||||
{
|
||||
if (Operator == nullptr)
|
||||
return nullptr;
|
||||
vfsdbg("Setting root to %s", RootName);
|
||||
FileSystemNode *newNode = new FileSystemNode;
|
||||
strcpy(newNode->Name, RootName);
|
||||
newNode->Flags = NodeFlags::FS_DIRECTORY;
|
||||
newNode->Operator = Operator;
|
||||
FileSystemRoot->Children.push_back(newNode);
|
||||
return newNode;
|
||||
}
|
||||
|
||||
FILE *Virtual::Mount(FileSystemOpeations *Operator, const char *Path)
|
||||
{
|
||||
SmartLock(VFSLock);
|
||||
|
||||
if (Operator == nullptr)
|
||||
return nullptr;
|
||||
|
||||
if (isempty((char *)Path))
|
||||
return nullptr;
|
||||
|
||||
vfsdbg("Mounting %s", Path);
|
||||
FILE *file = new FILE;
|
||||
cwk_path_get_basename(Path, &file->Name, 0);
|
||||
file->Status = FileStatus::OK;
|
||||
file->Node = Create(nullptr, Path);
|
||||
file->Node->Operator = Operator;
|
||||
file->Node->Flags = NodeFlags::FS_MOUNTPOINT;
|
||||
return file;
|
||||
}
|
||||
|
||||
FileStatus Virtual::Unmount(FILE *File)
|
||||
{
|
||||
SmartLock(VFSLock);
|
||||
if (File == nullptr)
|
||||
return FileStatus::INVALID_PARAMETER;
|
||||
vfsdbg("Unmounting %s", File->Name);
|
||||
return FileStatus::OK;
|
||||
}
|
||||
|
||||
FILE *Virtual::Open(const char *Path, FileSystemNode *Parent)
|
||||
{
|
||||
SmartLock(VFSLock);
|
||||
vfsdbg("Opening %s with parent %s", Path, Parent->Name);
|
||||
|
||||
if (strcmp(Path, ".") == 0)
|
||||
{
|
||||
FILE *file = new FILE;
|
||||
FileStatus filestatus = FileStatus::OK;
|
||||
file->Node = Parent;
|
||||
if (file->Node == nullptr)
|
||||
file->Status = FileStatus::NOT_FOUND;
|
||||
const char *basename;
|
||||
cwk_path_get_basename(GetPathFromNode(Parent), &basename, nullptr);
|
||||
file->Name = basename;
|
||||
return file;
|
||||
}
|
||||
|
||||
if (strcmp(Path, "..") == 0)
|
||||
{
|
||||
if (Parent->Parent != nullptr)
|
||||
Parent = Parent->Parent;
|
||||
|
||||
FILE *file = new FILE;
|
||||
FileStatus filestatus = FileStatus::OK;
|
||||
file->Node = Parent;
|
||||
if (file->Node == nullptr)
|
||||
file->Status = FileStatus::NOT_FOUND;
|
||||
const char *basename;
|
||||
cwk_path_get_basename(GetPathFromNode(Parent), &basename, nullptr);
|
||||
file->Name = basename;
|
||||
return file;
|
||||
}
|
||||
|
||||
if (Parent == nullptr)
|
||||
{
|
||||
if (FileSystemRoot->Children.size() >= 1)
|
||||
Parent = FileSystemRoot->Children[0]; // 0 - filesystem root
|
||||
else
|
||||
{
|
||||
// TODO: check if here is a bug or something...
|
||||
const char *PathCopy;
|
||||
size_t length;
|
||||
PathCopy = (char *)Path;
|
||||
cwk_path_get_root(PathCopy, &length); // not working?
|
||||
foreach (auto var in FileSystemRoot->Children)
|
||||
if (!strcmp(var->Name, PathCopy))
|
||||
{
|
||||
Parent = var;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
char *CleanPath = NormalizePath(Parent, Path);
|
||||
|
||||
FILE *file = new FILE;
|
||||
FileStatus filestatus = FileStatus::OK;
|
||||
filestatus = FileExists(Parent, CleanPath);
|
||||
/* TODO: Check for other errors */
|
||||
|
||||
if (filestatus != FileStatus::OK)
|
||||
{
|
||||
foreach (auto var in FileSystemRoot->Children)
|
||||
if (!strcmp(var->Name, CleanPath))
|
||||
{
|
||||
file->Node = var;
|
||||
if (file->Node == nullptr)
|
||||
goto OpenNodeFail;
|
||||
const char *basename;
|
||||
cwk_path_get_basename(GetPathFromNode(var), &basename, nullptr);
|
||||
file->Name = basename;
|
||||
goto OpenNodeExit;
|
||||
}
|
||||
|
||||
file->Node = GetNodeFromPath(FileSystemRoot->Children[0], CleanPath);
|
||||
if (file->Node != nullptr)
|
||||
{
|
||||
const char *basename;
|
||||
cwk_path_get_basename(GetPathFromNode(file->Node), &basename, nullptr);
|
||||
file->Name = basename;
|
||||
goto OpenNodeExit;
|
||||
}
|
||||
|
||||
OpenNodeFail:
|
||||
file->Status = filestatus;
|
||||
file->Node = nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
file->Node = GetNodeFromPath(Parent, CleanPath);
|
||||
if (file->Node == nullptr)
|
||||
file->Status = FileStatus::NOT_FOUND;
|
||||
const char *basename;
|
||||
cwk_path_get_basename(CleanPath, &basename, nullptr);
|
||||
file->Name = basename;
|
||||
return file;
|
||||
}
|
||||
OpenNodeExit:
|
||||
return file;
|
||||
}
|
||||
|
||||
uint64_t Virtual::Read(FILE *File, uint64_t Offset, uint8_t *Buffer, uint64_t Size)
|
||||
{
|
||||
SmartLock(VFSLock);
|
||||
if (File == nullptr)
|
||||
return 0;
|
||||
|
||||
File->Status = FileStatus::OK;
|
||||
|
||||
if (File->Node == nullptr)
|
||||
{
|
||||
File->Status = FileStatus::INVALID_PARAMETER;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (File->Node->Operator == nullptr)
|
||||
{
|
||||
File->Status = FileStatus::INVALID_PARAMETER;
|
||||
return 0;
|
||||
}
|
||||
vfsdbg("Reading %s out->%016x", File->Name, Buffer);
|
||||
return File->Node->Operator->Read(File->Node, Offset, Size, Buffer);
|
||||
}
|
||||
|
||||
uint64_t Virtual::Write(FILE *File, uint64_t Offset, uint8_t *Buffer, uint64_t Size)
|
||||
{
|
||||
SmartLock(VFSLock);
|
||||
if (File == nullptr)
|
||||
return 0;
|
||||
|
||||
File->Status = FileStatus::OK;
|
||||
|
||||
if (File->Node == nullptr)
|
||||
{
|
||||
File->Status = FileStatus::INVALID_PARAMETER;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (File->Node->Operator == nullptr)
|
||||
{
|
||||
File->Status = FileStatus::INVALID_PARAMETER;
|
||||
return 0;
|
||||
}
|
||||
vfsdbg("Writing %s out->%016x", File->Name, Buffer);
|
||||
return File->Node->Operator->Write(File->Node, Offset, Size, Buffer);
|
||||
}
|
||||
|
||||
FileStatus Virtual::Close(FILE *File)
|
||||
{
|
||||
SmartLock(VFSLock);
|
||||
if (File == nullptr)
|
||||
return FileStatus::INVALID_HANDLE;
|
||||
vfsdbg("Closing %s", File->Name);
|
||||
delete File;
|
||||
return FileStatus::OK;
|
||||
}
|
||||
|
||||
Virtual::Virtual()
|
||||
{
|
||||
trace("Initializing virtual file system...");
|
||||
FileSystemRoot = new FileSystemNode;
|
||||
FileSystemRoot->Flags = NodeFlags::FS_MOUNTPOINT;
|
||||
FileSystemRoot->Operator = nullptr;
|
||||
FileSystemRoot->Parent = nullptr;
|
||||
strcpy(FileSystemRoot->Name, "root");
|
||||
cwk_path_set_style(CWK_STYLE_UNIX);
|
||||
}
|
||||
|
||||
Virtual::~Virtual()
|
||||
{
|
||||
warn("Tried to uninitialize Virtual File System!");
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user