From 6f4c36e2979b9684a65e6d73b872d4628f3e452a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Duval?= Date: Mon, 24 Jan 2011 22:10:43 +0000 Subject: [PATCH] * Implemented a read-only btrfs file system, tested with a 400MB image. * Inline extent data isn't read with the file_cache yet as the data is not block aligned. git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@40282 a95241bf-73f2-0310-859d-f6bbb57e9c96 --- src/add-ons/kernel/file_systems/Jamfile | 1 + .../kernel/file_systems/btrfs/BPlusTree.cpp | 282 ++++++++ .../kernel/file_systems/btrfs/BPlusTree.h | 135 ++++ .../kernel/file_systems/btrfs/CRCTable.cpp | 69 ++ .../kernel/file_systems/btrfs/CRCTable.h | 10 + .../kernel/file_systems/btrfs/CachedBlock.h | 97 +++ .../kernel/file_systems/btrfs/Chunk.cpp | 77 ++ src/add-ons/kernel/file_systems/btrfs/Chunk.h | 31 + .../file_systems/btrfs/DirectoryIterator.cpp | 155 ++++ .../file_systems/btrfs/DirectoryIterator.h | 30 + .../kernel/file_systems/btrfs/Inode.cpp | 253 +++++++ src/add-ons/kernel/file_systems/btrfs/Inode.h | 152 ++++ src/add-ons/kernel/file_systems/btrfs/Jamfile | 15 + .../kernel/file_systems/btrfs/Utility.h | 41 ++ .../kernel/file_systems/btrfs/Volume.cpp | 535 ++++++++++++++ .../kernel/file_systems/btrfs/Volume.h | 82 +++ src/add-ons/kernel/file_systems/btrfs/btrfs.h | 328 +++++++++ .../kernel/file_systems/btrfs/crc_table.cpp | 68 ++ .../file_systems/btrfs/kernel_interface.cpp | 674 ++++++++++++++++++ 19 files changed, 3035 insertions(+) create mode 100644 src/add-ons/kernel/file_systems/btrfs/BPlusTree.cpp create mode 100644 src/add-ons/kernel/file_systems/btrfs/BPlusTree.h create mode 100644 src/add-ons/kernel/file_systems/btrfs/CRCTable.cpp create mode 100644 src/add-ons/kernel/file_systems/btrfs/CRCTable.h create mode 100644 src/add-ons/kernel/file_systems/btrfs/CachedBlock.h create mode 100644 src/add-ons/kernel/file_systems/btrfs/Chunk.cpp create mode 100644 src/add-ons/kernel/file_systems/btrfs/Chunk.h create mode 100644 src/add-ons/kernel/file_systems/btrfs/DirectoryIterator.cpp create mode 100644 src/add-ons/kernel/file_systems/btrfs/DirectoryIterator.h create mode 100644 src/add-ons/kernel/file_systems/btrfs/Inode.cpp create mode 100644 src/add-ons/kernel/file_systems/btrfs/Inode.h create mode 100644 src/add-ons/kernel/file_systems/btrfs/Jamfile create mode 100644 src/add-ons/kernel/file_systems/btrfs/Utility.h create mode 100644 src/add-ons/kernel/file_systems/btrfs/Volume.cpp create mode 100644 src/add-ons/kernel/file_systems/btrfs/Volume.h create mode 100644 src/add-ons/kernel/file_systems/btrfs/btrfs.h create mode 100644 src/add-ons/kernel/file_systems/btrfs/crc_table.cpp create mode 100644 src/add-ons/kernel/file_systems/btrfs/kernel_interface.cpp diff --git a/src/add-ons/kernel/file_systems/Jamfile b/src/add-ons/kernel/file_systems/Jamfile index e592797738..1ffc8823be 100644 --- a/src/add-ons/kernel/file_systems/Jamfile +++ b/src/add-ons/kernel/file_systems/Jamfile @@ -2,6 +2,7 @@ SubDir HAIKU_TOP src add-ons kernel file_systems ; SubInclude HAIKU_TOP src add-ons kernel file_systems bfs ; SubInclude HAIKU_TOP src add-ons kernel file_systems bindfs ; +SubInclude HAIKU_TOP src add-ons kernel file_systems btrfs ; SubInclude HAIKU_TOP src add-ons kernel file_systems cdda ; SubInclude HAIKU_TOP src add-ons kernel file_systems ext2 ; SubInclude HAIKU_TOP src add-ons kernel file_systems fat ; diff --git a/src/add-ons/kernel/file_systems/btrfs/BPlusTree.cpp b/src/add-ons/kernel/file_systems/btrfs/BPlusTree.cpp new file mode 100644 index 0000000000..04609624af --- /dev/null +++ b/src/add-ons/kernel/file_systems/btrfs/BPlusTree.cpp @@ -0,0 +1,282 @@ +/* + * Copyright 2011, Jérôme Duval, korli@users.berlios.de. + * Copyright 2001-2010, Axel Dörfler, axeld@pinc-software.de. + * This file may be used under the terms of the MIT License. + */ + + +//! B+Tree implementation + + +#include "BPlusTree.h" + +#include "CachedBlock.h" + +#include +#include +#include + + +//#define TRACE_BTRFS +#ifdef TRACE_BTRFS +# define TRACE(x...) dprintf("\33[34mbtrfs:\33[0m " x) +#else +# define TRACE(x...) ; +#endif +# define ERROR(x...) dprintf("\33[34mbtrfs:\33[0m " x) + + +BPlusTree::BPlusTree(Volume* volume, struct btrfs_stream *stream) + : + fStream(stream), + fRootBlock(0), + fVolume(volume) +{ + mutex_init(&fIteratorLock, "btrfs b+tree iterator"); +} + + +BPlusTree::BPlusTree(Volume* volume, fsblock_t rootBlock) + : + fStream(NULL), + fRootBlock(rootBlock), + fVolume(volume) +{ + mutex_init(&fIteratorLock, "btrfs b+tree iterator"); +} + + +BPlusTree::~BPlusTree() +{ + // if there are any TreeIterators left, we need to stop them + // (can happen when the tree's inode gets deleted while + // traversing the tree - a TreeIterator doesn't lock the inode) + mutex_lock(&fIteratorLock); + + SinglyLinkedList::Iterator iterator + = fIterators.GetIterator(); + while (iterator.HasNext()) + iterator.Next()->Stop(); + mutex_destroy(&fIteratorLock); +} + + +int32 +BPlusTree::_CompareKeys(struct btrfs_key &key1, struct btrfs_key &key2) +{ + if (key1.ObjectID() > key2.ObjectID()) + return 1; + if (key1.ObjectID() < key2.ObjectID()) + return -1; + if (key1.Type() > key2.Type()) + return 1; + if (key1.Type() < key2.Type()) + return -1; + if (key1.Offset() > key2.Offset()) + return 1; + if (key1.Offset() < key2.Offset()) + return -1; + return 0; +} + + +/*! Searches the key in the tree, and stores the allocated found item in + _value, if successful. + Returns B_OK when the key could be found, B_ENTRY_NOT_FOUND if not. + It can also return other errors to indicate that something went wrong. +*/ +status_t +BPlusTree::_Find(struct btrfs_key &key, void** _value, size_t* _size, + bplustree_traversing type) +{ + TRACE("Find() objectid %lld type %d offset %lld \n", key.ObjectID(), + key.Type(), key.Offset()); + btrfs_stream *stream = fStream; + CachedBlock cached(fVolume); + fsblock_t physical; + if (stream == NULL) { + if (fVolume->FindBlock(fRootBlock, physical) != B_OK) { + ERROR("Find() unmapped block %lld\n", fRootBlock); + return B_ERROR; + } + stream = (btrfs_stream *)cached.SetTo(physical); + } + + while (stream->header.Level() != 0) { + TRACE("Find() level %d\n", stream->header.Level()); + uint32 i = 1; + for (; i < stream->header.ItemCount(); i++) { + int32 comp = _CompareKeys(stream->index[i].key, key); + TRACE("Find() found index %ld at %lld comp %ld\n", i, + stream->index[i].BlockNum(), comp); + if (comp < 0) + continue; + if (comp > 0 || type == BPLUSTREE_BACKWARD) + break; + } + TRACE("Find() getting index %ld at %lld\n", i - 1, + stream->index[i - 1].BlockNum()); + + if (fVolume->FindBlock(stream->index[i - 1].BlockNum(), physical) + != B_OK) { + ERROR("Find() unmapped block %lld\n", + stream->index[i - 1].BlockNum()); + return B_ERROR; + } + stream = (btrfs_stream *)cached.SetTo(physical); + } + + uint32 i; +#ifdef TRACE_BTRFS + TRACE("Find() dump count %ld\n", stream->header.ItemCount()); + for (i = 0; i < stream->header.ItemCount(); i++) { + int32 comp = _CompareKeys(key, stream->entries[i].key); + TRACE("Find() dump %ld %ld offset %lld comp %ld\n", + stream->entries[i].Offset(), + stream->entries[i].Size(), stream->entries[i].key.Offset(), comp); + } +#endif + + for (i = 0; i < stream->header.ItemCount(); i++) { + int32 comp = _CompareKeys(key, stream->entries[i].key); + TRACE("Find() found %ld %ld oid %lld type %d offset %lld comp %ld\n", + stream->entries[i].Offset(), stream->entries[i].Size(), + stream->entries[i].key.ObjectID(), stream->entries[i].key.Type(), + stream->entries[i].key.Offset(), comp); + if (comp == 0) + break; + if (comp < 0 && i > 0) { + if (type == BPLUSTREE_EXACT) + return B_ENTRY_NOT_FOUND; + if (type == BPLUSTREE_BACKWARD) + i--; + break; + } + } + + if (i == stream->header.ItemCount()) { + if (type == BPLUSTREE_BACKWARD) + i--; + else + return B_ENTRY_NOT_FOUND; + } + + if (i < stream->header.ItemCount() + && stream->entries[i].key.Type() == key.Type()) { + TRACE("Find() found %ld %ld\n", stream->entries[i].Offset(), + stream->entries[i].Size()); + if (_value != NULL) { + *_value = malloc(stream->entries[i].Size()); + memcpy(*_value, ((uint8 *)&stream->entries[0] + + stream->entries[i].Offset()), + stream->entries[i].Size()); + key.SetOffset(stream->entries[i].key.Offset()); + if (_size != NULL) + *_size = stream->entries[i].Size(); + } + return B_OK; + } + + + TRACE("Find() not found %lld %lld\n", key.Offset(), key.ObjectID()); + + return B_ENTRY_NOT_FOUND; +} + + +status_t +BPlusTree::FindNext(struct btrfs_key &key, void** _value, size_t* _size) +{ + return _Find(key, _value, _size, BPLUSTREE_FORWARD); +} + + +status_t +BPlusTree::FindPrevious(struct btrfs_key &key, void** _value, size_t* _size) +{ + return _Find(key, _value, _size, BPLUSTREE_BACKWARD); +} + + +status_t +BPlusTree::FindExact(struct btrfs_key &key, void** _value, size_t* _size) +{ + return _Find(key, _value, _size, BPLUSTREE_EXACT); +} + + +void +BPlusTree::_AddIterator(TreeIterator* iterator) +{ + MutexLocker _(fIteratorLock); + fIterators.Add(iterator); +} + + +void +BPlusTree::_RemoveIterator(TreeIterator* iterator) +{ + MutexLocker _(fIteratorLock); + fIterators.Remove(iterator); +} + + +// #pragma mark - + + +TreeIterator::TreeIterator(BPlusTree* tree, struct btrfs_key &key) + : + fTree(tree), + fCurrentKey(key) +{ + Rewind(); + tree->_AddIterator(this); +} + + +TreeIterator::~TreeIterator() +{ + if (fTree) + fTree->_RemoveIterator(this); +} + + +/*! Iterates through the tree in the specified direction. +*/ +status_t +TreeIterator::Traverse(bplustree_traversing direction, struct btrfs_key &key, + void** value, size_t* size) +{ + if (fTree == NULL) + return B_INTERRUPTED; + + fCurrentKey.SetOffset(fCurrentKey.Offset() + direction); + status_t status = fTree->_Find(fCurrentKey, value, size, + direction); + if (status != B_OK) { + TRACE("TreeIterator::Traverse() Find failed\n"); + return B_ENTRY_NOT_FOUND; + } + + return B_OK; +} + + +/*! just sets the current key in the iterator. +*/ +status_t +TreeIterator::Find(struct btrfs_key &key) +{ + if (fTree == NULL) + return B_INTERRUPTED; + fCurrentKey = key; + return B_OK; +} + + +void +TreeIterator::Stop() +{ + fTree = NULL; +} + diff --git a/src/add-ons/kernel/file_systems/btrfs/BPlusTree.h b/src/add-ons/kernel/file_systems/btrfs/BPlusTree.h new file mode 100644 index 0000000000..fb051a0357 --- /dev/null +++ b/src/add-ons/kernel/file_systems/btrfs/BPlusTree.h @@ -0,0 +1,135 @@ +/* + * Copyright 2011, Jérôme Duval, korli@users.berlios.de. + * Copyright 2001-2010, Axel Dörfler, axeld@pinc-software.de. + * This file may be used under the terms of the MIT License. + */ +#ifndef B_PLUS_TREE_H +#define B_PLUS_TREE_H + + +#include "btrfs.h" + +#include + +#include "Volume.h" + + +#define BPLUSTREE_NULL -1LL +#define BPLUSTREE_FREE -2LL + + +enum bplustree_traversing { + BPLUSTREE_FORWARD = 1, + BPLUSTREE_EXACT = 0, + BPLUSTREE_BACKWARD = -1, + + BPLUSTREE_BEGIN = 0, + BPLUSTREE_END = -1 +}; + + +// #pragma mark - in-memory structures + +template class Stack; +class TreeIterator; + + +// needed for searching (utilizing a stack) +struct node_and_key { + off_t nodeOffset; + uint16 keyIndex; +}; + + +class BPlusTree { +public: + BPlusTree(Volume* volume, + struct btrfs_stream *stream); + BPlusTree(Volume* volume, + fsblock_t rootBlock); + ~BPlusTree(); + status_t FindExact(struct btrfs_key &key, void** value, + size_t* size = NULL); + status_t FindNext(struct btrfs_key &key, void** value, + size_t* size = NULL); + status_t FindPrevious(struct btrfs_key &key, void** value, + size_t* size = NULL); + +private: + BPlusTree(const BPlusTree& other); + BPlusTree& operator=(const BPlusTree& other); + // no implementation + + int32 _CompareKeys(struct btrfs_key &key1, + struct btrfs_key &key2); + status_t _Find(struct btrfs_key &key, void** value, + size_t* size, bplustree_traversing type); + void _AddIterator(TreeIterator* iterator); + void _RemoveIterator(TreeIterator* iterator); +private: + friend class TreeIterator; + + struct btrfs_stream* fStream; + fsblock_t fRootBlock; + Volume* fVolume; + mutex fIteratorLock; + SinglyLinkedList fIterators; +}; + + +class TreeIterator : public SinglyLinkedListLinkImpl { +public: + TreeIterator(BPlusTree* tree, struct btrfs_key &key); + ~TreeIterator(); + + status_t Traverse(bplustree_traversing direction, + struct btrfs_key &key, void** value, + size_t *size = NULL); + status_t Find(struct btrfs_key &key); + + status_t Rewind(); + status_t GetNextEntry(struct btrfs_key &key, void** value, + size_t *size = NULL); + status_t GetPreviousEntry(struct btrfs_key &key, void** value, + size_t *size = NULL); + + BPlusTree* Tree() const { return fTree; } + +private: + friend class BPlusTree; + + // called by BPlusTree + void Stop(); + +private: + BPlusTree* fTree; + struct btrfs_key fCurrentKey; +}; + + +// #pragma mark - TreeIterator inline functions + + +inline status_t +TreeIterator::Rewind() +{ + fCurrentKey.SetOffset(BPLUSTREE_BEGIN); + return B_OK; +} + +inline status_t +TreeIterator::GetNextEntry(struct btrfs_key &key, void** value, size_t *size) +{ + return Traverse(BPLUSTREE_FORWARD, key, value, size); +} + +inline status_t +TreeIterator::GetPreviousEntry(struct btrfs_key &key, void** value, + size_t *size) +{ + return Traverse(BPLUSTREE_BACKWARD, key, value, size); +} + + + +#endif // B_PLUS_TREE_H diff --git a/src/add-ons/kernel/file_systems/btrfs/CRCTable.cpp b/src/add-ons/kernel/file_systems/btrfs/CRCTable.cpp new file mode 100644 index 0000000000..75f35e51ce --- /dev/null +++ b/src/add-ons/kernel/file_systems/btrfs/CRCTable.cpp @@ -0,0 +1,69 @@ +/* + * Copyright 2011, Haiku Inc. All rights reserved. + * This file may be used under the terms of the MIT License. + * + * Authors: + * Jérôme Duval + */ + + +#include + + +//! CRC 03667067501 table, as generated by crc_table.cpp +static uint32 kCrcTable[256] = { + 0x00000000, 0xf26b8303, 0xe13b70f7, 0x1350f3f4, 0xc79a971f, 0x35f1141c, 0x26a1e7e8, 0xd4ca64eb, + 0x8ad958cf, 0x78b2dbcc, 0x6be22838, 0x9989ab3b, 0x4d43cfd0, 0xbf284cd3, 0xac78bf27, 0x5e133c24, + 0x105ec76f, 0xe235446c, 0xf165b798, 0x030e349b, 0xd7c45070, 0x25afd373, 0x36ff2087, 0xc494a384, + 0x9a879fa0, 0x68ec1ca3, 0x7bbcef57, 0x89d76c54, 0x5d1d08bf, 0xaf768bbc, 0xbc267848, 0x4e4dfb4b, + 0x20bd8ede, 0xd2d60ddd, 0xc186fe29, 0x33ed7d2a, 0xe72719c1, 0x154c9ac2, 0x061c6936, 0xf477ea35, + 0xaa64d611, 0x580f5512, 0x4b5fa6e6, 0xb93425e5, 0x6dfe410e, 0x9f95c20d, 0x8cc531f9, 0x7eaeb2fa, + 0x30e349b1, 0xc288cab2, 0xd1d83946, 0x23b3ba45, 0xf779deae, 0x05125dad, 0x1642ae59, 0xe4292d5a, + 0xba3a117e, 0x4851927d, 0x5b016189, 0xa96ae28a, 0x7da08661, 0x8fcb0562, 0x9c9bf696, 0x6ef07595, + 0x417b1dbc, 0xb3109ebf, 0xa0406d4b, 0x522bee48, 0x86e18aa3, 0x748a09a0, 0x67dafa54, 0x95b17957, + 0xcba24573, 0x39c9c670, 0x2a993584, 0xd8f2b687, 0x0c38d26c, 0xfe53516f, 0xed03a29b, 0x1f682198, + 0x5125dad3, 0xa34e59d0, 0xb01eaa24, 0x42752927, 0x96bf4dcc, 0x64d4cecf, 0x77843d3b, 0x85efbe38, + 0xdbfc821c, 0x2997011f, 0x3ac7f2eb, 0xc8ac71e8, 0x1c661503, 0xee0d9600, 0xfd5d65f4, 0x0f36e6f7, + 0x61c69362, 0x93ad1061, 0x80fde395, 0x72966096, 0xa65c047d, 0x5437877e, 0x4767748a, 0xb50cf789, + 0xeb1fcbad, 0x197448ae, 0x0a24bb5a, 0xf84f3859, 0x2c855cb2, 0xdeeedfb1, 0xcdbe2c45, 0x3fd5af46, + 0x7198540d, 0x83f3d70e, 0x90a324fa, 0x62c8a7f9, 0xb602c312, 0x44694011, 0x5739b3e5, 0xa55230e6, + 0xfb410cc2, 0x092a8fc1, 0x1a7a7c35, 0xe811ff36, 0x3cdb9bdd, 0xceb018de, 0xdde0eb2a, 0x2f8b6829, + 0x82f63b78, 0x709db87b, 0x63cd4b8f, 0x91a6c88c, 0x456cac67, 0xb7072f64, 0xa457dc90, 0x563c5f93, + 0x082f63b7, 0xfa44e0b4, 0xe9141340, 0x1b7f9043, 0xcfb5f4a8, 0x3dde77ab, 0x2e8e845f, 0xdce5075c, + 0x92a8fc17, 0x60c37f14, 0x73938ce0, 0x81f80fe3, 0x55326b08, 0xa759e80b, 0xb4091bff, 0x466298fc, + 0x1871a4d8, 0xea1a27db, 0xf94ad42f, 0x0b21572c, 0xdfeb33c7, 0x2d80b0c4, 0x3ed04330, 0xccbbc033, + 0xa24bb5a6, 0x502036a5, 0x4370c551, 0xb11b4652, 0x65d122b9, 0x97baa1ba, 0x84ea524e, 0x7681d14d, + 0x2892ed69, 0xdaf96e6a, 0xc9a99d9e, 0x3bc21e9d, 0xef087a76, 0x1d63f975, 0x0e330a81, 0xfc588982, + 0xb21572c9, 0x407ef1ca, 0x532e023e, 0xa145813d, 0x758fe5d6, 0x87e466d5, 0x94b49521, 0x66df1622, + 0x38cc2a06, 0xcaa7a905, 0xd9f75af1, 0x2b9cd9f2, 0xff56bd19, 0x0d3d3e1a, 0x1e6dcdee, 0xec064eed, + 0xc38d26c4, 0x31e6a5c7, 0x22b65633, 0xd0ddd530, 0x0417b1db, 0xf67c32d8, 0xe52cc12c, 0x1747422f, + 0x49547e0b, 0xbb3ffd08, 0xa86f0efc, 0x5a048dff, 0x8ecee914, 0x7ca56a17, 0x6ff599e3, 0x9d9e1ae0, + 0xd3d3e1ab, 0x21b862a8, 0x32e8915c, 0xc083125f, 0x144976b4, 0xe622f5b7, 0xf5720643, 0x07198540, + 0x590ab964, 0xab613a67, 0xb831c993, 0x4a5a4a90, 0x9e902e7b, 0x6cfbad78, 0x7fab5e8c, 0x8dc0dd8f, + 0xe330a81a, 0x115b2b19, 0x020bd8ed, 0xf0605bee, 0x24aa3f05, 0xd6c1bc06, 0xc5914ff2, 0x37faccf1, + 0x69e9f0d5, 0x9b8273d6, 0x88d28022, 0x7ab90321, 0xae7367ca, 0x5c18e4c9, 0x4f48173d, 0xbd23943e, + 0xf36e6f75, 0x0105ec76, 0x12551f82, 0xe03e9c81, 0x34f4f86a, 0xc69f7b69, 0xd5cf889d, 0x27a40b9e, + 0x79b737ba, 0x8bdcb4b9, 0x988c474d, 0x6ae7c44e, 0xbe2da0a5, 0x4c4623a6, 0x5f16d052, 0xad7d5351 +}; + + +/*! \brief Calculates the UDF crc checksum for the given byte stream. + + Based on crc code from UDF-2.50 6.5, as permitted. + This is reversed. + + \param data Pointer to the byte stream. + \param length Length of the byte stream in bytes. + + \return The crc checksum, or 0 if an error occurred. +*/ +uint32 +calculate_crc(uint32 crc, uint8 *data, uint16 length) +{ + if (data) { + for ( ; length > 0; length--, data++) + crc = kCrcTable[(crc ^ *data) & 0xff] ^ (crc >> 8); + } + return crc; +} + diff --git a/src/add-ons/kernel/file_systems/btrfs/CRCTable.h b/src/add-ons/kernel/file_systems/btrfs/CRCTable.h new file mode 100644 index 0000000000..965251a762 --- /dev/null +++ b/src/add-ons/kernel/file_systems/btrfs/CRCTable.h @@ -0,0 +1,10 @@ +/* + * Copyright 2011, Haiku Inc. All rights reserved. + * This file may be used under the terms of the MIT License. + * + * Authors: + * Jérôme Duval + */ + +uint32 calculate_crc(uint32 crc, uint8 *data, uint16 length); + diff --git a/src/add-ons/kernel/file_systems/btrfs/CachedBlock.h b/src/add-ons/kernel/file_systems/btrfs/CachedBlock.h new file mode 100644 index 0000000000..09fec08abb --- /dev/null +++ b/src/add-ons/kernel/file_systems/btrfs/CachedBlock.h @@ -0,0 +1,97 @@ +/* + * Copyright 2001-2008, Axel Dörfler, axeld@pinc-software.de. + * This file may be used under the terms of the MIT License. + */ +#ifndef CACHED_BLOCK_H +#define CACHED_BLOCK_H + +//! interface for the block cache + +#include + +#include "Volume.h" + + +class CachedBlock { +public: + CachedBlock(Volume* volume); + CachedBlock(Volume* volume, off_t block); + ~CachedBlock(); + + void Keep(); + void Unset(); + + const uint8* SetTo(off_t block); + + const uint8* Block() const { return fBlock; } + off_t BlockNumber() const { return fBlockNumber; } + +private: + CachedBlock(const CachedBlock &); + CachedBlock &operator=(const CachedBlock &); + // no implementation + +protected: + Volume* fVolume; + off_t fBlockNumber; + uint8* fBlock; +}; + + +// inlines + + +inline +CachedBlock::CachedBlock(Volume* volume) + : + fVolume(volume), + fBlockNumber(0), + fBlock(NULL) +{ +} + + +inline +CachedBlock::CachedBlock(Volume* volume, off_t block) + : + fVolume(volume), + fBlockNumber(0), + fBlock(NULL) +{ + SetTo(block); +} + + +inline +CachedBlock::~CachedBlock() +{ + Unset(); +} + + +inline void +CachedBlock::Keep() +{ + fBlock = NULL; +} + + +inline void +CachedBlock::Unset() +{ + if (fBlock != NULL) { + block_cache_put(fVolume->BlockCache(), fBlockNumber); + fBlock = NULL; + } +} + + +inline const uint8 * +CachedBlock::SetTo(off_t block) +{ + Unset(); + fBlockNumber = block; + return fBlock = (uint8 *)block_cache_get(fVolume->BlockCache(), block); +} + +#endif // CACHED_BLOCK_H diff --git a/src/add-ons/kernel/file_systems/btrfs/Chunk.cpp b/src/add-ons/kernel/file_systems/btrfs/Chunk.cpp new file mode 100644 index 0000000000..d9b39e21a7 --- /dev/null +++ b/src/add-ons/kernel/file_systems/btrfs/Chunk.cpp @@ -0,0 +1,77 @@ +/* + * Copyright 2011, Haiku Inc. All rights reserved. + * This file may be used under the terms of the MIT License. + * + * Authors: + * Jérôme Duval + */ + + +#include "Chunk.h" + +#include +#include + + +//#define TRACE_BTRFS +#ifdef TRACE_BTRFS +# define TRACE(x...) dprintf("\33[34mbtrfs:\33[0m " x) +#else +# define TRACE(x...) ; +#endif +# define FATAL(x...) dprintf("\33[34mbtrfs:\33[0m " x) + + +Chunk::Chunk(struct btrfs_chunk* chunk, fsblock_t offset) + : + fChunk(NULL), + fInitStatus(B_OK) +{ + fChunkOffset = offset; + fChunk = (struct btrfs_chunk*)malloc(sizeof(struct btrfs_chunk) + + chunk->StripeCount() * sizeof(struct btrfs_stripe)); + if (fChunk == NULL) + fInitStatus = B_NO_MEMORY; + memcpy(fChunk, chunk, sizeof(struct btrfs_chunk) + + chunk->StripeCount() * sizeof(struct btrfs_stripe)); + + TRACE("chunk[0] length %llu owner %llu stripe_length %llu type %llu " + "stripe_count %u sub_stripes %u sector_size %lu\n", chunk->Length(), + chunk->Owner(), chunk->StripeLength(), chunk->Type(), + chunk->StripeCount(), chunk->SubStripes(), chunk->SectorSize()); + for(int32 i = 0; i < chunk->StripeCount(); i++) { + TRACE("chunk.stripe[%ld].physical %lld deviceid %lld\n", i, + chunk->stripes[i].Offset(), chunk->stripes[i].DeviceID()); + } +} + + +Chunk::~Chunk() +{ + free(fChunk); +} + + +uint32 +Chunk::Size() const +{ + return sizeof(struct btrfs_chunk) + + fChunk->StripeCount() * sizeof(struct btrfs_stripe); +} + + +status_t +Chunk::FindBlock(off_t logical, off_t &physical) +{ + if (fChunk == NULL) + return B_NO_INIT; + + if (logical < fChunkOffset + || logical > (fChunkOffset + fChunk->Length())) + return B_BAD_VALUE; + + // only one stripe + physical = logical + fChunk->stripes[0].Offset() - fChunkOffset; + return B_OK; +} + diff --git a/src/add-ons/kernel/file_systems/btrfs/Chunk.h b/src/add-ons/kernel/file_systems/btrfs/Chunk.h new file mode 100644 index 0000000000..b751e72e87 --- /dev/null +++ b/src/add-ons/kernel/file_systems/btrfs/Chunk.h @@ -0,0 +1,31 @@ +/* + * Copyright 2011, Haiku Inc. All rights reserved. + * This file may be used under the terms of the MIT License. + * + * Authors: + * Jérôme Duval + */ +#ifndef CHUNK_H +#define CHUNK_H + + +#include "btrfs.h" + + +class Chunk { +public: + Chunk(struct btrfs_chunk* chunk, + fsblock_t offset); + ~Chunk(); + uint32 Size() const; + status_t FindBlock(off_t logical, off_t &physical); + fsblock_t Offset() const { return fChunkOffset; } + fsblock_t End() const + { return fChunkOffset + fChunk->Length(); } +private: + struct btrfs_chunk* fChunk; + fsblock_t fChunkOffset; + status_t fInitStatus; +}; + +#endif // CHUNK_H diff --git a/src/add-ons/kernel/file_systems/btrfs/DirectoryIterator.cpp b/src/add-ons/kernel/file_systems/btrfs/DirectoryIterator.cpp new file mode 100644 index 0000000000..0803f55425 --- /dev/null +++ b/src/add-ons/kernel/file_systems/btrfs/DirectoryIterator.cpp @@ -0,0 +1,155 @@ +/* + * Copyright 2011, Jérôme Duval, korli@users.berlios.de. + * This file may be used under the terms of the MIT License. + */ + + +#include "DirectoryIterator.h" + +#include +#include + +#include "CRCTable.h" + + +//#define TRACE_BTRFS +#ifdef TRACE_BTRFS +# define TRACE(x...) dprintf("\33[34mbtrfs:\33[0m " x) +#else +# define TRACE(x...) ; +#endif +# define ERROR(x...) dprintf("\33[34mbtrfs:\33[0m " x) + + +DirectoryIterator::DirectoryIterator(Inode* inode) + : + fOffset(0), + fInode(inode), + fIterator(NULL) +{ + struct btrfs_key key; + key.SetType(BTRFS_KEY_TYPE_DIR_INDEX); + key.SetObjectID(inode->ID()); + fIterator = new(std::nothrow) TreeIterator(inode->GetVolume()->FSTree(), + key); +} + + +DirectoryIterator::~DirectoryIterator() +{ + delete fIterator; +} + + +status_t +DirectoryIterator::InitCheck() +{ + return fIterator != NULL ? B_OK : B_NO_MEMORY; +} + + +status_t +DirectoryIterator::GetNext(char* name, size_t* _nameLength, ino_t* _id) +{ + if (fOffset == 0) { + *_nameLength = 3; + strlcpy(name, "..", *_nameLength); + *_id = fInode->ID(); + fOffset = 1; + return B_OK; + } else if (fOffset == 1) { + *_nameLength = 2; + strlcpy(name, ".", *_nameLength); + fOffset = 2; + if (fInode->ID() == BTRFS_OBJECT_ID_CHUNK_TREE) { + *_id = fInode->ID(); + return B_OK; + } + return fInode->FindParent(_id); + } + + btrfs_key key; + btrfs_dir_entry *entries; + size_t entries_length; + status_t status = fIterator->GetNextEntry(key, (void**)&entries, + &entries_length); + if (status != B_OK) + return status; + + btrfs_dir_entry *entry = entries; + uint16 current = 0; + while (current < entries_length) { + current += entry->Length(); + break; + // TODO there could be several entries with the same name hash + entry = (btrfs_dir_entry *)((uint8*)entry + entry->Length()); + } + + TRACE("DirectoryIterator::GetNext() entries_length %ld name_length %d\n", + entries_length, entry->NameLength()); + + memcpy(name, entry + 1, entry->NameLength()); + name[entry->NameLength()] = '\0'; + *_nameLength = entry->NameLength(); + *_id = entry->InodeID(); + free(entries); + + return B_OK; +} + + +status_t +DirectoryIterator::Lookup(const char* name, size_t nameLength, ino_t* _id) +{ + if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) { + if (strcmp(name, ".") == 0 + || fInode->ID() == BTRFS_OBJECT_ID_CHUNK_TREE) { + *_id = fInode->ID(); + return B_OK; + } + return fInode->FindParent(_id); + } + + uint32 hash = calculate_crc((uint32)~1, (uint8*)name, nameLength); + struct btrfs_key key; + key.SetType(BTRFS_KEY_TYPE_DIR_ITEM); + key.SetObjectID(fInode->ID()); + key.SetOffset(hash); + + btrfs_dir_entry *entries; + size_t length; + status_t status = fInode->GetVolume()->FSTree()->FindExact(key, + (void**)&entries, &length); + if (status != B_OK) { + TRACE("DirectoryIterator::Lookup(): Couldn't find entry with hash %lu " + "\"%s\"\n", hash, name); + return status; + } + + btrfs_dir_entry *entry = entries; + uint16 current = 0; + while (current < length) { + current += entry->Length(); + break; + // TODO there could be several entries with the same name hash + entry = (btrfs_dir_entry *)((uint8*)entry + entry->Length()); + } + + TRACE("DirectoryIterator::Lookup() entries_length %ld name_length %d\n", + length, entry->NameLength()); + + *_id = entry->InodeID(); + free(entries); + + return B_OK; +} + + +status_t +DirectoryIterator::Rewind() +{ + fIterator->Rewind(); + fOffset = BPLUSTREE_BEGIN; + return B_OK; +} + diff --git a/src/add-ons/kernel/file_systems/btrfs/DirectoryIterator.h b/src/add-ons/kernel/file_systems/btrfs/DirectoryIterator.h new file mode 100644 index 0000000000..069c9a0ab6 --- /dev/null +++ b/src/add-ons/kernel/file_systems/btrfs/DirectoryIterator.h @@ -0,0 +1,30 @@ +/* + * Copyright 2011, Jérôme Duval, korli@users.berlios.de. + * This file may be used under the terms of the MIT License. + */ +#ifndef DIRECTORYITERATOR_H +#define DIRECTORYITERATOR_H + + +#include "BPlusTree.h" +#include "Inode.h" + + +class DirectoryIterator { +public: + DirectoryIterator(Inode* inode); + ~DirectoryIterator(); + + status_t InitCheck(); + + status_t GetNext(char* name, size_t* _nameLength, ino_t* _id); + status_t Lookup(const char* name, size_t nameLength, ino_t* _id); + status_t Rewind(); +private: + uint64 fOffset; + Inode* fInode; + TreeIterator* fIterator; +}; + + +#endif // DIRECTORYITERATOR_H diff --git a/src/add-ons/kernel/file_systems/btrfs/Inode.cpp b/src/add-ons/kernel/file_systems/btrfs/Inode.cpp new file mode 100644 index 0000000000..3d394e10f5 --- /dev/null +++ b/src/add-ons/kernel/file_systems/btrfs/Inode.cpp @@ -0,0 +1,253 @@ +/* + * Copyright 2011, Jérôme Duval, korli@users.berlios.de. + * Copyright 2008, Axel Dörfler, axeld@pinc-software.de. + * This file may be used under the terms of the MIT License. + */ + + +#include "Inode.h" + +#include +#include + +#include "BPlusTree.h" +#include "CachedBlock.h" +#include "Utility.h" + + +#undef ASSERT +//#define TRACE_BTRFS +#ifdef TRACE_BTRFS +# define TRACE(x...) dprintf("\33[34mbtrfs:\33[0m " x) +# define ASSERT(x) { if (!(x)) kernel_debugger("btrfs: assert failed: " #x "\n"); } +#else +# define TRACE(x...) ; +# define ASSERT(x) ; +#endif +#define ERROR(x...) dprintf("\33[34mbtrfs:\33[0m " x) + + +Inode::Inode(Volume* volume, ino_t id) + : + fVolume(volume), + fID(id), + fCache(NULL), + fMap(NULL) +{ + rw_lock_init(&fLock, "btrfs inode"); + + fInitStatus = UpdateNodeFromDisk(); + if (fInitStatus == B_OK) { + if (!IsDirectory() && !IsSymLink()) { + fCache = file_cache_create(fVolume->ID(), ID(), Size()); + fMap = file_map_create(fVolume->ID(), ID(), Size()); + } + } +} + + +Inode::Inode(Volume* volume) + : + fVolume(volume), + fID(0), + fCache(NULL), + fMap(NULL), + fInitStatus(B_NO_INIT) +{ + rw_lock_init(&fLock, "btrfs inode"); +} + + +Inode::~Inode() +{ + TRACE("Inode destructor\n"); + file_cache_delete(FileCache()); + file_map_delete(Map()); + TRACE("Inode destructor: Done\n"); +} + + +status_t +Inode::InitCheck() +{ + return fInitStatus; +} + + +status_t +Inode::UpdateNodeFromDisk() +{ + struct btrfs_key search_key; + search_key.SetType(BTRFS_KEY_TYPE_INODE_ITEM); + search_key.SetObjectID(fID); + search_key.SetOffset(0); + + struct btrfs_inode *node; + if (fVolume->FSTree()->FindExact(search_key, (void**)&node) != B_OK) { + ERROR("Inode::UpdateNodeFromDisk(): Couldn't find inode %" + B_PRIdINO "\n", fID); + return B_ENTRY_NOT_FOUND; + } + + memcpy(&fNode, node, sizeof(struct btrfs_inode)); + free(node); + return B_OK; +} + + +status_t +Inode::CheckPermissions(int accessMode) const +{ + // you never have write access to a read-only volume + if ((accessMode & W_OK) != 0 && fVolume->IsReadOnly()) + return B_READ_ONLY_DEVICE; + + // get node permissions + mode_t mode = Mode(); + int userPermissions = (mode & S_IRWXU) >> 6; + int groupPermissions = (mode & S_IRWXG) >> 3; + int otherPermissions = mode & S_IRWXO; + + // get the node permissions for this uid/gid + int permissions = 0; + uid_t uid = geteuid(); + gid_t gid = getegid(); + + if (uid == 0) { + // user is root + // root has always read/write permission, but at least one of the + // X bits must be set for execute permission + permissions = userPermissions | groupPermissions | otherPermissions + | R_OK | W_OK; + } else if (uid == (uid_t)fNode.UserID()) { + // user is node owner + permissions = userPermissions; + } else if (gid == (gid_t)fNode.GroupID()) { + // user is in owning group + permissions = groupPermissions; + } else { + // user is one of the others + permissions = otherPermissions; + } + + return (accessMode & ~permissions) == 0 ? B_OK : B_NOT_ALLOWED; + return B_OK; +} + + +status_t +Inode::FindBlock(off_t pos, off_t& physical, off_t *_length) +{ + struct btrfs_key search_key; + search_key.SetType(BTRFS_KEY_TYPE_EXTENT_DATA); + search_key.SetObjectID(fID); + search_key.SetOffset(pos + 1); + + btrfs_extent_data *extent_data; + status_t status = fVolume->FSTree()->FindPrevious(search_key, + (void**)&extent_data); + if (status != B_OK) { + ERROR("Inode::FindBlock(): Couldn't find extent_data 0x%lx\n", status); + return status; + } + + TRACE("Inode::FindBlock(%" B_PRIdINO ") key.Offset() %lld\n", ID(), + search_key.Offset()); + + off_t diff = pos - search_key.Offset(); + off_t logical = 0; + if (extent_data->Type() == BTRFS_EXTENT_DATA_REGULAR) + logical = diff + extent_data->disk_offset; + else + panic("unknown extent type; %d\n", extent_data->Type()); + status = fVolume->FindBlock(logical, physical); + if (_length != NULL) + *_length = extent_data->Size() - diff; + TRACE("Inode::FindBlock(%" B_PRIdINO ") %lld physical %lld\n", ID(), + pos, physical); + + free(extent_data); + return status; +} + + +status_t +Inode::ReadAt(off_t pos, uint8* buffer, size_t* _length) +{ + size_t length = *_length; + + // set/check boundaries for pos/length + if (pos < 0) { + ERROR("inode %" B_PRIdINO ": ReadAt failed(pos %lld, length %lu)\n", + ID(), pos, length); + return B_BAD_VALUE; + } + + if (pos >= Size() || length == 0) { + TRACE("inode %" B_PRIdINO ": ReadAt 0 (pos %lld, length %lu)\n", + ID(), pos, length); + *_length = 0; + return B_NO_ERROR; + } + + // the file cache doesn't seem to like non block aligned file offset + // so we avoid the file cache for inline extents + struct btrfs_key search_key; + search_key.SetType(BTRFS_KEY_TYPE_EXTENT_DATA); + search_key.SetObjectID(fID); + search_key.SetOffset(pos + 1); + + btrfs_extent_data *extent_data; + status_t status = fVolume->FSTree()->FindPrevious(search_key, + (void**)&extent_data); + if (status != B_OK) { + ERROR("Inode::FindBlock(): Couldn't find extent_data 0x%lx\n", status); + return status; + } + + if (FileCache() != NULL + && extent_data->Type() == BTRFS_EXTENT_DATA_REGULAR) { + TRACE("inode %" B_PRIdINO ": ReadAt cache (pos %lld, length %lu)\n", + ID(), pos, length); + free(extent_data); + return file_cache_read(FileCache(), NULL, pos, buffer, _length); + } + + TRACE("Inode::ReadAt(%" B_PRIdINO ") key.Offset() %lld\n", ID(), + search_key.Offset()); + + off_t diff = pos - search_key.Offset(); + if (extent_data->Type() != BTRFS_EXTENT_DATA_INLINE) + panic("unknown extent type; %d\n", extent_data->Type()); + + *_length = min_c(extent_data->MemoryBytes() - diff, *_length); + memcpy(buffer, extent_data->inline_data, *_length); + free(extent_data); + return B_OK; + +} + + +status_t +Inode::FindParent(ino_t *id) +{ + struct btrfs_key search_key; + search_key.SetType(BTRFS_KEY_TYPE_INODE_REF); + search_key.SetObjectID(fID); + search_key.SetOffset(-1); + + void *node_ref; + if (fVolume->FSTree()->FindPrevious(search_key, &node_ref) != B_OK) { + ERROR("Inode::FindParent(): Couldn't find inode for %" B_PRIdINO "\n", + fID); + return B_ERROR; + } + + free(node_ref); + *id = search_key.Offset(); + TRACE("Inode::FindParent() for %" B_PRIdINO ": %" B_PRIdINO "\n", fID, + *id); + + return B_OK; +} + diff --git a/src/add-ons/kernel/file_systems/btrfs/Inode.h b/src/add-ons/kernel/file_systems/btrfs/Inode.h new file mode 100644 index 0000000000..b6fa15766f --- /dev/null +++ b/src/add-ons/kernel/file_systems/btrfs/Inode.h @@ -0,0 +1,152 @@ +/* + * Copyright 2011, Jérôme Duval, korli@users.berlios.de. + * Copyright 2008, Axel Dörfler, axeld@pinc-software.de. + * This file may be used under the terms of the MIT License. + */ +#ifndef INODE_H +#define INODE_H + + +#include +#include +#include + +#include "btrfs.h" +#include "Volume.h" + + +//#define TRACE_BTRFS +#ifdef TRACE_BTRFS +# define TRACEI(x...) dprintf("\33[34mbtrfs:\33[0m " x) +#else +# define TRACEI(x...) ; +#endif + + +class Inode { +public: + Inode(Volume* volume, ino_t id); + ~Inode(); + + status_t InitCheck(); + + ino_t ID() const { return fID; } + + rw_lock* Lock() { return &fLock; } + + status_t UpdateNodeFromDisk(); + + bool IsDirectory() const + { return S_ISDIR(Mode()); } + bool IsFile() const + { return S_ISREG(Mode()); } + bool IsSymLink() const + { return S_ISLNK(Mode()); } + status_t CheckPermissions(int accessMode) const; + + mode_t Mode() const { return fNode.Mode(); } + off_t Size() const { return fNode.Size(); } + uid_t UserID() const { return fNode.UserID(); } + gid_t GroupID() const { return fNode.GroupID(); } + void GetChangeTime(struct timespec ×pec) const + { fNode.GetChangeTime(timespec); } + void GetModificationTime(struct timespec ×pec) const + { fNode.GetModificationTime(timespec); } + void GetCreationTime(struct timespec ×pec) const + { fNode.GetCreationTime(timespec); } + void GetAccessTime(struct timespec ×pec) const + { fNode.GetCreationTime(timespec); } + + Volume* GetVolume() const { return fVolume; } + + status_t FindBlock(off_t logical, off_t& physical, + off_t *_length = NULL); + status_t ReadAt(off_t pos, uint8 *buffer, size_t *length); + status_t FillGapWithZeros(off_t start, off_t end); + + void* FileCache() const { return fCache; } + void* Map() const { return fMap; } + + status_t FindParent(ino_t *id); +private: + Inode(Volume* volume); + Inode(const Inode&); + Inode &operator=(const Inode&); + // no implementation + + uint64 _NumBlocks(); + + rw_lock fLock; + ::Volume* fVolume; + ino_t fID; + void* fCache; + void* fMap; + status_t fInitStatus; + struct btrfs_inode fNode; +}; + + +// The Vnode class provides a convenience layer upon get_vnode(), so that +// you don't have to call put_vnode() anymore, which may make code more +// readable in some cases + +class Vnode { +public: + Vnode(Volume* volume, ino_t id) + : + fInode(NULL) + { + SetTo(volume, id); + } + + Vnode() + : + fStatus(B_NO_INIT), + fInode(NULL) + { + } + + ~Vnode() + { + Unset(); + } + + status_t InitCheck() + { + return fStatus; + } + + void Unset() + { + if (fInode != NULL) { + put_vnode(fInode->GetVolume()->FSVolume(), fInode->ID()); + fInode = NULL; + fStatus = B_NO_INIT; + } + } + + status_t SetTo(Volume* volume, ino_t id) + { + Unset(); + + return fStatus = get_vnode(volume->FSVolume(), id, (void**)&fInode); + } + + status_t Get(Inode** _inode) + { + *_inode = fInode; + return fStatus; + } + + void Keep() + { + TRACEI("Vnode::Keep()\n"); + fInode = NULL; + } + +private: + status_t fStatus; + Inode* fInode; +}; + +#endif // INODE_H diff --git a/src/add-ons/kernel/file_systems/btrfs/Jamfile b/src/add-ons/kernel/file_systems/btrfs/Jamfile new file mode 100644 index 0000000000..28b29c44d5 --- /dev/null +++ b/src/add-ons/kernel/file_systems/btrfs/Jamfile @@ -0,0 +1,15 @@ +SubDir HAIKU_TOP src add-ons kernel file_systems btrfs ; + +UsePrivateHeaders [ FDirName kernel util ] ; +UsePrivateHeaders shared storage ; +UsePrivateKernelHeaders ; + +KernelAddon btrfs : + kernel_interface.cpp + BPlusTree.cpp + Chunk.cpp + CRCTable.cpp + DirectoryIterator.cpp + Inode.cpp + Volume.cpp +; diff --git a/src/add-ons/kernel/file_systems/btrfs/Utility.h b/src/add-ons/kernel/file_systems/btrfs/Utility.h new file mode 100644 index 0000000000..07a252e742 --- /dev/null +++ b/src/add-ons/kernel/file_systems/btrfs/Utility.h @@ -0,0 +1,41 @@ +/* + * Copyright 2001-2009, Axel Dörfler, axeld@pinc-software.de. + * This file may be used under the terms of the MIT License. + */ +#ifndef UTILITY_H +#define UTILITY_H + + +#include "btrfs.h" + + +enum inode_type { + S_DIRECTORY = S_IFDIR, + S_FILE = S_IFREG, + S_SYMLINK = S_IFLNK, + + S_INDEX_TYPES = (S_STR_INDEX | S_INT_INDEX | S_UINT_INDEX + | S_LONG_LONG_INDEX | S_ULONG_LONG_INDEX + | S_FLOAT_INDEX | S_DOUBLE_INDEX), + + S_EXTENDED_TYPES = (S_ATTR_DIR | S_ATTR | S_INDEX_DIR) +}; + + +/*! Converts the open mode, the open flags given to bfs_open(), into + access modes, e.g. since O_RDONLY requires read access to the + file, it will be converted to R_OK. +*/ +inline int +open_mode_to_access(int openMode) +{ + openMode &= O_RWMASK; + if (openMode == O_RDONLY) + return R_OK; + if (openMode == O_WRONLY) + return W_OK; + + return R_OK | W_OK; +} + +#endif // UTILITY_H diff --git a/src/add-ons/kernel/file_systems/btrfs/Volume.cpp b/src/add-ons/kernel/file_systems/btrfs/Volume.cpp new file mode 100644 index 0000000000..13fc4823aa --- /dev/null +++ b/src/add-ons/kernel/file_systems/btrfs/Volume.cpp @@ -0,0 +1,535 @@ +/* + * Copyright 2011, Jérôme Duval, korli@users.berlios.de. + * Copyright 2008-2010, Axel Dörfler, axeld@pinc-software.de. + * This file may be used under the terms of the MIT License. + */ + + +//! Super block, mounting, etc. + + +#include "Volume.h" + +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "BPlusTree.h" +#include "CachedBlock.h" +#include "Chunk.h" +#include "Inode.h" + + +//#define TRACE_BTRFS +#ifdef TRACE_BTRFS +# define TRACE(x...) dprintf("\33[34mbtrfs:\33[0m " x) +#else +# define TRACE(x...) ; +#endif +# define ERROR(x...) dprintf("\33[34mbtrfs:\33[0m " x) + + +class DeviceOpener { +public: + DeviceOpener(int fd, int mode); + DeviceOpener(const char* device, int mode); + ~DeviceOpener(); + + int Open(const char* device, int mode); + int Open(int fd, int mode); + void* InitCache(off_t numBlocks, uint32 blockSize); + void RemoveCache(bool allowWrites); + + void Keep(); + + int Device() const { return fDevice; } + int Mode() const { return fMode; } + bool IsReadOnly() const + { return _IsReadOnly(fMode); } + + status_t GetSize(off_t* _size, uint32* _blockSize = NULL); + +private: + static bool _IsReadOnly(int mode) + { return (mode & O_RWMASK) == O_RDONLY;} + static bool _IsReadWrite(int mode) + { return (mode & O_RWMASK) == O_RDWR;} + + int fDevice; + int fMode; + void* fBlockCache; +}; + + +DeviceOpener::DeviceOpener(const char* device, int mode) + : + fBlockCache(NULL) +{ + Open(device, mode); +} + + +DeviceOpener::DeviceOpener(int fd, int mode) + : + fBlockCache(NULL) +{ + Open(fd, mode); +} + + +DeviceOpener::~DeviceOpener() +{ + if (fDevice >= 0) { + RemoveCache(false); + close(fDevice); + } +} + + +int +DeviceOpener::Open(const char* device, int mode) +{ + fDevice = open(device, mode | O_NOCACHE); + if (fDevice < 0) + fDevice = errno; + + if (fDevice < 0 && _IsReadWrite(mode)) { + // try again to open read-only (don't rely on a specific error code) + return Open(device, O_RDONLY | O_NOCACHE); + } + + if (fDevice >= 0) { + // opening succeeded + fMode = mode; + if (_IsReadWrite(mode)) { + // check out if the device really allows for read/write access + device_geometry geometry; + if (!ioctl(fDevice, B_GET_GEOMETRY, &geometry)) { + if (geometry.read_only) { + // reopen device read-only + close(fDevice); + return Open(device, O_RDONLY | O_NOCACHE); + } + } + } + } + + return fDevice; +} + + +int +DeviceOpener::Open(int fd, int mode) +{ + fDevice = dup(fd); + if (fDevice < 0) + return errno; + + fMode = mode; + + return fDevice; +} + + +void* +DeviceOpener::InitCache(off_t numBlocks, uint32 blockSize) +{ + return fBlockCache = block_cache_create(fDevice, numBlocks, blockSize, + IsReadOnly()); +} + + +void +DeviceOpener::RemoveCache(bool allowWrites) +{ + if (fBlockCache == NULL) + return; + + block_cache_delete(fBlockCache, allowWrites); + fBlockCache = NULL; +} + + +void +DeviceOpener::Keep() +{ + fDevice = -1; +} + + +/*! Returns the size of the device in bytes. It uses B_GET_GEOMETRY + to compute the size, or fstat() if that failed. +*/ +status_t +DeviceOpener::GetSize(off_t* _size, uint32* _blockSize) +{ + device_geometry geometry; + if (ioctl(fDevice, B_GET_GEOMETRY, &geometry) < 0) { + // maybe it's just a file + struct stat stat; + if (fstat(fDevice, &stat) < 0) + return B_ERROR; + + if (_size) + *_size = stat.st_size; + if (_blockSize) // that shouldn't cause us any problems + *_blockSize = 512; + + return B_OK; + } + + if (_size) { + *_size = 1ULL * geometry.head_count * geometry.cylinder_count + * geometry.sectors_per_track * geometry.bytes_per_sector; + } + if (_blockSize) + *_blockSize = geometry.bytes_per_sector; + + return B_OK; +} + + +// #pragma mark - + + +bool +btrfs_super_block::IsValid() +{ + // TODO: check some more values! + if (strncmp(magic, BTRFS_SUPER_BLOCK_MAGIC, sizeof(magic)) != 0) + return false; + + return true; +} + + +// #pragma mark - + + +Volume::Volume(fs_volume* volume) + : + fFSVolume(volume), + fFlags(0), + fChunk(NULL), + fChunkTree(NULL) +{ + mutex_init(&fLock, "btrfs volume"); +} + + +Volume::~Volume() +{ + TRACE("Volume destructor.\n"); +} + + +bool +Volume::IsValidSuperBlock() +{ + return fSuperBlock.IsValid(); +} + + +const char* +Volume::Name() const +{ + if (fSuperBlock.label[0]) + return fSuperBlock.label; + + return fName; +} + + +status_t +Volume::Mount(const char* deviceName, uint32 flags) +{ + flags |= B_MOUNT_READ_ONLY; + // we only support read-only for now + + if ((flags & B_MOUNT_READ_ONLY) != 0) { + TRACE("Volume::Mount(): Read only\n"); + } else { + TRACE("Volume::Mount(): Read write\n"); + } + + DeviceOpener opener(deviceName, (flags & B_MOUNT_READ_ONLY) != 0 + ? O_RDONLY : O_RDWR); + fDevice = opener.Device(); + if (fDevice < B_OK) { + ERROR("Volume::Mount(): couldn't open device\n"); + return fDevice; + } + + if (opener.IsReadOnly()) + fFlags |= VOLUME_READ_ONLY; + + // read the super block + status_t status = Identify(fDevice, &fSuperBlock); + if (status != B_OK) { + ERROR("Volume::Mount(): Identify() failed\n"); + return status; + } + + fBlockSize = fSuperBlock.BlockSize(); + TRACE("block size %ld\n", fBlockSize); + + uint8* start = (uint8*)&fSuperBlock.system_chunk_array[0]; + uint8* end = (uint8*)&fSuperBlock.system_chunk_array[2048]; + while (start < end) { + struct btrfs_key* key = (struct btrfs_key*)start; + TRACE("system_chunk_array object_id 0x%llx offset 0x%llx type 0x%x\n", + key->ObjectID(), key->Offset(), key->Type()); + if (key->Type() != BTRFS_KEY_TYPE_CHUNK_ITEM) { + break; + } + + struct btrfs_chunk* chunk = (struct btrfs_chunk*)(key + 1); + fChunk = new(std::nothrow) Chunk(chunk, key->Offset()); + if (fChunk == NULL) + return B_ERROR; + start += sizeof(struct btrfs_key) + fChunk->Size(); + } + + TRACE("Volume::Mount() generation: %lld\n", fSuperBlock.Generation()); + fsblock_t physical = 0; + FindBlock(fSuperBlock.Root(), physical); + TRACE("Volume::Mount() root: %lld (physical %lld)\n", + fSuperBlock.Root(), physical); + FindBlock(fSuperBlock.ChunkRoot(), physical); + TRACE("Volume::Mount() chunk_root: %lld (physical %lld)\n", + fSuperBlock.ChunkRoot(), physical); + FindBlock(fSuperBlock.LogRoot(), physical); + TRACE("Volume::Mount() log_root: %lld (physical %lld)\n", + fSuperBlock.LogRoot(), physical); + + // check if the device size is large enough to hold the file system + off_t diskSize; + status = opener.GetSize(&diskSize); + if (status != B_OK) + return status; + if (diskSize < (off_t)fSuperBlock.TotalSize()) + return B_BAD_VALUE; + + fBlockCache = opener.InitCache(fSuperBlock.TotalSize() / fBlockSize, + fBlockSize); + if (fBlockCache == NULL) + return B_ERROR; + + TRACE("Volume::Mount(): Initialized block cache: %p\n", fBlockCache); + + fChunkTree = new(std::nothrow) BPlusTree(this, fSuperBlock.ChunkRoot()); + if (fChunkTree == NULL) + return B_NO_MEMORY; + + FindBlock(fSuperBlock.Root(), physical); + TRACE("Volume::Mount() root: %lld (physical %lld)\n", + fSuperBlock.Root(), physical); + FindBlock(fSuperBlock.ChunkRoot(), physical); + TRACE("Volume::Mount() chunk_root: %lld (physical %lld)\n", + fSuperBlock.ChunkRoot(), physical); + FindBlock(fSuperBlock.LogRoot(), physical); + TRACE("Volume::Mount() log_root: %lld (physical %lld)\n", + fSuperBlock.LogRoot(), physical); + + fRootTree = new(std::nothrow) BPlusTree(this, fSuperBlock.Root()); + if (fRootTree == NULL) + return B_NO_MEMORY; + TRACE("Volume::Mount(): Searching extent root\n"); + struct btrfs_key search_key; + search_key.SetOffset(0); + search_key.SetType(BTRFS_KEY_TYPE_ROOT_ITEM); + search_key.SetObjectID(BTRFS_OBJECT_ID_EXTENT_TREE); + struct btrfs_root *root; + if (fRootTree->FindNext(search_key, (void**)&root) != B_OK) { + ERROR("Volume::Mount(): Couldn't find extent root\n"); + return B_ERROR; + } + TRACE("Volume::Mount(): Found extent root: %lld\n", root->BlockNum()); + fExtentTree = new(std::nothrow) BPlusTree(this, root->BlockNum()); + free(root); + if (fExtentTree == NULL) + return B_NO_MEMORY; + + search_key.SetOffset(0); + search_key.SetObjectID(BTRFS_OBJECT_ID_FS_TREE); + if (fRootTree->FindNext(search_key, (void**)&root) != B_OK) { + ERROR("Volume::Mount(): Couldn't find fs root\n"); + return B_ERROR; + } + TRACE("Volume::Mount(): Found fs root: %lld\n", root->BlockNum()); + fFSTree = new(std::nothrow) BPlusTree(this, root->BlockNum()); + free(root); + if (fFSTree == NULL) + return B_NO_MEMORY; + + search_key.SetOffset(0); + search_key.SetObjectID(BTRFS_OBJECT_ID_DEV_TREE); + if (fRootTree->FindNext(search_key, (void**)&root) != B_OK) { + ERROR("Volume::Mount(): Couldn't find dev root\n"); + return B_ERROR; + } + TRACE("Volume::Mount(): Found dev root: %lld\n", root->BlockNum()); + fDevTree = new(std::nothrow) BPlusTree(this, root->BlockNum()); + free(root); + if (fDevTree == NULL) + return B_NO_MEMORY; + + search_key.SetOffset(0); + search_key.SetObjectID(BTRFS_OBJECT_ID_CHECKSUM_TREE); + if (fRootTree->FindNext(search_key, (void**)&root) != B_OK) { + ERROR("Volume::Mount(): Couldn't find checksum root\n"); + return B_ERROR; + } + TRACE("Volume::Mount(): Found checksum root: %lld\n", root->BlockNum()); + fChecksumTree = new(std::nothrow) BPlusTree(this, root->BlockNum()); + free(root); + if (fChecksumTree == NULL) + return B_NO_MEMORY; + + // ready + status = get_vnode(fFSVolume, BTRFS_OBJECT_ID_CHUNK_TREE, + (void**)&fRootNode); + if (status != B_OK) { + ERROR("could not create root node: get_vnode() failed!\n"); + return status; + } + + TRACE("Volume::Mount(): Found root node: %lld (%s)\n", fRootNode->ID(), + strerror(fRootNode->InitCheck())); + + // all went fine + opener.Keep(); + + if (!fSuperBlock.label[0]) { + // generate a more or less descriptive volume name + off_t divisor = 1ULL << 40; + char unit = 'T'; + if (diskSize < divisor) { + divisor = 1UL << 30; + unit = 'G'; + if (diskSize < divisor) { + divisor = 1UL << 20; + unit = 'M'; + } + } + + double size = double((10 * diskSize + divisor - 1) / divisor); + // %g in the kernel does not support precision... + + snprintf(fName, sizeof(fName), "%g %cB Btrfs Volume", + size / 10, unit); + } + + return B_OK; +} + + +status_t +Volume::Unmount() +{ + TRACE("Volume::Unmount()\n"); + delete fExtentTree; + delete fChecksumTree; + delete fFSTree; + delete fDevTree; + fExtentTree = NULL; + fChecksumTree = NULL; + fFSTree = NULL; + fDevTree = NULL; + + TRACE("Volume::Unmount(): Putting root node\n"); + put_vnode(fFSVolume, RootNode()->ID()); + TRACE("Volume::Unmount(): Deleting the block cache\n"); + block_cache_delete(fBlockCache, !IsReadOnly()); + TRACE("Volume::Unmount(): Closing device\n"); + close(fDevice); + + TRACE("Volume::Unmount(): Done\n"); + return B_OK; +} + + +status_t +Volume::LoadSuperBlock() +{ + CachedBlock cached(this); + const uint8* block = cached.SetTo(BTRFS_SUPER_BLOCK_OFFSET / fBlockSize); + + if (block == NULL) + return B_IO_ERROR; + + memcpy(&fSuperBlock, block + BTRFS_SUPER_BLOCK_OFFSET % fBlockSize, + sizeof(fSuperBlock)); + + return B_OK; +} + + +status_t +Volume::FindBlock(off_t logical, fsblock_t &physicalBlock) +{ + off_t physical; + status_t status = FindBlock(logical, physical); + if (status != B_OK) + return status; + physicalBlock = physical / fBlockSize; + return B_OK; +} + + +status_t +Volume::FindBlock(off_t logical, off_t &physical) +{ + if (fChunkTree == NULL + || (logical >= fChunk->Offset() && logical < fChunk->End())) { + // try with fChunk + return fChunk->FindBlock(logical, physical); + } + + struct btrfs_key search_key; + search_key.SetOffset(logical); + search_key.SetType(BTRFS_KEY_TYPE_CHUNK_ITEM); + search_key.SetObjectID(BTRFS_OBJECT_ID_CHUNK_TREE); + struct btrfs_chunk *chunk; + size_t chunk_length; + status_t status = fChunkTree->FindPrevious(search_key, (void**)&chunk, + &chunk_length); + if (status != B_OK) + return status; + + Chunk _chunk(chunk, search_key.Offset()); + free(chunk); + status = _chunk.FindBlock(logical, physical); + if (status != B_OK) + return status; + TRACE("Volume::FindBlock(): logical: %lld, physical: %lld\n", logical, + physical); + return B_OK; +} + + +// #pragma mark - Disk scanning and initialization + + +/*static*/ status_t +Volume::Identify(int fd, btrfs_super_block* superBlock) +{ + if (read_pos(fd, BTRFS_SUPER_BLOCK_OFFSET, superBlock, + sizeof(btrfs_super_block)) != sizeof(btrfs_super_block)) + return B_IO_ERROR; + + if (!superBlock->IsValid()) { + ERROR("invalid super block!\n"); + return B_BAD_VALUE; + } + + return B_OK; +} + diff --git a/src/add-ons/kernel/file_systems/btrfs/Volume.h b/src/add-ons/kernel/file_systems/btrfs/Volume.h new file mode 100644 index 0000000000..a832fd2c6e --- /dev/null +++ b/src/add-ons/kernel/file_systems/btrfs/Volume.h @@ -0,0 +1,82 @@ +/* + * Copyright 2011, Jérôme Duval, korli@users.berlios.de. + * Copyright 2008-2010, Axel Dörfler, axeld@pinc-software.de. + * This file may be used under the terms of the MIT License. + */ +#ifndef VOLUME_H +#define VOLUME_H + + +#include + +#include "btrfs.h" + + +enum volume_flags { + VOLUME_READ_ONLY = 0x0001 +}; + +class BPlusTree; +class Chunk; +class Inode; + + +class Volume { +public: + Volume(fs_volume* volume); + ~Volume(); + + status_t Mount(const char* device, uint32 flags); + status_t Unmount(); + + bool IsValidSuperBlock(); + bool IsReadOnly() const + { return (fFlags & VOLUME_READ_ONLY) != 0; } + + Inode* RootNode() const { return fRootNode; } + int Device() const { return fDevice; } + + dev_t ID() const + { return fFSVolume ? fFSVolume->id : -1; } + fs_volume* FSVolume() const { return fFSVolume; } + const char* Name() const; + BPlusTree* FSTree() const { return fFSTree; } + BPlusTree* RootTree() const { return fRootTree; } + + uint32 BlockSize() const { return fBlockSize; } + btrfs_super_block& SuperBlock() { return fSuperBlock; } + + status_t LoadSuperBlock(); + + // cache access + void* BlockCache() { return fBlockCache; } + + static status_t Identify(int fd, btrfs_super_block* superBlock); + + status_t FindBlock(off_t logical, fsblock_t &physical); + status_t FindBlock(off_t logical, off_t &physical); + +private: + mutex fLock; + fs_volume* fFSVolume; + int fDevice; + btrfs_super_block fSuperBlock; + char fName[32]; + + uint32 fFlags; + uint32 fBlockSize; + + void* fBlockCache; + Inode* fRootNode; + + Chunk* fChunk; + BPlusTree* fChunkTree; + BPlusTree* fRootTree; + BPlusTree* fDevTree; + BPlusTree* fExtentTree; + BPlusTree* fFSTree; + BPlusTree* fChecksumTree; +}; + +#endif // VOLUME_H + diff --git a/src/add-ons/kernel/file_systems/btrfs/btrfs.h b/src/add-ons/kernel/file_systems/btrfs/btrfs.h new file mode 100644 index 0000000000..14b220abc1 --- /dev/null +++ b/src/add-ons/kernel/file_systems/btrfs/btrfs.h @@ -0,0 +1,328 @@ +/* + * Copyright 2011, Jérôme Duval, korli@users.berlios.de. + * Distributed under the terms of the MIT License. + */ +#ifndef BTRFS_H +#define BTRFS_H + + +#include + +#include +#include +#include + + +typedef uint64 fileblock_t; // file block number +typedef uint64 fsblock_t; // filesystem block number + + +#define BTRFS_SUPER_BLOCK_OFFSET 0x10000 + +struct btrfs_key { + uint64 object_id; + uint8 type; + uint64 offset; + + uint64 ObjectID() const { return B_LENDIAN_TO_HOST_INT64(object_id); } + uint8 Type() const { return type; } + uint64 Offset() const { return B_LENDIAN_TO_HOST_INT64(offset); } + void SetObjectID(uint64 id) { object_id = B_HOST_TO_LENDIAN_INT64(id); } + void SetType(uint8 key_type) { type = key_type; } + void SetOffset(uint64 off) { offset = B_HOST_TO_LENDIAN_INT64(off); } +} _PACKED; + +struct btrfs_timespec { + uint64 seconds; + uint32 nanoseconds; +} _PACKED; + +struct btrfs_header { + uint8 checksum[32]; + uint8 fsid[16]; + uint64 blocknum; + uint64 flags; + uint8 chunk_tree_uuid[16]; + uint64 generation; + uint64 owner; + uint32 item_count; + uint8 level; + uint64 BlockNum() const { return B_LENDIAN_TO_HOST_INT64(blocknum); } + uint64 Flags() const { return B_LENDIAN_TO_HOST_INT64(flags); } + uint64 Generation() const { + return B_LENDIAN_TO_HOST_INT64(generation); } + uint64 Owner() const { + return B_LENDIAN_TO_HOST_INT64(owner); } + uint32 ItemCount() const { + return B_LENDIAN_TO_HOST_INT32(item_count); } + uint8 Level() const { return level; } +} _PACKED; + +struct btrfs_index { + btrfs_key key; + uint64 blocknum; + uint64 generation; + uint64 BlockNum() const { return B_LENDIAN_TO_HOST_INT64(blocknum); } + uint64 Generation() const { + return B_LENDIAN_TO_HOST_INT64(generation); } +} _PACKED; + +struct btrfs_entry { + btrfs_key key; + uint32 offset; + uint32 size; + uint32 Offset() const { + return B_LENDIAN_TO_HOST_INT32(offset); } + uint32 Size() const { + return B_LENDIAN_TO_HOST_INT32(size); } +} _PACKED; + +struct btrfs_stream { + btrfs_header header; + union { + btrfs_entry entries[0]; + btrfs_index index[0]; + }; +} _PACKED; + +struct btrfs_stripe { + uint64 device_id; + uint64 offset; + uint8 device_uuid[16]; + uint64 DeviceID() { return B_LENDIAN_TO_HOST_INT64(device_id); } + uint64 Offset() { return B_LENDIAN_TO_HOST_INT64(offset); } +} _PACKED; + +struct btrfs_chunk { + uint64 length; + uint64 owner; + uint64 stripe_length; + uint64 type; + uint32 io_align; + uint32 io_width; + uint32 sector_size; + uint16 stripe_count; + uint16 sub_stripes; + struct btrfs_stripe stripes[0]; + uint64 Length() const { return B_LENDIAN_TO_HOST_INT64(length); } + uint64 Owner() const { return B_LENDIAN_TO_HOST_INT64(owner); } + uint64 StripeLength() const + { return B_LENDIAN_TO_HOST_INT64(stripe_length); } + uint64 Type() const { return B_LENDIAN_TO_HOST_INT64(type); } + uint32 IOAlign() const { return B_LENDIAN_TO_HOST_INT32(io_align); } + uint32 IOWidth() const { return B_LENDIAN_TO_HOST_INT32(io_width); } + uint32 SectorSize() const + { return B_LENDIAN_TO_HOST_INT32(sector_size); } + uint16 StripeCount() const + { return B_LENDIAN_TO_HOST_INT16(stripe_count); } + uint16 SubStripes() const + { return B_LENDIAN_TO_HOST_INT16(sub_stripes); } +} _PACKED; + +struct btrfs_device { + uint64 id; + uint64 total_size; + uint64 used_size; + uint32 io_align; + uint32 io_width; + uint32 sector_size; + uint64 type; + uint64 generation; + uint64 start_offset; + uint32 group; + uint8 seek_speed; + uint8 bandwidth; + uint8 uuid[16]; + uint8 fsid[16]; +} _PACKED; + + +struct btrfs_super_block { + uint8 checksum[32]; + uint8 fsid[16]; + uint64 blocknum; + uint64 flags; + char magic[8]; + uint64 generation; + uint64 root; + uint64 chunk_root; + uint64 log_root; + uint64 log_root_transaction_id; + uint64 total_size; + uint64 used_size; + uint64 root_dir_object_id; + uint64 num_devices; + uint32 sector_size; + uint32 node_size; + uint32 leaf_size; + uint32 stripe_size; + uint32 system_chunk_array_size; + uint64 chunk_root_generation; + uint64 compat_flags; + uint64 readonly_flags; + uint64 incompat_flags; + uint16 checksum_type; + uint8 root_level; + uint8 chunck_root_level; + uint8 log_root_level; + struct btrfs_device device; + char label[256]; + uint64 reserved[32]; + uint8 system_chunk_array[2048]; + + bool IsValid(); + // implemented in Volume.cpp + uint64 TotalSize() const { return B_LENDIAN_TO_HOST_INT64(total_size); } + uint32 BlockSize() const { return B_LENDIAN_TO_HOST_INT32(sector_size); } + uint64 RootDirObjectID() const { + return B_LENDIAN_TO_HOST_INT64(root_dir_object_id); } + uint64 Generation() const { + return B_LENDIAN_TO_HOST_INT64(generation); } + uint64 Root() const { + return B_LENDIAN_TO_HOST_INT64(root); } + uint64 ChunkRoot() const { + return B_LENDIAN_TO_HOST_INT64(chunk_root); } + uint64 LogRoot() const { + return B_LENDIAN_TO_HOST_INT64(log_root); } + uint8 ChunkRootLevel() { return chunck_root_level; } +} _PACKED; + +struct btrfs_inode { + uint64 generation; + uint64 transaction_id; + uint64 size; + uint64 nbytes; + uint64 blockgroup; + uint32 num_links; + uint32 uid; + uint32 gid; + uint32 mode; + uint64 rdev; + uint64 flags; + uint64 sequence; + uint64 reserved[4]; + struct btrfs_timespec access_time; + struct btrfs_timespec change_time; + struct btrfs_timespec modification_time; + struct btrfs_timespec creation_time; + uint64 Generation() const { return B_LENDIAN_TO_HOST_INT64(generation); } + uint64 Size() const { return B_LENDIAN_TO_HOST_INT64(size); } + uint32 UserID() const { return B_LENDIAN_TO_HOST_INT32(uid); } + uint32 GroupID() const { return B_LENDIAN_TO_HOST_INT32(gid); } + uint32 Mode() const { return B_LENDIAN_TO_HOST_INT32(mode); } + uint64 Flags() const { return B_LENDIAN_TO_HOST_INT64(flags); } + uint64 Sequence() const { return B_LENDIAN_TO_HOST_INT64(sequence); } + static void _DecodeTime(struct timespec ×pec, + const struct btrfs_timespec &time) + { + timespec.tv_sec = B_LENDIAN_TO_HOST_INT64(time.seconds); + timespec.tv_nsec = B_LENDIAN_TO_HOST_INT32(time.nanoseconds); + } + void GetAccessTime(struct timespec ×pec) const + { _DecodeTime(timespec, access_time); } + void GetChangeTime(struct timespec ×pec) const + { _DecodeTime(timespec, change_time); } + void GetModificationTime(struct timespec ×pec) const + { _DecodeTime(timespec, modification_time); } + void GetCreationTime(struct timespec ×pec) const + { _DecodeTime(timespec, creation_time); } +} _PACKED; + +struct btrfs_root { + btrfs_inode inode; + uint64 generation; + uint64 root_dirid; + uint64 blocknum; + uint64 limit_bytes; + uint64 used_bytes; + uint64 last_snapshot; + uint64 flags; + uint32 refs; + btrfs_key drop_progress; + uint8 drop_level; + uint8 level; + uint64 Generation() const { + return B_LENDIAN_TO_HOST_INT64(generation); } + uint64 BlockNum() const { return B_LENDIAN_TO_HOST_INT64(blocknum); } +} _PACKED; + +struct btrfs_dir_entry { + btrfs_key location; + uint64 transaction_id; + uint16 data_length; + uint16 name_length; + uint8 type; + uint16 DataLength() const { return B_LENDIAN_TO_HOST_INT16(data_length); } + uint16 NameLength() const { return B_LENDIAN_TO_HOST_INT16(name_length); } + ino_t InodeID() const { return location.ObjectID(); } + uint16 Length() const + { return sizeof(this) + NameLength() + DataLength(); } +} _PACKED; + +struct btrfs_extent_data { + uint64 generation; + uint64 memory_size; + uint8 compression; + uint8 encryption; + uint16 reserved; + uint8 type; + union { + struct { + uint64 disk_offset; + uint64 disk_size; + uint64 extent_offset; + uint64 size; + }; + uint8 inline_data[0]; + }; + uint64 Generation() const { + return B_LENDIAN_TO_HOST_INT64(generation); } + uint64 MemoryBytes() const { + return B_LENDIAN_TO_HOST_INT64(memory_size); } + uint8 Type() const { return type; } + uint64 DiskOffset() const { + return B_LENDIAN_TO_HOST_INT64(disk_offset); } + uint64 DiskSize() const { + return B_LENDIAN_TO_HOST_INT64(disk_size); } + uint64 ExtentOffset() const { + return B_LENDIAN_TO_HOST_INT64(extent_offset); } + uint64 Size() const { + return B_LENDIAN_TO_HOST_INT64(size); } +} _PACKED; + + +#define BTRFS_SUPER_BLOCK_MAGIC "_BHRfS_M" + +#define BTRFS_OBJECT_ID_ROOT_TREE 1 +#define BTRFS_OBJECT_ID_EXTENT_TREE 2 +#define BTRFS_OBJECT_ID_DEV_TREE 4 +#define BTRFS_OBJECT_ID_FS_TREE 5 +#define BTRFS_OBJECT_ID_ROOT_TREE_DIR 6 +#define BTRFS_OBJECT_ID_CHECKSUM_TREE 7 +#define BTRFS_OBJECT_ID_CHUNK_TREE 256 + +#define BTRFS_KEY_TYPE_CHUNK_ITEM 228 +#define BTRFS_KEY_TYPE_DIR_ITEM 84 +#define BTRFS_KEY_TYPE_DIR_INDEX 96 +#define BTRFS_KEY_TYPE_EXTENT_DATA 108 +#define BTRFS_KEY_TYPE_INODE_ITEM 1 +#define BTRFS_KEY_TYPE_INODE_REF 12 +#define BTRFS_KEY_TYPE_ROOT_ITEM 132 + +#define BTRFS_EXTENT_DATA_INLINE 0 +#define BTRFS_EXTENT_DATA_REGULAR 1 +#define BTRFS_EXTENT_DATA_PRE 2 + + +struct file_cookie { + bigtime_t last_notification; + off_t last_size; + int open_mode; +}; + +#define BTRFS_OPEN_MODE_USER_MASK 0x7fffffff + +extern fs_volume_ops gBtrfsVolumeOps; +extern fs_vnode_ops gBtrfsVnodeOps; + +#endif // BTRFS_H diff --git a/src/add-ons/kernel/file_systems/btrfs/crc_table.cpp b/src/add-ons/kernel/file_systems/btrfs/crc_table.cpp new file mode 100644 index 0000000000..8d959004ed --- /dev/null +++ b/src/add-ons/kernel/file_systems/btrfs/crc_table.cpp @@ -0,0 +1,68 @@ +//---------------------------------------------------------------------- +// This software is part of the OpenBeOS distribution and is covered +// by the OpenBeOS license. +// +// Copyright (c) 2003 Tyler Dauwalder, tyler@dauwalder.net +//--------------------------------------------------------------------- + +/*! \file crc_table.cpp + + Standalone program to generate the CRC table used for calculating + UDF tag id CRC values. + + This code based off of crc code in UDF-2.50 specs, as permitted. + See UDF-2.50 6.5 for more information. + + Reflected version by Jéme Duval +*/ + +#include +#include + +typedef unsigned int uint32 ; + +uint32 +reflect32 (uint32 b) +{ + uint32 rw = 0; + + for (int i = 0; i < 32; i++){ + if (b & 1) + rw |= 1 << (31 - i); + b >>= 1; + } + return rw; +} + + +int +main(int argc, char *argv[]) { + uint32 crc, poly; + + if (argc != 2) { + fprintf(stderr, "USAGE: crc_table \n"); + return 0; + } + + sscanf(argv[1], "%lo", &poly); + + printf("//! CRC 0%o table, as generated by crc_table.cpp\n", poly); + printf("static uint32 crc_table[256] = { \n"); + for (int n = 0; n < 256; n++) { + if (n%8 == 0) + printf(" "); + crc = reflect32(n); + for (int i = 0; i < 8; i++) { + if (crc & 0x80000000) + crc = (crc << 1) ^ poly; + else + crc <<= 1; + } + crc = reflect32(crc); + printf("0x%08x%s ", crc, (n != 255 ? "," : "")); + if (n%8 == 7) + printf("\n"); + } + printf("};\n"); + return 0; +} diff --git a/src/add-ons/kernel/file_systems/btrfs/kernel_interface.cpp b/src/add-ons/kernel/file_systems/btrfs/kernel_interface.cpp new file mode 100644 index 0000000000..c90ce29769 --- /dev/null +++ b/src/add-ons/kernel/file_systems/btrfs/kernel_interface.cpp @@ -0,0 +1,674 @@ +/* + * Copyright 2011, Jérôme Duval, korli@users.berlios.de. + * Copyright 2008, Axel Dörfler, axeld@pinc-software.de. + * This file may be used under the terms of the MIT License. + */ + + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "btrfs.h" +#include "DirectoryIterator.h" +#include "Inode.h" +#include "Utility.h" + + +//#define TRACE_BTRFS +#ifdef TRACE_BTRFS +# define TRACE(x...) dprintf("\33[34mbtrfs:\33[0m " x) +#else +# define TRACE(x...) ; +#endif +#define ERROR(x...) dprintf("\33[34mbtrfs:\33[0m " x) + + +#define BTRFS_IO_SIZE 65536 + + +struct identify_cookie { + btrfs_super_block super_block; +}; + + +//! btrfs_io() callback hook +static status_t +iterative_io_get_vecs_hook(void* cookie, io_request* request, off_t offset, + size_t size, struct file_io_vec* vecs, size_t* _count) +{ + Inode* inode = (Inode*)cookie; + + return file_map_translate(inode->Map(), offset, size, vecs, _count, + inode->GetVolume()->BlockSize()); +} + + +//! btrfs_io() callback hook +static status_t +iterative_io_finished_hook(void* cookie, io_request* request, status_t status, + bool partialTransfer, size_t bytesTransferred) +{ + Inode* inode = (Inode*)cookie; + rw_lock_read_unlock(inode->Lock()); + return B_OK; +} + + +// #pragma mark - Scanning + + +static float +btrfs_identify_partition(int fd, partition_data *partition, void **_cookie) +{ + btrfs_super_block superBlock; + status_t status = Volume::Identify(fd, &superBlock); + if (status != B_OK) + return -1; + + identify_cookie *cookie = new identify_cookie; + memcpy(&cookie->super_block, &superBlock, sizeof(btrfs_super_block)); + + *_cookie = cookie; + return 0.8f; +} + + +static status_t +btrfs_scan_partition(int fd, partition_data *partition, void *_cookie) +{ + identify_cookie *cookie = (identify_cookie *)_cookie; + + partition->status = B_PARTITION_VALID; + partition->flags |= B_PARTITION_FILE_SYSTEM; + partition->content_size = cookie->super_block.TotalSize(); + partition->block_size = cookie->super_block.BlockSize(); + partition->content_name = strdup(cookie->super_block.label); + if (partition->content_name == NULL) + return B_NO_MEMORY; + + return B_OK; +} + + +static void +btrfs_free_identify_partition_cookie(partition_data* partition, void* _cookie) +{ + delete (identify_cookie*)_cookie; +} + + +// #pragma mark - + + +static status_t +btrfs_mount(fs_volume* _volume, const char* device, uint32 flags, + const char* args, ino_t* _rootID) +{ + Volume* volume = new(std::nothrow) Volume(_volume); + if (volume == NULL) + return B_NO_MEMORY; + + // TODO: this is a bit hacky: we can't use publish_vnode() to publish + // the root node, or else its file cache cannot be created (we could + // create it later, though). Therefore we're using get_vnode() in Mount(), + // but that requires us to export our volume data before calling it. + _volume->private_volume = volume; + _volume->ops = &gBtrfsVolumeOps; + + status_t status = volume->Mount(device, flags); + if (status != B_OK) { + ERROR("Failed mounting the volume. Error: %s\n", strerror(status)); + delete volume; + return status; + } + + *_rootID = volume->RootNode()->ID(); + return B_OK; +} + + +static status_t +btrfs_unmount(fs_volume *_volume) +{ + Volume* volume = (Volume *)_volume->private_volume; + + status_t status = volume->Unmount(); + delete volume; + + return status; +} + + +static status_t +btrfs_read_fs_info(fs_volume* _volume, struct fs_info* info) +{ + Volume* volume = (Volume*)_volume->private_volume; + + // File system flags + info->flags = B_FS_IS_PERSISTENT + | (volume->IsReadOnly() ? B_FS_IS_READONLY : 0); + info->io_size = BTRFS_IO_SIZE; + info->block_size = volume->BlockSize(); + info->total_blocks = volume->SuperBlock().TotalSize() / volume->BlockSize(); + info->free_blocks = 0; //volume->NumFreeBlocks(); + + // Volume name + strlcpy(info->volume_name, volume->Name(), sizeof(info->volume_name)); + + // File system name + strlcpy(info->fsh_name, "btrfs", sizeof(info->fsh_name)); + + return B_OK; +} + + +// #pragma mark - + + +static status_t +btrfs_get_vnode(fs_volume* _volume, ino_t id, fs_vnode* _node, int* _type, + uint32* _flags, bool reenter) +{ + Volume* volume = (Volume*)_volume->private_volume; + + Inode* inode = new(std::nothrow) Inode(volume, id); + if (inode == NULL) + return B_NO_MEMORY; + + status_t status = inode->InitCheck(); + if (status != B_OK) + delete inode; + + if (status == B_OK) { + _node->private_node = inode; + _node->ops = &gBtrfsVnodeOps; + *_type = inode->Mode(); + *_flags = 0; + } else + ERROR("get_vnode: InitCheck() failed. Error: %s\n", strerror(status)); + + return status; +} + + +static status_t +btrfs_put_vnode(fs_volume* _volume, fs_vnode* _node, bool reenter) +{ + delete (Inode*)_node->private_node; + return B_OK; +} + + +static bool +btrfs_can_page(fs_volume* _volume, fs_vnode* _node, void* _cookie) +{ + return true; +} + + +static status_t +btrfs_read_pages(fs_volume* _volume, fs_vnode* _node, void* _cookie, + off_t pos, const iovec* vecs, size_t count, size_t* _numBytes) +{ + Volume* volume = (Volume*)_volume->private_volume; + Inode* inode = (Inode*)_node->private_node; + + if (inode->FileCache() == NULL) + return B_BAD_VALUE; + + rw_lock_read_lock(inode->Lock()); + + uint32 vecIndex = 0; + size_t vecOffset = 0; + size_t bytesLeft = *_numBytes; + status_t status; + + while (true) { + file_io_vec fileVecs[8]; + uint32 fileVecCount = 8; + + status = file_map_translate(inode->Map(), pos, bytesLeft, fileVecs, + &fileVecCount, 0); + if (status != B_OK && status != B_BUFFER_OVERFLOW) + break; + + bool bufferOverflow = status == B_BUFFER_OVERFLOW; + + size_t bytes = bytesLeft; + status = read_file_io_vec_pages(volume->Device(), fileVecs, + fileVecCount, vecs, count, &vecIndex, &vecOffset, &bytes); + if (status != B_OK || !bufferOverflow) + break; + + pos += bytes; + bytesLeft -= bytes; + } + + rw_lock_read_unlock(inode->Lock()); + + return status; +} + + +static status_t +btrfs_io(fs_volume* _volume, fs_vnode* _node, void* _cookie, io_request* request) +{ + Volume* volume = (Volume*)_volume->private_volume; + Inode* inode = (Inode*)_node->private_node; + +#ifndef BTRFS_SHELL + if (io_request_is_write(request) && volume->IsReadOnly()) { + notify_io_request(request, B_READ_ONLY_DEVICE); + return B_READ_ONLY_DEVICE; + } +#endif + + if (inode->FileCache() == NULL) { +#ifndef BTRFS_SHELL + notify_io_request(request, B_BAD_VALUE); +#endif + return B_BAD_VALUE; + } + + // We lock the node here and will unlock it in the "finished" hook. + rw_lock_read_lock(inode->Lock()); + + return do_iterative_fd_io(volume->Device(), request, + iterative_io_get_vecs_hook, iterative_io_finished_hook, inode); +} + + +static status_t +btrfs_get_file_map(fs_volume* _volume, fs_vnode* _node, off_t offset, + size_t size, struct file_io_vec* vecs, size_t* _count) +{ + TRACE("btrfs_get_file_map()\n"); + Volume* volume = (Volume*)_volume->private_volume; + Inode* inode = (Inode*)_node->private_node; + size_t index = 0, max = *_count; + + while (true) { + off_t blockOffset; + off_t blockLength; + status_t status = inode->FindBlock(offset, blockOffset, &blockLength); + if (status != B_OK) + return status; + + if (index > 0 && (vecs[index - 1].offset + == blockOffset - vecs[index - 1].length)) { + vecs[index - 1].length += blockLength; + } else { + if (index >= max) { + // we're out of file_io_vecs; let's bail out + *_count = index; + return B_BUFFER_OVERFLOW; + } + + vecs[index].offset = blockOffset; + vecs[index].length = blockLength; + index++; + } + + offset += blockLength; + size -= blockLength; + + if (size <= vecs[index - 1].length || offset >= inode->Size()) { + // We're done! + *_count = index; + TRACE("btrfs_get_file_map for inode %lld\n", inode->ID()); + return B_OK; + } + } + + // can never get here + return B_ERROR; +} + + +// #pragma mark - + + +static status_t +btrfs_lookup(fs_volume* _volume, fs_vnode* _directory, const char* name, + ino_t* _vnodeID) +{ + TRACE("btrfs_lookup: name address: %p (%s)\n", name, name); + Volume* volume = (Volume*)_volume->private_volume; + Inode* directory = (Inode*)_directory->private_node; + + // check access permissions + status_t status = directory->CheckPermissions(X_OK); + if (status < B_OK) + return status; + + status = DirectoryIterator(directory).Lookup(name, strlen(name), _vnodeID); + if (status != B_OK) + return status; + + return get_vnode(volume->FSVolume(), *_vnodeID, NULL); +} + + +static status_t +btrfs_ioctl(fs_volume* _volume, fs_vnode* _node, void* _cookie, uint32 cmd, + void* buffer, size_t bufferLength) +{ + TRACE("ioctl: %lu\n", cmd); + + /*Volume* volume = (Volume*)_volume->private_volume;*/ + return B_OK; +} + + +static status_t +btrfs_read_stat(fs_volume* _volume, fs_vnode* _node, struct stat* stat) +{ + Inode* inode = (Inode*)_node->private_node; + + stat->st_dev = inode->GetVolume()->ID(); + stat->st_ino = inode->ID(); + stat->st_nlink = 1; + stat->st_blksize = BTRFS_IO_SIZE; + + stat->st_uid = inode->UserID(); + stat->st_gid = inode->GroupID(); + stat->st_mode = inode->Mode(); + stat->st_type = 0; + + inode->GetAccessTime(stat->st_atim); + inode->GetModificationTime(stat->st_mtim); + inode->GetChangeTime(stat->st_ctim); + inode->GetCreationTime(stat->st_crtim); + + stat->st_size = inode->Size(); + stat->st_blocks = (inode->Size() + 511) / 512; + + return B_OK; +} + + +static status_t +btrfs_open(fs_volume* /*_volume*/, fs_vnode* _node, int openMode, + void** _cookie) +{ + Inode* inode = (Inode*)_node->private_node; + + // opening a directory read-only is allowed, although you can't read + // any data from it. + if (inode->IsDirectory() && (openMode & O_RWMASK) != 0) + return B_IS_A_DIRECTORY; + + status_t status = inode->CheckPermissions(open_mode_to_access(openMode) + | (openMode & O_TRUNC ? W_OK : 0)); + if (status != B_OK) + return status; + + // Prepare the cookie + file_cookie* cookie = new(std::nothrow) file_cookie; + if (cookie == NULL) + return B_NO_MEMORY; + ObjectDeleter cookieDeleter(cookie); + + cookie->open_mode = openMode & BTRFS_OPEN_MODE_USER_MASK; + cookie->last_size = inode->Size(); + cookie->last_notification = system_time(); + + if ((openMode & O_NOCACHE) != 0 && inode->FileCache() != NULL) { + // Disable the file cache, if requested? + status = file_cache_disable(inode->FileCache()); + if (status != B_OK) + return status; + } + + cookieDeleter.Detach(); + *_cookie = cookie; + + return B_OK; +} + + +static status_t +btrfs_read(fs_volume* _volume, fs_vnode* _node, void* _cookie, off_t pos, + void* buffer, size_t* _length) +{ + Inode* inode = (Inode*)_node->private_node; + + if (!inode->IsFile()) { + *_length = 0; + return inode->IsDirectory() ? B_IS_A_DIRECTORY : B_BAD_VALUE; + } + + return inode->ReadAt(pos, (uint8*)buffer, _length); +} + + +static status_t +btrfs_close(fs_volume *_volume, fs_vnode *_node, void *_cookie) +{ + return B_OK; +} + + +static status_t +btrfs_free_cookie(fs_volume* _volume, fs_vnode* _node, void* _cookie) +{ + file_cookie* cookie = (file_cookie*)_cookie; + Volume* volume = (Volume*)_volume->private_volume; + Inode* inode = (Inode*)_node->private_node; + + if (inode->Size() != cookie->last_size) + notify_stat_changed(volume->ID(), inode->ID(), B_STAT_SIZE); + + delete cookie; + return B_OK; +} + + +static status_t +btrfs_access(fs_volume* _volume, fs_vnode* _node, int accessMode) +{ + Inode* inode = (Inode*)_node->private_node; + return inode->CheckPermissions(accessMode); +} + + +static status_t +btrfs_read_link(fs_volume *_volume, fs_vnode *_node, char *buffer, + size_t *_bufferSize) +{ + Inode* inode = (Inode*)_node->private_node; + return inode->ReadAt(0, (uint8*)buffer, _bufferSize); +} + + +// #pragma mark - Directory functions + + +static status_t +btrfs_open_dir(fs_volume* /*_volume*/, fs_vnode* _node, void** _cookie) +{ + Inode* inode = (Inode*)_node->private_node; + status_t status = inode->CheckPermissions(R_OK); + if (status < B_OK) + return status; + + if (!inode->IsDirectory()) + return B_NOT_A_DIRECTORY; + + DirectoryIterator* iterator = new(std::nothrow) DirectoryIterator(inode); + if (iterator == NULL || iterator->InitCheck() != B_OK) { + delete iterator; + return B_NO_MEMORY; + } + + *_cookie = iterator; + return B_OK; +} + + +static status_t +btrfs_read_dir(fs_volume *_volume, fs_vnode *_node, void *_cookie, + struct dirent *dirent, size_t bufferSize, uint32 *_num) +{ + DirectoryIterator* iterator = (DirectoryIterator*)_cookie; + + size_t length = bufferSize; + ino_t id; + status_t status = iterator->GetNext(dirent->d_name, &length, &id); + if (status == B_ENTRY_NOT_FOUND) { + *_num = 0; + return B_OK; + } else if (status != B_OK) + return status; + + Volume* volume = (Volume*)_volume->private_volume; + dirent->d_dev = volume->ID(); + dirent->d_ino = id; + dirent->d_reclen = sizeof(struct dirent) + length; + *_num = 1; + + return B_OK; +} + + +static status_t +btrfs_rewind_dir(fs_volume * /*_volume*/, fs_vnode * /*node*/, void *_cookie) +{ + DirectoryIterator* iterator = (DirectoryIterator*)_cookie; + + return iterator->Rewind(); +} + + +static status_t +btrfs_close_dir(fs_volume * /*_volume*/, fs_vnode * /*node*/, void * /*_cookie*/) +{ + return B_OK; +} + + +static status_t +btrfs_free_dir_cookie(fs_volume *_volume, fs_vnode *_node, void *_cookie) +{ + delete (DirectoryIterator*)_cookie; + return B_OK; +} + + +fs_volume_ops gBtrfsVolumeOps = { + &btrfs_unmount, + &btrfs_read_fs_info, + NULL, // write_fs_info() + NULL, // fs_sync, + &btrfs_get_vnode, +}; + + +fs_vnode_ops gBtrfsVnodeOps = { + /* vnode operations */ + &btrfs_lookup, + NULL, + &btrfs_put_vnode, + NULL, // btrfs_remove_vnode, + + /* VM file access */ + &btrfs_can_page, + &btrfs_read_pages, + NULL, // btrfs_write_pages, + + NULL, // io() + NULL, // cancel_io() + + &btrfs_get_file_map, + + &btrfs_ioctl, + NULL, + NULL, // fs_select + NULL, // fs_deselect + NULL, // fs_fsync, + + &btrfs_read_link, + NULL, // fs_create_symlink, + + NULL, // fs_link, + NULL, // fs_unlink, + NULL, // fs_rename, + + &btrfs_access, + &btrfs_read_stat, + NULL, // fs_write_stat, + NULL, // fs_preallocate + + /* file operations */ + NULL, // fs_create, + &btrfs_open, + &btrfs_close, + &btrfs_free_cookie, + &btrfs_read, + NULL, // fs_write, + + /* directory operations */ + NULL, // fs_create_dir, + NULL, // fs_remove_dir, + &btrfs_open_dir, + &btrfs_close_dir, + &btrfs_free_dir_cookie, + &btrfs_read_dir, + &btrfs_rewind_dir, + + /* attribute directory operations */ + NULL, // fs_open_attr_dir, + NULL, // fs_close_attr_dir, + NULL, // fs_free_attr_dir_cookie, + NULL, // fs_read_attr_dir, + NULL, // fs_rewind_attr_dir, + + /* attribute operations */ + NULL, // fs_create_attr, + NULL, // fs_open_attr, + NULL, // fs_close_attr, + NULL, // fs_free_attr_cookie, + NULL, // fs_read_attr, + NULL, // fs_write_attr, + NULL, // fs_read_attr_stat, + NULL, // fs_write_attr_stat, + NULL, // fs_rename_attr, + NULL, // fs_remove_attr, +}; + + +static file_system_module_info sBtrfsFileSystem = { + { + "file_systems/btrfs" B_CURRENT_FS_API_VERSION, + 0, + NULL, + }, + + "btrfs", // short_name + "btrfs File System", // pretty_name + 0, // DDM flags + + // scanning + btrfs_identify_partition, + btrfs_scan_partition, + btrfs_free_identify_partition_cookie, + NULL, // free_partition_content_cookie() + + &btrfs_mount, + + NULL, +}; + + +module_info *modules[] = { + (module_info *)&sBtrfsFileSystem, + NULL, +};