vfs experiments 1

This commit is contained in:
2024-02-24 13:59:45 +01:00
parent f5e9867d5b
commit 548fd6511d
30 changed files with 746 additions and 26 deletions

View File

@@ -4,11 +4,15 @@
#include <cstddef>
#include "LockGuard.hpp"
#include "MemFs.hpp"
#include "MountTable.hpp"
#include "SerialTty.hpp"
#include "SkipList.hpp"
#include "String.hpp"
#include "TestTemplates.hpp"
#include "TtyManager.hpp"
#include "VFSGlobals.hpp"
#include "VFSTester.hpp"
#include "VMA.hpp"
#include "asserts.hpp"
#include "globals.hpp"
@@ -202,6 +206,12 @@ void user_task() {
}
}
void vfs_tester() {
VFSTester vfsTester;
vfsTester.test();
remove_self();
}
void ktask_main() {
GlobalTtyManager.add_tty(new SerialTty());
@@ -214,6 +224,8 @@ void ktask_main() {
new_ktask(templates_tester, "templates_tester");
new_ktask(templates_tester, "templates_tester2");
new_ktask(stress_tester, "stress_tester");
VFSGlobals::mounts.add_mount(new MemFs(&VFSGlobals::root));
new_ktask(vfs_tester, "vfs_tester");
for (int i = 0; i < saved_modules_size; i++) {
GlobalTtyManager.all_tty_putstr("Starting ");

View File

@@ -8,6 +8,8 @@
#include "memman.hpp"
#include "misc.hpp"
#include "FDT.hpp"
// Returns a free page frame in HHDM
static uint64_t *get_free_frame() {
uint64_t *res = static_cast<uint64_t *>(get4k());
@@ -160,6 +162,10 @@ int AddressSpace::unmap(void *virt) {
invlpg((void *) ((uint64_t) virt & 0x000FFFFFFFFFF000ULL));
return 1;
}
FDT *AddressSpace::getFdt() {
if (!_fdt) _fdt = new FDT();
return _fdt;
}
static volatile struct limine_kernel_address_request kernel_address_request = {
.id = LIMINE_KERNEL_ADDRESS_REQUEST,

View File

@@ -8,6 +8,8 @@
#include <stddef.h>
#include <stdint.h>
#include "PointersCollection.hpp"
#define PAGE_SIZE 4096
#define KERN_V2P(a) ((((uintptr_t) (a) + kernel_phys_base) & ~kernel_virt_base))
@@ -18,6 +20,8 @@
#define HHDM_V2P(a) ((((uintptr_t) (a)) & ~HHDM_BEGIN))
#define HHDM_P2V(a) ((((uintptr_t) (a)) | HHDM_BEGIN))
class FDT;
class AddressSpace {
public:
AddressSpace();
@@ -36,9 +40,13 @@ public:
return (uint64_t *) HHDM_V2P(PML4);
}
FDT *getFdt();
private:
// Pointer to PML4 in HDDM
uint64_t *PML4;
FDT *_fdt = nullptr;
};
extern AddressSpace *KERN_AddressSpace;

View File

@@ -2,4 +2,5 @@ target_include_directories(kernel.elf PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_sources(kernel.elf PRIVATE mutex.cpp cppsupport.cpp Spinlock.cpp LockGuard.cpp rand.cpp VMA.cpp asserts.cpp TtyManager.cpp Tty.cpp cv.cpp)
add_subdirectory(templates)
add_subdirectory(templates)
add_subdirectory(vfs)

View File

@@ -60,6 +60,10 @@ public:
size++;
}
bool empty() const {
return tail == nullptr;
}
T &back() {
if (tail != nullptr) {
assert(size > 0);
@@ -69,6 +73,15 @@ public:
assert(false);
}
const T &back() const {
if (tail != nullptr) {
assert(size > 0);
return tail->val;
}
assert(false);
}
void pop_back() {
if (!head) {
assert(size == 0);

View File

@@ -26,11 +26,13 @@ public:
UniquePtr &operator=(UniquePtr const &other) = delete;
UniquePtr(UniquePtr &&other) {
delete ptr;
ptr = other.ptr;
other.ptr = nullptr;
}
UniquePtr &operator=(UniquePtr &&other) {
delete ptr;
ptr = other.ptr;
other.ptr = nullptr;
return *this;

View File

@@ -276,6 +276,7 @@ public:
while (curL > 0 &&
root->next[curL] == nullptr)
curL--;
cur->data = V();
nodeAllocator.push(cur);
return true;
};

View File

@@ -11,59 +11,62 @@
class String {
public:
String() noexcept {
data = static_cast<char *>(kmalloc(1 * sizeof(char)));
_data = static_cast<char *>(kmalloc(1 * sizeof(char)));
curLen = 0;
data[0] = '\0';
_data[0] = '\0';
}
String(const char *in) noexcept {
curLen = strlen(in);
data = static_cast<char *>(kmalloc((curLen + 1) * sizeof(char)));
data[0] = '\0';
_data = static_cast<char *>(kmalloc((curLen + 1) * sizeof(char)));
_data[0] = '\0';
strcat(data, in);
strcat(_data, in);
}
String(String const &str) noexcept {
curLen = str.curLen;
data = static_cast<char *>(kmalloc((curLen + 1) * sizeof(char)));
data[0] = '\0';
_data = static_cast<char *>(kmalloc((curLen + 1) * sizeof(char)));
_data[0] = '\0';
strcat(data, str.data);
strcat(_data, str._data);
}
String(String &&str) noexcept {
data = str.data;
_data = str._data;
curLen = str.curLen;
str.data = nullptr;
str._data = static_cast<char *>(kmalloc(1 * sizeof(char)));
str.curLen = 0;
str._data[0] = '\0';
}
String &operator=(String str) noexcept {
std::swap(data, str.data);
std::swap(_data, str._data);
std::swap(curLen, str.curLen);
return *this;
}
~String() noexcept {
if (data == nullptr) return;
kfree(data);
data = nullptr;
if (_data == nullptr) return;
kfree(_data);
_data = nullptr;
curLen = 0;
}
String &operator+=(String const &rhs) {
data = static_cast<char *>(krealloc(data, sizeof(char) * (curLen + rhs.curLen + 1)));
assert(data != nullptr);
_data = static_cast<char *>(krealloc(_data, sizeof(char) * (curLen + rhs.curLen + 1)));
assert(_data != nullptr);
strcat(data, rhs.data);
strcat(_data, rhs._data);
curLen += rhs.curLen;
return *this;
}
String &operator+=(int value) {
String &operator+=(unsigned long value) {
char buf[20];
itoa(value, buf, 10);
@@ -71,9 +74,31 @@ public:
return *this;
}
String &operator+=(unsigned long long value) {
char buf[32];
itoa(value, buf, 10);
*this += buf;
return *this;
}
String &operator+=(char c) {
_data = static_cast<char *>(krealloc(_data, sizeof(char) * (curLen + 2)));
assert(_data != nullptr);
_data[curLen] = c;
_data[curLen + 1] = '\0';
curLen++;
return *this;
}
const char *c_str() const {
return data;
return _data;
}
char *data() {
return _data;
}
size_t length() const {
@@ -85,28 +110,28 @@ public:
}
bool operator==(String const &rhs) const {
return strcmp(data, rhs.data) == 0;
return strcmp(_data, rhs._data) == 0;
}
bool operator!=(String const &rhs) const {
return strcmp(data, rhs.data) != 0;
return strcmp(_data, rhs._data) != 0;
}
bool operator<(String const &rhs) const {
return strcmp(data, rhs.data) < 0;
return strcmp(_data, rhs._data) < 0;
}
bool operator<=(String const &rhs) const {
return strcmp(data, rhs.data) <= 0;
return strcmp(_data, rhs._data) <= 0;
}
bool operator>(String const &rhs) const {
return strcmp(data, rhs.data) > 0;
return strcmp(_data, rhs._data) > 0;
}
private:
size_t curLen = 0;
char *data;
char *_data;
};
#endif

View File

@@ -119,6 +119,25 @@ public:
bool empty() const {
return curSize == 0;
}
T &back() {
assert(size() != 0);
return data[size() - 1];
}
const T &back() const {
assert(size() != 0);
return data[size() - 1];
}
Vector subvector(size_t start, size_t end) const {
Vector out;
if (start >= size()) return out;
end = end > size() ? size() : end;
for (size_t i = start; i < end; i++) {
out.emplace_back(data[i]);
}
return out;
}
private:
size_t capacity = 2;

View File

@@ -0,0 +1,14 @@
target_include_directories(kernel.elf PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_sources(kernel.elf PRIVATE
Node.cpp
MemFs.cpp
Filesystem.cpp
MountTable.cpp
Path.cpp
VFSTester.cpp
VFSApi.cpp
FDT.cpp
VFSGlobals.cpp
File.cpp
)

43
src/kernel/vfs/FDT.cpp Normal file
View File

@@ -0,0 +1,43 @@
//
// Created by Stepan Usatiuk on 23.02.2024.
//
#include "FDT.hpp"
#include "File.hpp"
#include "MountTable.hpp"
#include "PointersCollection.hpp"
#include "VFSGlobals.hpp"
#include "paging.hpp"
FDT::FD FDT::open(const Path &p) {
if (auto n = VFSGlobals::root.traverse(p)) {
_files.add(_cur_fd++, UniquePtr<File>(new File(n)));
return _cur_fd - 1;
}
return -1;
}
void FDT::close(FDT::FD fd) {
if (auto f = _files.find(fd))
if (!f->end)
if (f->key == fd) {
_files.erase(fd);
}
}
File *FDT::get(FDT::FD fd) {
if (auto f = _files.find(fd))
if (!f->end)
if (f->key == fd)
return f->data.get();
return nullptr;
}
FDT *FDT::current() {
return cur_task()->addressSpace->getFdt();
}
FDHandle::FDHandle(FDT::FD fd) : _fd(fd) {
}
FDHandle::~FDHandle() {
FDT::current()->close(_fd);
}

45
src/kernel/vfs/FDT.hpp Normal file
View File

@@ -0,0 +1,45 @@
//
// Created by Stepan Usatiuk on 23.02.2024.
//
#ifndef OS2_FDT_HPP
#define OS2_FDT_HPP
#include "Path.hpp"
#include "PointersCollection.hpp"
#include "SkipList.hpp"
#include "mutex.hpp"
class File;
class Node;
class FDT {
public:
using FD = int64_t;
FD open(const Path &p);
void close(FD fd);
File *get(FD fd);
static FDT *current();
private:
SkipList<FD, UniquePtr<File>> _files;
int64_t _cur_fd = 10;
Mutex _mtx;
};
class FDHandle {
public:
FDHandle(FDT::FD fd);
~FDHandle();
FDHandle(const File &f) = delete;
FDHandle &operator=(const File &o) = delete;
FDT::FD get() { return _fd; }
private:
FDT::FD _fd;
};
#endif//OS2_FDT_HPP

37
src/kernel/vfs/File.cpp Normal file
View File

@@ -0,0 +1,37 @@
//
// Created by Stepan Usatiuk on 24.02.2024.
//
#include "File.hpp"
#include "Node.hpp"
File::File(Node *node) : _n(node) {
_n->lock();
}
File::~File() {
_n->unlock();
}
Node *File::node() {
return _n;
}
NodeDir *File::dir() {
if (_n && _n->type() == Node::DIR) return static_cast<NodeDir *>(_n);
return nullptr;
}
NodeFile *File::file() {
if (_n && _n->type() == Node::FILE) return static_cast<NodeFile *>(_n);
return nullptr;
}
uint64_t File::seek(uint64_t pos) {
_pos = pos;
}
uint64_t File::read(char *buf, uint64_t size) {
if (file()) return file()->read(buf, _pos, size);
}
uint64_t File::write(const char *buf, uint64_t size) {
if (file()) return file()->write(buf, _pos, size);
}
uint64_t File::size() {
if (file()) return file()->size();
}

35
src/kernel/vfs/File.hpp Normal file
View File

@@ -0,0 +1,35 @@
//
// Created by Stepan Usatiuk on 24.02.2024.
//
#ifndef OS2_FILE_HPP
#define OS2_FILE_HPP
#include <cstdint>
class Node;
class NodeDir;
class NodeFile;
class File {
public:
File(Node *n);
~File();
File(const File &f) = delete;
File &operator=(const File &o) = delete;
Node *node();
NodeDir *dir();
NodeFile *file();
uint64_t seek(uint64_t pos);
uint64_t read(char *buf, uint64_t size);
uint64_t write(const char *buf, uint64_t size);
uint64_t size();
private:
Node *_n;
uint64_t _pos = 0;
};
#endif//OS2_FILE_HPP

View File

@@ -0,0 +1,11 @@
//
// Created by Stepan Usatiuk on 23.02.2024.
//
#include "Filesystem.hpp"
Filesystem::~Filesystem() = default;
Filesystem::Filesystem(NodeDir *mounted_on) : _mounted_on(mounted_on) {
assert(_mounted_on->type() == Node::DIR);
_mounted_on->set_mounted(this);
}

View File

@@ -0,0 +1,21 @@
//
// Created by Stepan Usatiuk on 23.02.2024.
//
#ifndef OS2_FILESYSTEM_HPP
#define OS2_FILESYSTEM_HPP
#include "Node.hpp"
class Filesystem {
public:
Filesystem(NodeDir *mounted_on);
virtual NodeDir *root() = 0;
virtual ~Filesystem() = 0;
NodeDir *_mounted_on;
};
#endif//OS2_FILESYSTEM_HPP

50
src/kernel/vfs/MemFs.cpp Normal file
View File

@@ -0,0 +1,50 @@
//
// Created by Stepan Usatiuk on 23.02.2024.
//
#include "MemFs.hpp"
#include "LockGuard.hpp"
Vector<Node *> MemFs::MemFsNodeDir::children() {
// assert(_lock.owner() == cur_task());
Vector<Node *> out;
for (auto c: _children) {
out.emplace_back(c.data);
}
return out;
}
NodeDir *MemFs::MemFsNodeDir::mkdir(const String &name) {
assert(_lock.owner() == cur_task());
auto newnode = new MemFsNodeDir();
newnode->_name = name;
_children.add(name, newnode);
return newnode;
}
NodeFile *MemFs::MemFsNodeDir::mkfile(const String &name) {
assert(_lock.owner() == cur_task());
auto newfile = new MemFsNodeFile(name);
_children.add(name, newfile);
return newfile;
}
bool MemFs::MemFsNodeFile::read(char *buf, size_t start, size_t num) {
assert(_lock.owner() == cur_task());
if (start >= _bytes.size()) return false;
if (start + num > _bytes.size()) return false;
for (size_t i = 0; i < num; i++) {
buf[i] = _bytes[start + i];
}
return false;
}
bool MemFs::MemFsNodeFile::write(const char *buf, size_t start, size_t num) {
assert(_lock.owner() == cur_task());
// fixme
while (_bytes.size() <= start + num) _bytes.emplace_back(0);
for (size_t i = 0; i < num; i++) {
_bytes[start + i] = buf[i];
}
return true;
}
size_t MemFs::MemFsNodeFile::size() {
return _bytes.size();
}

47
src/kernel/vfs/MemFs.hpp Normal file
View File

@@ -0,0 +1,47 @@
//
// Created by Stepan Usatiuk on 23.02.2024.
//
#ifndef OS2_MEMFS_HPP
#define OS2_MEMFS_HPP
#include "Filesystem.hpp"
#include "Node.hpp"
#include "SkipList.hpp"
#include "Vector.hpp"
class MemFs : public Filesystem {
struct MemFsNodeDir : public NodeDir {
public:
Vector<Node *> children() override;
NodeDir *mkdir(const String &name) override;
NodeFile *mkfile(const String &name) override;
private:
SkipList<String, Node *> _children;
};
struct MemFsNodeFile : public NodeFile {
public:
MemFsNodeFile(const String &name) { _name = name; }
bool read(char *buf, size_t start, size_t num) override;
bool write(const char *buf, size_t start, size_t num) override;
size_t size() override;
private:
Vector<uint8_t> _bytes;
};
public:
MemFs(NodeDir *mounted_on) : Filesystem(mounted_on) {}
NodeDir *root() override { return &_rootNode; }
private:
MemFsNodeDir _rootNode;
};
#endif//OS2_MEMFS_HPP

View File

@@ -0,0 +1,8 @@
//
// Created by Stepan Usatiuk on 23.02.2024.
//
#include "MountTable.hpp"
void MountTable::add_mount(Filesystem *fs) {
_mounts.emplace_front(fs);
}

View File

@@ -0,0 +1,23 @@
//
// Created by Stepan Usatiuk on 23.02.2024.
//
#ifndef OS2_MOUNTTABLE_HPP
#define OS2_MOUNTTABLE_HPP
#include "String.hpp"
#include "Filesystem.hpp"
#include "Node.hpp"
#include "Path.hpp"
class MountTable {
public:
void add_mount(Filesystem *fs);
private:
List<Filesystem *> _mounts;
};
#endif//OS2_MOUNTTABLE_HPP

33
src/kernel/vfs/Node.cpp Normal file
View File

@@ -0,0 +1,33 @@
//
// Created by Stepan Usatiuk on 23.02.2024.
//
#include "Node.hpp"
#include "LockGuard.hpp"
#include "Filesystem.hpp"
Node::~Node() = default;
Node *Node::traverse(const Path &path) {
// lock
NodeDir &nodeDir = static_cast<NodeDir &>(*this);
if (nodeDir._mount) return nodeDir._mount->root()->traverse(path);
if (path.empty()) return this;
if (_type == DIR) {
// Horribly inefficient
auto children = nodeDir.children();
for (size_t i = 0; i < children.size(); i++) {
if (children[i]->name() == path[0]) {
return children[i]->traverse(path.subvector(1, path.size()));
}
}
return nullptr;
}
return nullptr;
}
void NodeDir::set_mounted(Filesystem *mount) {
assert(_type == DIR);
assert(_mount == nullptr);
_mount = mount;
}

67
src/kernel/vfs/Node.hpp Normal file
View File

@@ -0,0 +1,67 @@
//
// Created by Stepan Usatiuk on 23.02.2024.
//
#ifndef OS2_NODE_HPP
#define OS2_NODE_HPP
#include "List.hpp"
#include "String.hpp"
#include "Path.hpp"
#include "mutex.hpp"
class Filesystem;
class Node {
public:
enum Type {
FILE,
DIR,
INVALID
};
virtual ~Node() = 0;
Type type() const { return _type; }
const String &name() const { return _name; }
virtual Node *traverse(const Path &path);
void lock() { _lock.lock(); }
void unlock() { _lock.unlock(); }
protected:
Node(Type type) : _type(type) {}
Type _type = Type::INVALID;
// This is uuugly
Mutex _lock;
String _name;
Filesystem *_mount = nullptr;
};
class NodeFile;
class NodeDir : public Node {
public:
virtual Vector<Node *> children() = 0;
virtual NodeDir *mkdir(const String &name) = 0;
virtual NodeFile *mkfile(const String &name) = 0;
virtual void set_mounted(Filesystem *mount);
protected:
NodeDir() : Node(Type::DIR) {}
};
class NodeFile : public Node {
public:
virtual bool read(char *buf, size_t start, size_t num) = 0;
virtual bool write(const char *buf, size_t start, size_t num) = 0;
virtual size_t size() = 0;
protected:
NodeFile() : Node(Type::FILE) {}
};
#endif//OS2_NODE_HPP

21
src/kernel/vfs/Path.cpp Normal file
View File

@@ -0,0 +1,21 @@
//
// Created by Stepan Usatiuk on 23.02.2024.
//
#include "Path.hpp"
Path StrToPath(const String &str) {
if (str.length() == 0) return Path();
Path out;
String buf;
for (size_t i = 0; i < str.length(); i++) {
if (str.c_str()[i] == '/') {
if (buf.length() > 0)
out.emplace_back(std::move(buf));
} else {
buf += str.c_str()[i];
}
}
if (buf.length() > 0)
out.emplace_back(std::move(buf));
return out;
}

16
src/kernel/vfs/Path.hpp Normal file
View File

@@ -0,0 +1,16 @@
//
// Created by Stepan Usatiuk on 23.02.2024.
//
#ifndef OS2_PATH_HPP
#define OS2_PATH_HPP
#include "List.hpp"
#include "String.hpp"
#include "Vector.hpp"
using Path = Vector<String>;
Path StrToPath(const String &str);
#endif//OS2_PATH_HPP

37
src/kernel/vfs/VFSApi.cpp Normal file
View File

@@ -0,0 +1,37 @@
//
// Created by Stepan Usatiuk on 23.02.2024.
//
#include "VFSApi.hpp"
#include "File.hpp"
#include "Node.hpp"
bool VFSApi::mkdir(const Path &path) {
auto root = path.subvector(0, path.size() - 1);
FDHandle root_fd = FDHandle(FDT::current()->open(root));
if (root_fd.get() == -1) return false;
File *root_f = FDT::current()->get(root_fd.get());
if (!root_f->dir()) return false;
root_f->dir()->mkdir(path.back());
return true;
}
bool VFSApi::touch(const Path &path) {
auto root = path.subvector(0, path.size() - 1);
FDHandle root_fd = FDHandle(FDT::current()->open(root));
if (root_fd.get() == -1) return false;
File *root_f = FDT::current()->get(root_fd.get());
if (!root_f->dir()) return false;
root_f->dir()->mkfile(path.back());
return true;
}
FDT::FD VFSApi::open(const Path &path) {
return FDT::current()->open(path);
}
void VFSApi::close(FDT::FD fd) {
return FDT::current()->close(fd);
}
File *VFSApi::get(FDT::FD fd) {
return FDT::current()->get(fd);
}

23
src/kernel/vfs/VFSApi.hpp Normal file
View File

@@ -0,0 +1,23 @@
//
// Created by Stepan Usatiuk on 23.02.2024.
//
#ifndef OS2_VFSAPI_HPP
#define OS2_VFSAPI_HPP
#include "FDT.hpp"
#include "Path.hpp"
namespace VFSApi {
bool mkdir(const Path &path);
bool touch(const Path &path);
FDT::FD open(const Path &path);
File *get(FDT::FD fd);
void close(FDT::FD fd);
};// namespace VFSApi
#endif//OS2_VFSAPI_HPP

View File

@@ -0,0 +1,21 @@
//
// Created by Stepan Usatiuk on 24.02.2024.
//
#include "VFSGlobals.hpp"
Vector<Node *> RootNode::children() {
assert(false);
return {};
}
NodeDir *RootNode::mkdir(const String &name) {
assert(false);
return nullptr;
}
NodeFile *RootNode::mkfile(const String &name) {
assert(false);
return nullptr;
}
RootNode VFSGlobals::root;
MountTable VFSGlobals::mounts;

View File

@@ -0,0 +1,24 @@
//
// Created by Stepan Usatiuk on 24.02.2024.
//
#ifndef OS2_VFSGLOBALS_HPP
#define OS2_VFSGLOBALS_HPP
#include "MountTable.hpp"
#include "Node.hpp"
class RootNode : public NodeDir {
public:
Vector<Node *> children() override;
NodeDir *mkdir(const String &name) override;
NodeFile *mkfile(const String &name) override;
};
namespace VFSGlobals {
extern RootNode root;
extern MountTable mounts;
};// namespace VFSGlobals
#endif//OS2_VFSGLOBALS_HPP

View File

@@ -0,0 +1,42 @@
//
// Created by Stepan Usatiuk on 23.02.2024.
//
#include "VFSTester.hpp"
#include "File.hpp"
#include "VFSApi.hpp"
void VFSTester::test() {
VFSApi::mkdir(StrToPath("/hello"));
VFSApi::mkdir(StrToPath("/hello/hellod2"));
VFSApi::touch(StrToPath("/hellof"));
VFSApi::touch(StrToPath("/hello/f2"));
FDT::FD a = VFSApi::open(StrToPath("/hello"));
FDT::FD b = VFSApi::open(StrToPath("/hello/hellod2"));
FDT::FD c = VFSApi::open(StrToPath("/hellof"));
FDT::FD d = VFSApi::open(StrToPath("/hello/f2"));
{
String t("hello wooooorld");
File *cf = VFSApi::get(c);
cf->write(t.c_str(), t.length() + 1);
}
assert(a != -1);
assert(b != -1);
assert(c != -1);
assert(d != -1);
VFSApi::close(a);
VFSApi::close(b);
VFSApi::close(c);
VFSApi::close(d);
c = VFSApi::open(StrToPath("/hellof"));
assert(c != -1);
{
String t("aaaaaaaaaaaaaaaaaaaa");
File *cf = VFSApi::get(c);
cf->read(t.data(), cf->size());
assert(t == "hello wooooorld");
}
}

View File

@@ -0,0 +1,15 @@
//
// Created by Stepan Usatiuk on 23.02.2024.
//
#ifndef OS2_VFSTESTER_HPP
#define OS2_VFSTESTER_HPP
class VFSTester {
public:
void test();
};
#endif//OS2_VFSTESTER_HPP