btrfs: partially implement filesystem initialization

Change-Id: I7fb3340dc39b3331d30e5113b5860d936efab253
Reviewed-on: https://review.haiku-os.org/c/1395
Reviewed-by: Adrien Destugues <[email protected]>
This commit is contained in:
Les De Ridder
2019-04-21 13:39:46 +00:00
committed by Jérôme Duval
parent 9d6d044fdd
commit aa08671d9c
10 changed files with 351 additions and 23 deletions
@@ -121,7 +121,8 @@ Inode::Create(Transaction& transaction, ino_t id, Inode* parent, int32 mode,
TRACE("Inode::Create() id % " B_PRIu64 " mode %" B_PRId32 " flags %" TRACE("Inode::Create() id % " B_PRIu64 " mode %" B_PRId32 " flags %"
B_PRIu64"\n", id, flags, mode); B_PRIu64"\n", id, flags, mode);
Volume* volume = parent->GetVolume(); Volume* volume = parent != NULL ?
parent->GetVolume() : transaction.GetJournal()->GetVolume();
uint64 nbytes = size; // allocated size uint64 nbytes = size; // allocated size
if (size > volume->MaxInlineSize()) if (size > volume->MaxInlineSize())
nbytes = (size / volume->SectorSize() + 1) * volume->SectorSize(); nbytes = (size / volume->SectorSize() + 1) * volume->SectorSize();
@@ -135,7 +136,8 @@ Inode::Create(Transaction& transaction, ino_t id, Inode* parent, int32 mode,
inode.blockgroup = 0; // normal inode only inode.blockgroup = 0; // normal inode only
inode.num_links = B_HOST_TO_LENDIAN_INT32(1); inode.num_links = B_HOST_TO_LENDIAN_INT32(1);
inode.uid = B_HOST_TO_LENDIAN_INT32(geteuid()); inode.uid = B_HOST_TO_LENDIAN_INT32(geteuid());
inode.gid = B_HOST_TO_LENDIAN_INT32(parent ? parent->GroupID() : getegid()); inode.gid = B_HOST_TO_LENDIAN_INT32(parent != NULL ?
parent->GroupID() : getegid());
inode.mode = B_HOST_TO_LENDIAN_INT32(mode);; inode.mode = B_HOST_TO_LENDIAN_INT32(mode);;
inode.rdev = 0; // normal file only inode.rdev = 0; // normal file only
inode.flags = B_HOST_TO_LENDIAN_INT64(flags); inode.flags = B_HOST_TO_LENDIAN_INT64(flags);
+13 -2
View File
@@ -1,24 +1,35 @@
SubDir HAIKU_TOP src add-ons kernel file_systems btrfs ; SubDir HAIKU_TOP src add-ons kernel file_systems btrfs ;
UsePrivateHeaders [ FDirName kernel util ] ; UsePrivateHeaders [ FDirName kernel util ] ;
UsePrivateHeaders shared storage ; UsePrivateHeaders shared storage file_systems ;
UsePrivateKernelHeaders ; UsePrivateKernelHeaders ;
UseBuildFeatureHeaders zlib ; UseBuildFeatureHeaders zlib ;
DEFINES += DEBUG_APP="\\\"btrfs\\\"" ;
UseHeaders [ FDirName $(HAIKU_TOP) src libs uuid ] : true ;
Includes [ FGristFiles Inode.cpp ] Includes [ FGristFiles Inode.cpp ]
: [ BuildFeatureAttribute zlib : headers ] ; : [ BuildFeatureAttribute zlib : headers ] ;
KernelAddon btrfs : KernelAddon btrfs :
btrfs_disk_system.cpp
kernel_interface.cpp kernel_interface.cpp
Attribute.cpp Attribute.cpp
AttributeIterator.cpp AttributeIterator.cpp
BTree.cpp BTree.cpp
Chunk.cpp Chunk.cpp
CRCTable.cpp CRCTable.cpp
DebugSupport.cpp
DirectoryIterator.cpp DirectoryIterator.cpp
ExtentAllocator.cpp ExtentAllocator.cpp
Inode.cpp Inode.cpp
Journal.cpp Journal.cpp
Volume.cpp Volume.cpp
: kernel_libz.a :
kernel_libz.a
libuuid_kernel.a
; ;
SEARCH on [ FGristFiles DebugSupport.cpp ]
+= [ FDirName $(HAIKU_TOP) src add-ons kernel file_systems shared ] ;
@@ -45,6 +45,7 @@ public:
Transaction(); Transaction();
~Transaction(); ~Transaction();
Journal* GetJournal() const { return fJournal; }
int32 ID() const { return fJournal->TransactionID(); } int32 ID() const { return fJournal->TransactionID(); }
uint64 SystemID() const uint64 SystemID() const
{ return fJournal->SystemTransactionID(); } { return fJournal->SystemTransactionID(); }
@@ -1,7 +1,9 @@
/* /*
* Copyright 2019, Les De Ridder, [email protected]
* Copyright 2017, Chế Vũ Gia Hy, [email protected]. * Copyright 2017, Chế Vũ Gia Hy, [email protected].
* Copyright 2011, Jérôme Duval, [email protected]. * Copyright 2011, Jérôme Duval, [email protected].
* Copyright 2008-2010, Axel Dörfler, [email protected]. * Copyright 2008-2010, Axel Dörfler, [email protected].
*
* This file may be used under the terms of the MIT License. * This file may be used under the terms of the MIT License.
*/ */
@@ -13,9 +15,11 @@
#include "BTree.h" #include "BTree.h"
#include "CachedBlock.h" #include "CachedBlock.h"
#include "Chunk.h" #include "Chunk.h"
#include "DebugSupport.h"
#include "ExtentAllocator.h"
#include "Inode.h" #include "Inode.h"
#include "Journal.h" #include "Journal.h"
#include "ExtentAllocator.h" #include "Utility.h"
//#define TRACE_BTRFS //#define TRACE_BTRFS
@@ -191,7 +195,7 @@ DeviceOpener::GetSize(off_t* _size, uint32* _blockSize)
bool bool
btrfs_super_block::IsValid() btrfs_super_block::IsValid() const
{ {
// TODO: check some more values! // TODO: check some more values!
if (strncmp(magic, BTRFS_SUPER_BLOCK_MAGIC, sizeof(magic)) != 0) if (strncmp(magic, BTRFS_SUPER_BLOCK_MAGIC, sizeof(magic)) != 0)
@@ -201,6 +205,33 @@ btrfs_super_block::IsValid()
} }
void
btrfs_super_block::Initialize(const char* name, off_t numBlocks,
uint32 blockSize, uint32 sectorSize)
{
memset(this, 0, sizeof(btrfs_super_block));
uuid_generate(fsid);
blocknum = B_HOST_TO_LENDIAN_INT64(BTRFS_SUPER_BLOCK_OFFSET);
num_devices = B_HOST_TO_LENDIAN_INT64(1);
strncpy(magic, BTRFS_SUPER_BLOCK_MAGIC_TEMPORARY, sizeof(magic));
generation = B_HOST_TO_LENDIAN_INT64(1);
root = B_HOST_TO_LENDIAN_INT64(BTRFS_RESERVED_SPACE_OFFSET + blockSize);
chunk_root = B_HOST_TO_LENDIAN_INT64(Root() + blockSize);
total_size = B_HOST_TO_LENDIAN_INT64(numBlocks * blockSize);
used_size = B_HOST_TO_LENDIAN_INT64(6 * blockSize);
sector_size = B_HOST_TO_LENDIAN_INT32(sectorSize);
leaf_size = B_HOST_TO_LENDIAN_INT32(blockSize);
node_size = B_HOST_TO_LENDIAN_INT32(blockSize);
stripe_size = B_HOST_TO_LENDIAN_INT32(blockSize);
checksum_type = B_HOST_TO_LENDIAN_INT32(BTRFS_CSUM_TYPE_CRC32);
chunk_root_generation = B_HOST_TO_LENDIAN_INT64(1);
// TODO(lesderid): Support configurable filesystem features
incompat_flags = B_HOST_TO_LENDIAN_INT64(0);
strlcpy(label, name, BTRFS_LABEL_SIZE);
}
// #pragma mark - // #pragma mark -
@@ -453,6 +484,78 @@ Volume::Mount(const char* deviceName, uint32 flags)
} }
status_t
Volume::Initialize(int fd, const char* label, uint32 blockSize,
uint32 sectorSize)
{
TRACE("Volume::Initialize()\n");
// label must != NULL and may not contain '/' or '\\'
if (label == NULL
|| strchr(label, '/') != NULL || strchr(label, '\\') != NULL) {
return B_BAD_VALUE;
}
if ((blockSize != 1024 && blockSize != 2048 && blockSize != 4096
&& blockSize != 8192 && blockSize != 16384)
|| blockSize % sectorSize != 0) {
return B_BAD_VALUE;
}
DeviceOpener opener(fd, O_RDWR);
if (opener.Device() < B_OK)
return B_BAD_VALUE;
if (opener.IsReadOnly())
return B_READ_ONLY_DEVICE;
fDevice = opener.Device();
uint32 deviceBlockSize;
off_t deviceSize;
if (opener.GetSize(&deviceSize, &deviceBlockSize) < B_OK)
return B_ERROR;
off_t numBlocks = deviceSize / sectorSize;
// create valid superblock
fSuperBlock.Initialize(label, numBlocks, blockSize, sectorSize);
fBlockSize = fSuperBlock.BlockSize();
fSectorSize = fSuperBlock.SectorSize();
// TODO(lesderid): Initialize remaining core structures
// (extent tree, chunk tree, fs tree, etc.)
status_t status = WriteSuperBlock();
if (status < B_OK)
return status;
fBlockCache = opener.InitCache(fSuperBlock.TotalSize() / fBlockSize,
fBlockSize);
if (fBlockCache == NULL)
return B_ERROR;
fJournal = new(std::nothrow) Journal(this);
if (fJournal == NULL)
RETURN_ERROR(B_ERROR);
// TODO(lesderid): Perform secondary initialization (in transactions)
// (add block groups to extent tree, create root dir, etc.)
Transaction transaction(this);
// TODO(lesderid): Write all superblocks when transactions are committed
status = transaction.Done();
if (status < B_OK)
return status;
opener.RemoveCache(true);
TRACE("Volume::Initialize(): Done\n");
return B_OK;
}
status_t status_t
Volume::Unmount() Volume::Unmount()
{ {
@@ -546,6 +649,20 @@ Volume::FindBlock(off_t logical, off_t& physical)
} }
status_t
Volume::WriteSuperBlock()
{
// TODO(lesderid): Calculate checksum
if (write_pos(fDevice, BTRFS_SUPER_BLOCK_OFFSET, &fSuperBlock,
sizeof(btrfs_super_block))
!= sizeof(btrfs_super_block))
return B_IO_ERROR;
return B_OK;
}
/* Wrapper function for allocating new block /* Wrapper function for allocating new block
*/ */
status_t status_t
@@ -30,6 +30,8 @@ public:
status_t Mount(const char* device, uint32 flags); status_t Mount(const char* device, uint32 flags);
status_t Unmount(); status_t Unmount();
status_t Initialize(int fd, const char* label,
uint32 blockSize, uint32 sectorSize);
bool IsValidSuperBlock(); bool IsValidSuperBlock();
bool IsReadOnly() const bool IsReadOnly() const
@@ -58,6 +60,7 @@ public:
btrfs_super_block& SuperBlock() { return fSuperBlock; } btrfs_super_block& SuperBlock() { return fSuperBlock; }
status_t LoadSuperBlock(); status_t LoadSuperBlock();
status_t WriteSuperBlock();
// cache access // cache access
void* BlockCache() { return fBlockCache; } void* BlockCache() { return fBlockCache; }
+20 -11
View File
@@ -13,7 +13,11 @@
typedef uint64 fileblock_t; // file block number typedef uint64 fileblock_t; // file block number
typedef uint64 fsblock_t; // filesystem block number typedef uint64 fsblock_t; // filesystem block number
#define BTRFS_SUPER_BLOCK_OFFSET 0x10000 #define BTRFS_LABEL_SIZE 256
#define BTRFS_SUPER_BLOCK_OFFSET 0x10000 // 64KiB
#define BTRFS_RESERVED_SPACE_OFFSET 0x100000 // 1MiB
#define BTRFS_NUM_ROOT_BACKUPS 4 #define BTRFS_NUM_ROOT_BACKUPS 4
@@ -93,10 +97,10 @@ struct btrfs_timespec {
struct btrfs_header { struct btrfs_header {
uint8 checksum[32]; uint8 checksum[32];
uint8 fsid[16]; uuid_t fsid;
uint64 logical_address; uint64 logical_address;
uint64 flags; uint64 flags;
uint8 chunk_tree_uuid[16]; uuid_t chunk_tree_uuid;
uint64 generation; uint64 generation;
uint64 owner; uint64 owner;
uint32 item_count; uint32 item_count;
@@ -162,7 +166,7 @@ struct btrfs_stream {
struct btrfs_stripe { struct btrfs_stripe {
uint64 device_id; uint64 device_id;
uint64 offset; uint64 offset;
uint8 device_uuid[16]; uuid_t device_uuid;
uint64 DeviceID() const { return B_LENDIAN_TO_HOST_INT64(device_id); } uint64 DeviceID() const { return B_LENDIAN_TO_HOST_INT64(device_id); }
uint64 Offset() const { return B_LENDIAN_TO_HOST_INT64(offset); } uint64 Offset() const { return B_LENDIAN_TO_HOST_INT64(offset); }
} _PACKED; } _PACKED;
@@ -208,14 +212,14 @@ struct btrfs_device {
uint32 group; uint32 group;
uint8 seek_speed; uint8 seek_speed;
uint8 bandwidth; uint8 bandwidth;
uint8 uuid[16]; uuid_t uuid;
uint8 fsid[16]; uuid_t fsid;
} _PACKED; } _PACKED;
struct btrfs_super_block { struct btrfs_super_block {
uint8 checksum[32]; uint8 checksum[32];
uint8 fsid[16]; uuid_t fsid;
uint64 blocknum; uint64 blocknum;
uint64 flags; uint64 flags;
char magic[8]; char magic[8];
@@ -242,13 +246,15 @@ struct btrfs_super_block {
uint8 chunk_root_level; uint8 chunk_root_level;
uint8 log_root_level; uint8 log_root_level;
btrfs_device device; btrfs_device device;
char label[256]; char label[BTRFS_LABEL_SIZE];
uint64 reserved[32]; uint64 reserved[32];
uint8 system_chunk_array[2048]; uint8 system_chunk_array[2048];
btrfs_backup_roots backup_roots[BTRFS_NUM_ROOT_BACKUPS]; btrfs_backup_roots backup_roots[BTRFS_NUM_ROOT_BACKUPS];
bool IsValid(); // implemented in Volume.cpp:
// implemented in Volume.cpp bool IsValid() const;
void Initialize(const char* name, off_t numBlocks,
uint32 blockSize, uint32 sectorSize);
uint64 TotalSize() const { return B_LENDIAN_TO_HOST_INT64(total_size); } uint64 TotalSize() const { return B_LENDIAN_TO_HOST_INT64(total_size); }
uint32 BlockSize() const { return B_LENDIAN_TO_HOST_INT32(node_size); } uint32 BlockSize() const { return B_LENDIAN_TO_HOST_INT32(node_size); }
uint32 SectorSize() const { return B_LENDIAN_TO_HOST_INT32(sector_size); } uint32 SectorSize() const { return B_LENDIAN_TO_HOST_INT32(sector_size); }
@@ -456,10 +462,13 @@ struct btrfs_extent_data_ref {
uint32 RefCount() const { return B_LENDIAN_TO_HOST_INT32(ref_count); } uint32 RefCount() const { return B_LENDIAN_TO_HOST_INT32(ref_count); }
} _PACKED; } _PACKED;
#define BTRFS_SUPER_BLOCK_MAGIC "_BHRfS_M" #define BTRFS_SUPER_BLOCK_MAGIC "_BHRfS_M"
#define BTRFS_SUPER_BLOCK_MAGIC_TEMPORARY "!BHRfS_M"
#define BTRFS_FIRST_SUBVOLUME 256 #define BTRFS_FIRST_SUBVOLUME 256
#define BTRFS_CSUM_TYPE_CRC32 0
#define BTRFS_OBJECT_ID_ROOT_TREE 1 #define BTRFS_OBJECT_ID_ROOT_TREE 1
#define BTRFS_OBJECT_ID_EXTENT_TREE 2 #define BTRFS_OBJECT_ID_EXTENT_TREE 2
#define BTRFS_OBJECT_ID_CHUNK_TREE 3 #define BTRFS_OBJECT_ID_CHUNK_TREE 3
@@ -0,0 +1,67 @@
/*
* Copyright 2007-2008, Ingo Weinhold, [email protected].
* Copyright 2019, Les De Ridder, [email protected]
*
* Distributed under the terms of the MIT License.
*/
#include "btrfs_disk_system.h"
#include "btrfs.h"
#include "Volume.h"
status_t
parse_initialize_parameters(const char* parameterString,
initialize_parameters& parameters)
{
parameters.verbose = false;
void *handle = parse_driver_settings_string(parameterString);
if (handle == NULL)
return B_ERROR;
if (get_driver_boolean_parameter(handle, "verbose", false, true))
parameters.verbose = true;
const char *ss_string = get_driver_parameter(handle, "sector_size",
NULL, NULL);
uint32 sectorSize = B_PAGE_SIZE;
if (ss_string != NULL)
sectorSize = strtoul(ss_string, NULL, 0);
const char *bs_string = get_driver_parameter(handle, "block_size",
NULL, NULL);
uint32 blockSize = max_c(16384, B_PAGE_SIZE);
if (bs_string != NULL)
blockSize = strtoul(bs_string, NULL, 0);
// TODO(lesderid): accept more settings (allocation profiles, uuid, etc.)
delete_driver_settings(handle);
if ((blockSize != 1024 && blockSize != 2048 && blockSize != 4096
&& blockSize != 8192 && blockSize != 16384)
|| blockSize % sectorSize != 0) {
return B_BAD_VALUE;
}
parameters.sectorSize = sectorSize;
parameters.blockSize = blockSize;
return B_OK;
}
status_t
check_volume_name(const char* name)
{
if (name == NULL || strlen(name) >= BTRFS_LABEL_SIZE
|| strchr(name, '/') != NULL
|| strchr(name, '\\') != NULL) {
return B_BAD_VALUE;
}
return B_OK;
}
@@ -0,0 +1,25 @@
/*
* Copyright 2007, Ingo Weinhold, [email protected].
* Copyright 2019, Les De Ridder, [email protected]
*
* Distributed under the terms of the MIT License.
*/
#ifndef _BTRFS_DISK_SYSTEM_H
#define _BTRFS_DISK_SYSTEM_H
#include "system_dependencies.h"
struct initialize_parameters {
uint32 blockSize;
uint32 sectorSize;
bool verbose;
};
status_t parse_initialize_parameters(const char* parameterString,
initialize_parameters& parameters);
status_t check_volume_name(const char* name);
#endif // _BTRFS_DISK_SYSTEM_H
@@ -1,7 +1,9 @@
/* /*
* Copyright 2019, Les De Ridder, [email protected]
* Copyright 2017, Chế Vũ Gia Hy, [email protected]. * Copyright 2017, Chế Vũ Gia Hy, [email protected].
* Copyright 2011, Jérôme Duval, [email protected]. * Copyright 2011, Jérôme Duval, [email protected].
* Copyright 2008, Axel Dörfler, [email protected]. * Copyright 2008, Axel Dörfler, [email protected].
*
* This file may be used under the terms of the MIT License. * This file may be used under the terms of the MIT License.
*/ */
@@ -9,6 +11,8 @@
#include "Attribute.h" #include "Attribute.h"
#include "AttributeIterator.h" #include "AttributeIterator.h"
#include "btrfs.h" #include "btrfs.h"
#include "btrfs_disk_system.h"
#include "DebugSupport.h"
#include "DirectoryIterator.h" #include "DirectoryIterator.h"
#include "Inode.h" #include "Inode.h"
#include "Utility.h" #include "Utility.h"
@@ -20,8 +24,6 @@
#else #else
# define TRACE(x...) ; # define TRACE(x...) ;
#endif #endif
#define ERROR(x...) dprintf("\33[34mbtrfs:\33[0m " x)
#define BTRFS_IO_SIZE 65536 #define BTRFS_IO_SIZE 65536
@@ -833,6 +835,85 @@ btrfs_remove_attr(fs_volume* _volume, fs_vnode* vnode,
return EROFS; return EROFS;
} }
static uint32
btrfs_get_supported_operations(partition_data* partition, uint32 mask)
{
// TODO: We should at least check the partition size.
return B_DISK_SYSTEM_SUPPORTS_INITIALIZING
| B_DISK_SYSTEM_SUPPORTS_CONTENT_NAME
// | B_DISK_SYSTEM_SUPPORTS_WRITING
;
}
static status_t
btrfs_initialize(int fd, partition_id partitionID, const char* name,
const char* parameterString, off_t partitionSize, disk_job_id job)
{
// check name
status_t status = check_volume_name(name);
if (status != B_OK)
return status;
// parse parameters
initialize_parameters parameters;
status = parse_initialize_parameters(parameterString, parameters);
if (status != B_OK)
return status;
update_disk_device_job_progress(job, 0);
// initialize the volume
Volume volume(NULL);
status = volume.Initialize(fd, name, parameters.blockSize,
parameters.sectorSize);
if (status < B_OK) {
INFORM("Initializing volume failed: %s\n", strerror(status));
return status;
}
// rescan partition
status = scan_partition(partitionID);
if (status != B_OK)
return status;
update_disk_device_job_progress(job, 1);
// print some info, if desired
if (parameters.verbose) {
btrfs_super_block super = volume.SuperBlock();
INFORM(("Disk was initialized successfully.\n"));
INFORM("\tlabel: \"%s\"\n", super.label);
INFORM("\tblock size: %u bytes\n", (unsigned)super.BlockSize());
INFORM("\tsector size: %u bytes\n", (unsigned)super.SectorSize());
}
return B_OK;
}
static status_t
btrfs_uninitialize(int fd, partition_id partitionID, off_t partitionSize,
uint32 blockSize, disk_job_id job)
{
if (blockSize == 0)
return B_BAD_VALUE;
update_disk_device_job_progress(job, 0.0);
// just overwrite the superblock
btrfs_super_block superBlock;
memset(&superBlock, 0, sizeof(superBlock));
if (write_pos(fd, BTRFS_SUPER_BLOCK_OFFSET, &superBlock,
sizeof(superBlock)) < 0)
return errno;
update_disk_device_job_progress(job, 1.0);
return B_OK;
}
// #pragma mark - // #pragma mark -
@@ -842,8 +923,12 @@ btrfs_std_ops(int32 op, ...)
{ {
switch (op) { switch (op) {
case B_MODULE_INIT: case B_MODULE_INIT:
init_debugging();
return B_OK; return B_OK;
case B_MODULE_UNINIT: case B_MODULE_UNINIT:
exit_debugging();
return B_OK; return B_OK;
default: default:
@@ -943,7 +1028,13 @@ static file_system_module_info sBtrfsFileSystem = {
"btrfs", // short_name "btrfs", // short_name
"Btrfs File System", // pretty_name "Btrfs File System", // pretty_name
0, // DDM flags
// DDM flags
0
| B_DISK_SYSTEM_SUPPORTS_INITIALIZING
| B_DISK_SYSTEM_SUPPORTS_CONTENT_NAME
// | B_DISK_SYSTEM_SUPPORTS_WRITING
,
// scanning // scanning
btrfs_identify_partition, btrfs_identify_partition,
@@ -955,7 +1046,7 @@ static file_system_module_info sBtrfsFileSystem = {
/* capability querying operations */ /* capability querying operations */
NULL, &btrfs_get_supported_operations,
NULL, // validate_resize NULL, // validate_resize
NULL, // validate_move NULL, // validate_move
@@ -973,8 +1064,8 @@ static file_system_module_info sBtrfsFileSystem = {
NULL, // move NULL, // move
NULL, // set_content_name NULL, // set_content_name
NULL, // set_content_parameters NULL, // set_content_parameters
NULL, // initialize btrfs_initialize,
NULL // unitialize btrfs_uninitialize
}; };
@@ -27,6 +27,7 @@
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/types.h> #include <sys/types.h>
#include <ByteOrder.h> #include <ByteOrder.h>
#include <driver_settings.h>
#include <fs_cache.h> #include <fs_cache.h>
#include <fs_interface.h> #include <fs_interface.h>
#include <fs_info.h> #include <fs_info.h>
@@ -43,6 +44,7 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <unistd.h> #include <unistd.h>
#include <uuid.h>
#include <zlib.h> #include <zlib.h>
#endif // !FS_SHELL #endif // !FS_SHELL