From 8794d8c48f3c595cf42a9ae36fe661e38c019f34 Mon Sep 17 00:00:00 2001 From: Ingo Weinhold Date: Tue, 20 Jul 2010 17:13:34 +0000 Subject: [PATCH] * Volume::Create{File,Directory,SymLink}(): - Simplified by using the new common _CreateNode(). Changed the constructors slightly -- the block index is now set via a separate SetBlockIndex() method. - The block allocated for the node was not cleared. At least for directories that is required, though. * Added attribute support: - Refactored/extended {open,create}_file() and DirCookie a bit, so they can also be used for attributes/attribute directories. - Implemented all relevant attribute FS hooks. - Node::DeletingNode(): Changed interface (it creates the transaction(s) as needed, now and doesn't return the error code anymore. The base class implementation frees the attribute directory, the Directory implementation also unlinks all entries and frees the entry tree. * checksumfs_write_stat(): B_STAT_MODE was not handled. git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@37626 a95241bf-73f2-0310-859d-f6bbb57e9c96 --- .../kernel/file_corruption/fs/Directory.cpp | 154 ++- .../kernel/file_corruption/fs/Directory.h | 8 +- .../system/kernel/file_corruption/fs/File.cpp | 32 +- .../system/kernel/file_corruption/fs/File.h | 5 +- .../system/kernel/file_corruption/fs/Node.cpp | 55 +- .../system/kernel/file_corruption/fs/Node.h | 26 +- .../kernel/file_corruption/fs/SymLink.cpp | 4 +- .../kernel/file_corruption/fs/SymLink.h | 3 +- .../kernel/file_corruption/fs/Volume.cpp | 134 ++- .../system/kernel/file_corruption/fs/Volume.h | 5 + .../kernel/file_corruption/fs/checksumfs.cpp | 1065 +++++++++++++---- 11 files changed, 1178 insertions(+), 313 deletions(-) diff --git a/src/tests/system/kernel/file_corruption/fs/Directory.cpp b/src/tests/system/kernel/file_corruption/fs/Directory.cpp index f7049b7377..73272cb9f6 100644 --- a/src/tests/system/kernel/file_corruption/fs/Directory.cpp +++ b/src/tests/system/kernel/file_corruption/fs/Directory.cpp @@ -72,6 +72,10 @@ public: status_t RemoveEntry(const char* name, Transaction& transaction); + status_t FreeTree(Transaction& transaction); + + bool IsEmpty() const; + bool Check(); private: @@ -975,6 +979,71 @@ ASSERT(nextInfo.entryBlock.Check()); } +status_t +DirEntryTree::FreeTree(Transaction& transaction) +{ + status_t error = _InitReadOnly(); + if (error != B_OK) + RETURN_ERROR(error); + + int32 depth = _Depth(); + if (depth == 0) + return B_OK; + + LevelInfo* infos = new(std::nothrow) LevelInfo[ + kCheckSumFSMaxDirEntryTreeDepth + 1]; + if (infos == NULL) + RETURN_ERROR(B_NO_MEMORY); + ArrayDeleter infosDeleter(infos); + + infos[0].entryBlock.SetTo(fRootEntryBlock, fRootEntryBlockSize); + infos[0].index = 0; + + // Iterate through the tree in post order. We don't touch the content of + // any block, we only free the blocks. + int32 level = 0; + while (true) { + LevelInfo& info = infos[level]; + + if (level == depth || info.index >= info.entryBlock.EntryCount()) { + // we're through with the block + if (level == 0) + break; + + // free it + error = fDirectory->GetVolume()->GetBlockAllocator()->Free( + info.block.Index(), 1, transaction); + + // continue with the next sibling branch + infos[--level].index++; + } + + // descend to next level + uint64 nextBlockIndex = info.entryBlock.BlockIndexAt(info.index); + + LevelInfo& nextInfo = infos[++level]; + if (!nextInfo.block.GetReadable(fDirectory->GetVolume(), + nextBlockIndex)) { + RETURN_ERROR(B_ERROR); + } + + nextInfo.entryBlock.SetTo( + (checksumfs_dir_entry_block*)nextInfo.block.Data(), + B_PAGE_SIZE); + } + + return B_OK; +} + + +bool +DirEntryTree::IsEmpty() const +{ + DirEntryBlock entryBlock(fRootEntryBlock, fRootEntryBlockSize); + return entryBlock.EntryCount() == 0; +} + + bool DirEntryTree::Check() { @@ -1442,9 +1511,9 @@ Directory::Directory(Volume* volume, uint64 blockIndex, } -Directory::Directory(Volume* volume, uint64 blockIndex, mode_t mode) +Directory::Directory(Volume* volume, mode_t mode) : - Node(volume, blockIndex, mode) + Node(volume, mode) { } @@ -1454,6 +1523,72 @@ Directory::~Directory() } +void +Directory::DeletingNode() +{ + Node::DeletingNode(); + + // iterate through the directory and remove references to all entries' nodes + char* name = (char*)malloc(kCheckSumFSNameLength + 1); + if (name != NULL) { + name[0] = '\0'; + + DirEntryTree entryTree(this); + size_t nameLength; + uint64 blockIndex; + while (entryTree.LookupNextEntry(name, name, nameLength, + blockIndex) == B_OK) { + Node* node; + if (GetVolume()->GetNode(blockIndex, node) == B_OK) { + Transaction transaction(GetVolume()); + if (transaction.StartAndAddNode(node) == B_OK) { + node->SetHardLinks(node->HardLinks() - 1); + if (node->HardLinks() == 0) + GetVolume()->RemoveNode(node); + + if (transaction.Commit() != B_OK) { + ERROR("Failed to commit transaction for dereferencing " + "entry node of deleted directory at %" B_PRIu64 + "\n", BlockIndex()); + } + } else { + ERROR("Failed to start transaction for dereferencing " + "entry node of deleted directory at %" B_PRIu64 "\n", + BlockIndex()); + } + + GetVolume()->PutNode(node); + } else { + ERROR("Failed to get node %" B_PRIu64 " referenced by deleted " + "directory at %" B_PRIu64 "\n", blockIndex, BlockIndex()); + } + } + + free(name); + } + + // free the directory entry block tree + Transaction transaction(GetVolume()); + if (transaction.Start() != B_OK) { + ERROR("Failed to start transaction for freeing entry tree of deleted " + "directory at %" B_PRIu64 "\n", BlockIndex()); + return; + } + + DirEntryTree entryTree(this); + if (entryTree.FreeTree(transaction) != B_OK) { + ERROR("Failed to freeing entry tree of deleted directory at %" B_PRIu64 + "\n", BlockIndex()); + return; + } + + if (transaction.Commit() != B_OK) { + ERROR("Failed to commit transaction for freeing entry tree of deleted " + "directory at %" B_PRIu64 "\n", BlockIndex()); + } +} + + status_t Directory::LookupEntry(const char* name, uint64& _blockIndex) { @@ -1486,12 +1621,19 @@ Directory::InsertEntry(const char* name, uint64 blockIndex, status_t -Directory::RemoveEntry(const char* name, Transaction& transaction) +Directory::RemoveEntry(const char* name, Transaction& transaction, + bool* _lastEntryRemoved) { DirEntryTree entryTree(this); status_t error = entryTree.RemoveEntry(name, transaction); - if (error == B_OK) - ASSERT(entryTree.Check()); - return error; + if (error != B_OK) + return error; + + ASSERT(entryTree.Check()); + + if (_lastEntryRemoved != NULL) + *_lastEntryRemoved = entryTree.IsEmpty(); + + return B_OK; } diff --git a/src/tests/system/kernel/file_corruption/fs/Directory.h b/src/tests/system/kernel/file_corruption/fs/Directory.h index 4d7547d3ef..2b5d1b27d9 100644 --- a/src/tests/system/kernel/file_corruption/fs/Directory.h +++ b/src/tests/system/kernel/file_corruption/fs/Directory.h @@ -13,10 +13,11 @@ class Directory : public Node { public: Directory(Volume* volume, uint64 blockIndex, const checksumfs_node& nodeData); - Directory(Volume* volume, uint64 blockIndex, - mode_t mode); + Directory(Volume* volume, mode_t mode); virtual ~Directory(); + virtual void DeletingNode(); + status_t LookupEntry(const char* name, uint64& _blockIndex); status_t LookupNextEntry(const char* name, @@ -26,7 +27,8 @@ public: status_t InsertEntry(const char* name, uint64 blockIndex, Transaction& transaction); status_t RemoveEntry(const char* name, - Transaction& transaction); + Transaction& transaction, + bool* _lastEntryRemoved = NULL); }; diff --git a/src/tests/system/kernel/file_corruption/fs/File.cpp b/src/tests/system/kernel/file_corruption/fs/File.cpp index 3957a0ab21..a4742e779c 100644 --- a/src/tests/system/kernel/file_corruption/fs/File.cpp +++ b/src/tests/system/kernel/file_corruption/fs/File.cpp @@ -54,9 +54,9 @@ File::File(Volume* volume, uint64 blockIndex, const checksumfs_node& nodeData) } -File::File(Volume* volume, uint64 blockIndex, mode_t mode) +File::File(Volume* volume, mode_t mode) : - Node(volume, blockIndex, mode), + Node(volume, mode), fFileCache(NULL), fFileMap(NULL) { @@ -89,10 +89,32 @@ File::InitForVFS() } -status_t -File::DeletingNode(Transaction& transaction) +void +File::DeletingNode() { - return Resize(0, false, transaction); + Node::DeletingNode(); + + // start a transaction + Transaction transaction(GetVolume()); + status_t error = transaction.Start(); + if (error != B_OK) { + ERROR("Failed to start transaction for deleting contents of file at %" + B_PRIu64 "\n", BlockIndex()); + return; + } + + error = Resize(0, false, transaction); + if (error != B_OK) { + ERROR("Failed to delete contents of file at %" B_PRIu64 "\n", + BlockIndex()); + return; + } + + error = transaction.Commit(); + if (error != B_OK) { + ERROR("Failed to commit transaction for deleting contents of file at %" + B_PRIu64 "\n", BlockIndex()); + } } diff --git a/src/tests/system/kernel/file_corruption/fs/File.h b/src/tests/system/kernel/file_corruption/fs/File.h index 189466036c..be10794460 100644 --- a/src/tests/system/kernel/file_corruption/fs/File.h +++ b/src/tests/system/kernel/file_corruption/fs/File.h @@ -16,12 +16,11 @@ class File : public Node { public: File(Volume* volume, uint64 blockIndex, const checksumfs_node& nodeData); - File(Volume* volume, uint64 blockIndex, - mode_t mode); + File(Volume* volume, mode_t mode); virtual ~File(); virtual status_t InitForVFS(); - virtual status_t DeletingNode(Transaction& transaction); + virtual void DeletingNode(); virtual status_t Resize(uint64 newSize, bool fillWithZeroes, Transaction& transaction); diff --git a/src/tests/system/kernel/file_corruption/fs/Node.cpp b/src/tests/system/kernel/file_corruption/fs/Node.cpp index 224bc3e562..167c775b56 100644 --- a/src/tests/system/kernel/file_corruption/fs/Node.cpp +++ b/src/tests/system/kernel/file_corruption/fs/Node.cpp @@ -37,10 +37,10 @@ Node::Node(Volume* volume, uint64 blockIndex, const checksumfs_node& nodeData) } -Node::Node(Volume* volume, uint64 blockIndex, mode_t mode) +Node::Node(Volume* volume, mode_t mode) : fVolume(volume), - fBlockIndex(blockIndex), + fBlockIndex(0), fNodeDataDirty(true) { _Init(); @@ -70,6 +70,13 @@ Node::~Node() } +void +Node::SetBlockIndex(uint64 blockIndex) +{ + fBlockIndex = blockIndex; +} + + status_t Node::InitForVFS() { @@ -77,10 +84,22 @@ Node::InitForVFS() } -status_t -Node::DeletingNode(Transaction& transaction) +void +Node::DeletingNode() { - return B_OK; + // delete the node's attribute directory + if (AttributeDirectory() == 0) + return; + + Node* attributeDirectory; + if (fVolume->GetNode(AttributeDirectory(), attributeDirectory) == B_OK) { + fVolume->RemoveNode(attributeDirectory); + fVolume->PutNode(attributeDirectory); + } else { + ERROR("Failed to get attribute directory (at %" B_PRIu64 ") for " + "deleted node at %" B_PRIu64 "\n", AttributeDirectory(), + BlockIndex()); + } } @@ -113,6 +132,24 @@ Node::Sync() } +void +Node::SetMode(uint32 mode) +{ + ASSERT((mode & S_IFMT) == (Mode() & S_IFMT)); + + fNode.mode = mode; + fNodeDataDirty = true; +} + + +void +Node::SetAttributeType(uint32 type) +{ + fNode.attributeType = type; + fNodeDataDirty = true; +} + + void Node::SetParentDirectory(uint32 blockIndex) { @@ -121,6 +158,14 @@ Node::SetParentDirectory(uint32 blockIndex) } +void +Node::SetAttributeDirectory(uint32 blockIndex) +{ + fNode.attributeDirectory = blockIndex; + fNodeDataDirty = true; +} + + void Node::SetHardLinks(uint32 value) { diff --git a/src/tests/system/kernel/file_corruption/fs/Node.h b/src/tests/system/kernel/file_corruption/fs/Node.h index 0b914b0fd9..21ffdbe0ac 100644 --- a/src/tests/system/kernel/file_corruption/fs/Node.h +++ b/src/tests/system/kernel/file_corruption/fs/Node.h @@ -30,12 +30,13 @@ class Node { public: Node(Volume* volume, uint64 blockIndex, const checksumfs_node& nodeData); - Node(Volume* volume, uint64 blockIndex, - mode_t mode); + Node(Volume* volume, mode_t mode); virtual ~Node(); + void SetBlockIndex(uint64 blockIndex); + virtual status_t InitForVFS(); - virtual status_t DeletingNode(Transaction& transaction); + virtual void DeletingNode(); virtual status_t Resize(uint64 newSize, bool fillWithZeroes, Transaction& transaction); @@ -50,7 +51,9 @@ public: inline Volume* GetVolume() const { return fVolume; } inline uint64 BlockIndex() const { return fBlockIndex; } inline uint32 Mode() const { return fNode.mode; } + inline uint32 AttributeType() const; inline uint64 ParentDirectory() const; + inline uint64 AttributeDirectory() const; inline uint32 HardLinks() const { return fNode.hardLinks; } inline uint32 UID() const { return fNode.uid; } inline uint32 GID() const { return fNode.gid; } @@ -60,7 +63,10 @@ public: inline uint64 ModificationTime() const; inline uint64 ChangeTime() const; + void SetMode(uint32 mode); + void SetAttributeType(uint32 type); void SetParentDirectory(uint32 blockIndex); + void SetAttributeDirectory(uint32 blockIndex); void SetHardLinks(uint32 value); void SetUID(uint32 uid); void SetGID(uint32 gid); @@ -94,6 +100,13 @@ private: }; +uint32 +Node::AttributeType() const +{ + return fNode.attributeType; +} + + uint64 Node::ParentDirectory() const { @@ -101,6 +114,13 @@ Node::ParentDirectory() const } +uint64 +Node::AttributeDirectory() const +{ + return fNode.attributeDirectory; +} + + uint64 Node::CreationTime() const { diff --git a/src/tests/system/kernel/file_corruption/fs/SymLink.cpp b/src/tests/system/kernel/file_corruption/fs/SymLink.cpp index 04edd92633..49f224137d 100644 --- a/src/tests/system/kernel/file_corruption/fs/SymLink.cpp +++ b/src/tests/system/kernel/file_corruption/fs/SymLink.cpp @@ -24,9 +24,9 @@ SymLink::SymLink(Volume* volume, uint64 blockIndex, } -SymLink::SymLink(Volume* volume, uint64 blockIndex, mode_t mode) +SymLink::SymLink(Volume* volume, mode_t mode) : - Node(volume, blockIndex, mode) + Node(volume, mode) { } diff --git a/src/tests/system/kernel/file_corruption/fs/SymLink.h b/src/tests/system/kernel/file_corruption/fs/SymLink.h index 2c53f8731b..0c5fbce6d5 100644 --- a/src/tests/system/kernel/file_corruption/fs/SymLink.h +++ b/src/tests/system/kernel/file_corruption/fs/SymLink.h @@ -13,8 +13,7 @@ class SymLink : public Node { public: SymLink(Volume* volume, uint64 blockIndex, const checksumfs_node& nodeData); - SymLink(Volume* volume, uint64 blockIndex, - mode_t mode); + SymLink(Volume* volume, mode_t mode); virtual ~SymLink(); status_t ReadSymLink(char* buffer, size_t toRead, diff --git a/src/tests/system/kernel/file_corruption/fs/Volume.cpp b/src/tests/system/kernel/file_corruption/fs/Volume.cpp index 8886207974..6ab3620e69 100644 --- a/src/tests/system/kernel/file_corruption/fs/Volume.cpp +++ b/src/tests/system/kernel/file_corruption/fs/Volume.cpp @@ -35,7 +35,7 @@ Volume::Volume(uint32 flags) : fFSVolume(NULL), fFD(-1), - fFlags(B_FS_IS_PERSISTENT + fFlags(B_FS_IS_PERSISTENT | B_FS_HAS_ATTR | B_FS_HAS_MIME | (flags & B_FS_IS_READONLY != 0 ? B_FS_IS_READONLY : 0)), fBlockCache(NULL), fTotalBlocks(0), @@ -281,6 +281,13 @@ Volume::RemoveNode(Node* node) } +status_t +Volume::UnremoveNode(Node* node) +{ + return unremove_vnode(fFSVolume, node->BlockIndex()); +} + + status_t Volume::ReadNode(uint64 blockIndex, Node*& _node) { @@ -326,28 +333,14 @@ status_t Volume::CreateDirectory(mode_t mode, Transaction& transaction, Directory*& _directory) { - // allocate a free block - AllocatedBlock allocatedBlock(fBlockAllocator, transaction); - status_t error = allocatedBlock.Allocate(); + Directory* directory = new(std::nothrow) Directory(this, + (mode & S_IUMSK) | S_IFDIR); + + status_t error = _CreateNode(directory, transaction); if (error != B_OK) return error; - // create the directory - Directory* directory = new(std::nothrow) Directory(this, - allocatedBlock.Index(), (mode & S_IUMSK) | S_IFDIR); - if (directory == NULL) - return B_NO_MEMORY; - - // attach the directory to the transaction - error = transaction.AddNode(directory, TRANSACTION_DELETE_NODE); - if (error != B_OK) { - delete directory; - return error; - } - - allocatedBlock.Detach(); _directory = directory; - return B_OK; } @@ -355,28 +348,13 @@ Volume::CreateDirectory(mode_t mode, Transaction& transaction, status_t Volume::CreateFile(mode_t mode, Transaction& transaction, File*& _file) { - // allocate a free block - AllocatedBlock allocatedBlock(fBlockAllocator, transaction); - status_t error = allocatedBlock.Allocate(); + File* file = new(std::nothrow) File(this, (mode & S_IUMSK) | S_IFREG); + + status_t error = _CreateNode(file, transaction); if (error != B_OK) return error; - // create the file - File* file = new(std::nothrow) File(this, allocatedBlock.Index(), - (mode & S_IUMSK) | S_IFREG); - if (file == NULL) - return B_NO_MEMORY; - - // attach the file to the transaction - error = transaction.AddNode(file, TRANSACTION_DELETE_NODE); - if (error != B_OK) { - delete file; - return error; - } - - allocatedBlock.Detach(); _file = file; - return B_OK; } @@ -384,28 +362,14 @@ Volume::CreateFile(mode_t mode, Transaction& transaction, File*& _file) status_t Volume::CreateSymLink(mode_t mode, Transaction& transaction, SymLink*& _symLink) { - // allocate a free block - AllocatedBlock allocatedBlock(fBlockAllocator, transaction); - status_t error = allocatedBlock.Allocate(); + SymLink* symLink = new(std::nothrow) SymLink(this, + (mode & S_IUMSK) | S_IFLNK); + + status_t error = _CreateNode(symLink, transaction); if (error != B_OK) return error; - // create the symlink - SymLink* symLink = new(std::nothrow) SymLink(this, allocatedBlock.Index(), - (mode & S_IUMSK) | S_IFLNK); - if (symLink == NULL) - return B_NO_MEMORY; - - // attach the symlink to the transaction - error = transaction.AddNode(symLink, TRANSACTION_DELETE_NODE); - if (error != B_OK) { - delete symLink; - return error; - } - - allocatedBlock.Detach(); _symLink = symLink; - return B_OK; } @@ -413,32 +377,35 @@ Volume::CreateSymLink(mode_t mode, Transaction& transaction, SymLink*& _symLink) status_t Volume::DeleteNode(Node* node) { + // let the node delete data associated with it + node->DeletingNode(); + + uint64 blockIndex = node->BlockIndex(); + + // delete the node itself Transaction transaction(this); status_t error = transaction.Start(); if (error == B_OK) { - error = node->DeletingNode(transaction); - if (error != B_OK) { - ERROR("Preparing deletion of failed for node at %" B_PRIu64 "\n", - node->BlockIndex()); - } - error = fBlockAllocator->Free(node->BlockIndex(), 1, transaction); if (error == B_OK) { error = transaction.Commit(); if (error != B_OK) { - ERROR("Failed to commit transaction for delete node at %" - B_PRIu64 "\n", node->BlockIndex()); + ERROR("Failed to commit transaction for deleting node at %" + B_PRIu64 "\n", blockIndex); } } else { ERROR("Failed to free block for node at %" B_PRIu64 "\n", - node->BlockIndex()); + blockIndex); } } else { - ERROR("Failed to start transaction for delete node at %" B_PRIu64 "\n", - node->BlockIndex()); + ERROR("Failed to start transaction for deleting node at %" B_PRIu64 + "\n", blockIndex); } + transaction.Abort(); + delete node; + return error; } @@ -510,3 +477,38 @@ Volume::_Init(uint64 totalBlocks) return B_OK; } + + +status_t +Volume::_CreateNode(Node* node, Transaction& transaction) +{ + if (node == NULL) + return B_NO_MEMORY; + + ObjectDeleter nodeDeleter(node); + + // allocate a free block + AllocatedBlock allocatedBlock(fBlockAllocator, transaction); + status_t error = allocatedBlock.Allocate(); + if (error != B_OK) + return error; + + // clear the block + { + Block block; + if (!block.GetZero(this, allocatedBlock.Index(), transaction)) + return B_ERROR; + } + + node->SetBlockIndex(allocatedBlock.Index()); + + // attach the node to the transaction + error = transaction.AddNode(node, TRANSACTION_DELETE_NODE); + if (error != B_OK) + return error; + + allocatedBlock.Detach(); + nodeDeleter.Detach(); + + return B_OK; +} diff --git a/src/tests/system/kernel/file_corruption/fs/Volume.h b/src/tests/system/kernel/file_corruption/fs/Volume.h index 3e5ab5a657..e2a5f5e8dd 100644 --- a/src/tests/system/kernel/file_corruption/fs/Volume.h +++ b/src/tests/system/kernel/file_corruption/fs/Volume.h @@ -41,6 +41,7 @@ public: status_t GetNode(uint64 blockIndex, Node*& _node); status_t PutNode(Node* node); status_t RemoveNode(Node* node); + status_t UnremoveNode(Node* node); status_t ReadNode(uint64 blockIndex, Node*& _node); @@ -73,6 +74,10 @@ public: private: status_t _Init(uint64 totalBlocks); + status_t _CreateNode(Node* node, + Transaction& transaction); + status_t _DeleteDirectoryEntries(Directory* directory); + private: fs_volume* fFSVolume; int fFD; diff --git a/src/tests/system/kernel/file_corruption/fs/checksumfs.cpp b/src/tests/system/kernel/file_corruption/fs/checksumfs.cpp index 481770d503..edf84ccfcd 100644 --- a/src/tests/system/kernel/file_corruption/fs/checksumfs.cpp +++ b/src/tests/system/kernel/file_corruption/fs/checksumfs.cpp @@ -147,6 +147,12 @@ struct DirCookie { return fDirectory; } + void SetTo(Directory* directory, bool skipArtificialEntries) + { + fDirectory = directory; + Rewind(skipArtificialEntries); + } + status_t ReadNextEntry(struct dirent* buffer, size_t size, uint32& _countRead) { @@ -199,9 +205,9 @@ struct DirCookie { return B_OK; } - void Rewind() + void Rewind(bool skipArtificialEntries = false) { - fIterationState = DOT; + fIterationState = skipArtificialEntries ? OTHERS : DOT; fEntryName[0] = '\0'; } @@ -218,6 +224,115 @@ private: }; +struct AttrDirCookie { + AttrDirCookie(Node* node) + : + fNode(node), + fAttributeDirectory(NULL), + fDirCookie(NULL) + { + } + + ~AttrDirCookie() + { + if (fAttributeDirectory != NULL) + fNode->GetVolume()->PutNode(fAttributeDirectory); + } + + status_t ReadNextEntry(struct dirent* buffer, size_t size, + uint32& _countRead) + { + status_t error = _UpdateAttributeDirectory(); + if (error != B_OK) + RETURN_ERROR(error); + + if (fAttributeDirectory == NULL) { + _countRead = 0; + return B_OK; + } + + return fDirCookie.ReadNextEntry(buffer, size, _countRead); + } + + void Rewind() + { + fDirCookie.Rewind(true); + } + +private: + status_t _UpdateAttributeDirectory() + { + uint64 blockIndex = fNode->AttributeDirectory(); + if (blockIndex == 0) { + // no (empty) attribute directory + if (fAttributeDirectory != NULL) { + fNode->GetVolume()->PutNode(fAttributeDirectory); + fAttributeDirectory = NULL; + } + + return B_OK; + } + + if (fAttributeDirectory != NULL) { + if (blockIndex == fAttributeDirectory->BlockIndex()) + return B_OK; + + // The attribute directory has changed in the meantime -- get rid + // of the old one. + fNode->GetVolume()->PutNode(fAttributeDirectory); + fAttributeDirectory = NULL; + } + + // get the attribute directory node + Node* node; + status_t error = fNode->GetVolume()->GetNode(blockIndex, node); + if (error != B_OK) + RETURN_ERROR(error); + + fAttributeDirectory = dynamic_cast(node); + if (fAttributeDirectory == NULL) { + fNode->GetVolume()->PutNode(node); + ERROR("checksumfs: attribute directory (%" B_PRIu64 ") of node %" + B_PRIu64 " is not a directory!\n", blockIndex, + fNode->BlockIndex()); + RETURN_ERROR(B_BAD_DATA); + } + + fDirCookie.SetTo(fAttributeDirectory, true); + + return B_OK; + } + +private: + Node* fNode; + Directory* fAttributeDirectory; + DirCookie fDirCookie; +}; + + +struct AttributeCookie { + char* name; + Node* attribute; + FileCookie* fileCookie; + + AttributeCookie(const char* name) + : + name(strdup(name)), + attribute(NULL), + fileCookie(NULL) + { + } + + ~AttributeCookie() + { + if (attribute != NULL) + attribute->GetVolume()->PutNode(attribute); + delete fileCookie; + free(name); + } +}; + + // #pragma mark - @@ -397,6 +512,280 @@ remove_entry(fs_volume* fsVolume, fs_vnode* parent, const char* name, } +/*! Opens the node according to the given open mode (if the permissions allow + that) and creates a file cookie. +*/ +static status_t +open_file(Volume* volume, Node* node, int openMode, Transaction& transaction, + bool commitTransaction, FileCookie*& _cookie) +{ + // translate the open mode to required permissions + uint32 accessFlags = 0; + switch (openMode & O_RWMASK) { + case O_RDONLY: + accessFlags = R_OK; + break; + case O_WRONLY: + accessFlags = W_OK; + break; + case O_RDWR: + accessFlags = R_OK | W_OK; + break; + } + + // We need to at least read-lock the node. If O_TRUNC is specified, we even + // need a write lock and a transaction. + NodeReadLocker nodeReadLocker; + + if ((openMode & O_TRUNC) != 0) { + accessFlags |= W_OK; + + status_t error = transaction.IsActive() + ? transaction.AddNode(node) : transaction.StartAndAddNode(node); + if (error != B_OK) + RETURN_ERROR(error); + } else if (!transaction.IsNodeLocked(node)) + nodeReadLocker.SetTo(node, false); + + // check permissions + if ((accessFlags & W_OK) != 0) { + if (volume->IsReadOnly()) + return B_READ_ONLY_DEVICE; + if (S_ISDIR(node->Mode())) + return B_IS_A_DIRECTORY; + } + + if ((openMode & O_DIRECTORY) != 0 && !S_ISDIR(node->Mode())) + return B_NOT_A_DIRECTORY; + + status_t error = check_access(node, accessFlags); + if (error != B_OK) + return error; + + // TODO: Support O_NOCACHE. + + FileCookie* cookie = new(std::nothrow) FileCookie(openMode); + if (cookie == NULL) + return B_NO_MEMORY; + ObjectDeleter cookieDeleter(cookie); + + // truncate the file, if requested + if ((openMode & O_TRUNC) != 0 && node->Size() > 0) { + error = node->Resize(0, false, transaction); + if (error != B_OK) + return error; + + node->Touched(NODE_MODIFIED); + + if (commitTransaction) { + uint32 statFlags = B_STAT_MODIFICATION_TIME | B_STAT_CHANGE_TIME + | B_STAT_SIZE; + error = transaction.Commit(StatChangedNotification(node, + statFlags)); + if (error != B_OK) + return error; + } + } + + _cookie = cookieDeleter.Detach(); + return B_OK; +} + + +static status_t +create_file(Volume* volume, Directory* directory, const char* name, + int openMode, int permissions, Transaction& transaction, + bool commitTransaction, FileCookie*& _cookie, Node*& _node, bool& _created) +{ + Node* childNode = NULL; + NodePutter childNodePutter; + + // Start the transaction and add the directory. We only need a read lock + // for the lookup, but later we'll need a write lock, if we have to create + // the file. So this is simpler. + status_t error = B_OK; + bool directoryLocked = false; + if (transaction.IsActive()) { + directoryLocked = transaction.IsNodeLocked(directory); + if (!directoryLocked) + error = transaction.AddNode(directory); + } else + error = transaction.StartAndAddNode(directory); + if (error != B_OK) + RETURN_ERROR(error); + + // look up the entry + uint64 blockIndex; + error = directory->LookupEntry(name, blockIndex); + if (error == B_OK) { + // the entry already exists + if ((openMode & O_EXCL) != 0) + return B_FILE_EXISTS; + + // get the entry's node + error = volume->GetNode(blockIndex, childNode); + if (error != B_OK) + RETURN_ERROR(error); + childNodePutter.SetTo(childNode); + + // We can (must even) unlock the directory now. The file won't go + // anywhere, since a transaction is already running. + if (!directoryLocked) + transaction.RemoveNode(directory); + + error = open_file(volume, childNode, openMode, transaction, + commitTransaction, _cookie); + if (error != B_OK) + RETURN_ERROR(error); + + childNodePutter.Detach(); + _node = childNode; + _created = false; + return B_OK; + } + + if (error != B_ENTRY_NOT_FOUND) + RETURN_ERROR(error); + + // The entry doesn't exist yet. We have to create a new file. + + // check the directory write permission + error = check_access(directory, W_OK); + if (error != B_OK) + return error; + + // don't create an entry in an unlinked directory + if (directory->HardLinks() == 0) + RETURN_ERROR(B_ENTRY_NOT_FOUND); + + // create a file + File* newFile; + error = volume->CreateFile(permissions, transaction, newFile); + if (error != B_OK) + return error; + + // insert the new file + error = directory->InsertEntry(name, newFile->BlockIndex(), transaction); + if (error != B_OK) + return error; + + // open the file + FileCookie* cookie; + error = open_file(volume, newFile, openMode & ~O_TRUNC, transaction, + false, cookie); + if (error != B_OK) + RETURN_ERROR(error); + ObjectDeleter cookieDeleter(cookie); + + // update stat data + newFile->SetHardLinks(1); + newFile->SetParentDirectory(directory->BlockIndex()); + + directory->Touched(NODE_MODIFIED); + + // announce the new vnode (needed for creating the file cache), but don't + // publish it yet + error = volume->NewNode(newFile); + if (error != B_OK) + RETURN_ERROR(error); + + // There's a vnode now -- the File object will be deleted when that is + // removed. + transaction.UpdateNodeFlags(newFile, TRANSACTION_REMOVE_NODE_ON_ERROR); + + // create the file cache + error = newFile->InitForVFS(); + if (error != B_OK) + return error; + + // node is fully initialized -- publish the vnode + error = volume->PublishNode(newFile, 0); + if (error != B_OK) { + // publish_vnode() deletes the vnode on error, but it doesn't call the + // remove_vnode() hook. So we need to make sure the object is deleted. + transaction.UpdateNodeFlags(newFile, TRANSACTION_DELETE_NODE); + RETURN_ERROR(error); + } + + // commit the transaction + if (commitTransaction) { + error = transaction.Commit(EntryCreatedNotification(directory, name, + newFile)); + if (error != B_OK) { + volume->PutNode(newFile); + RETURN_ERROR(error); + } + } + + _cookie = cookieDeleter.Detach(); + _node = newFile; + _created = true; + + return B_OK; +} + + +/*! Gets the node's attribute directory. + If a transaction is given and the attribute directory doesn't exist, a new + one is created and associate with the node. + On success the caller gets a reference to the attribute directory and is + responsible for putting it. If a transaction was given, the attribute + directory must be put after committing/aborting the transaction. +*/ +static status_t +get_attribute_directory(Node* node, Transaction* transaction, + Directory*& _attributeDirectory) +{ + uint64 blockIndex = node->AttributeDirectory(); + Directory* attributeDirectory; + + if (blockIndex != 0) { + // get the attribute directory node + Node* attrDirNode; + status_t error = node->GetVolume()->GetNode(blockIndex, attrDirNode); + if (error != B_OK) + RETURN_ERROR(error); + + attributeDirectory = dynamic_cast(attrDirNode); + if (attributeDirectory == NULL) { + node->GetVolume()->PutNode(node); + ERROR("checksumfs: attribute directory (%" B_PRIu64 ") of node %" + B_PRIu64 " is not a directory!\n", blockIndex, + node->BlockIndex()); + RETURN_ERROR(B_BAD_DATA); + } + } else { + // no (i.e. empty) attribute directory yet + if (transaction == NULL) + return B_ENTRY_NOT_FOUND; + + // create a new one + status_t error = node->GetVolume()->CreateDirectory( + S_IRWXU | S_IRWXG | S_IRWXO, *transaction, attributeDirectory); + if (error != B_OK) + RETURN_ERROR(error); + + attributeDirectory->SetMode(attributeDirectory->Mode() | S_ATTR_DIR); + attributeDirectory->SetParentDirectory(node->BlockIndex()); + attributeDirectory->SetHardLinks(1); + node->SetAttributeDirectory(attributeDirectory->BlockIndex()); + + // publish it + error = node->GetVolume()->PublishNode(attributeDirectory, 0); + if (error != B_OK) + RETURN_ERROR(error); + + // We have published the attribute directory, so don't delete it when + // committing or aborting the transaction. Instead, on error remove it. + transaction->UpdateNodeFlags(attributeDirectory, + TRANSACTION_REMOVE_NODE_ON_ERROR); + } + + _attributeDirectory = attributeDirectory; + return B_OK; +} + + // #pragma mark - FS operations @@ -1076,6 +1465,16 @@ checksumfs_write_stat(fs_volume* fsVolume, fs_vnode* vnode, updateChanged = true; } + if ((statMask & B_STAT_MODE) != 0) { + // only the user or root can do that + if (!isOwnerOrRoot) + RETURN_ERROR(B_NOT_ALLOWED); + + node->SetMode((node->Mode() & ~(mode_t)S_IUMSK) + | (st->st_mode & S_IUMSK)); + updateChanged = true; + } + if ((statMask & B_STAT_CREATION_TIME) != 0) { // the user or root can do that or any user with write access if (!isOwnerOrRoot && !hasWriteAccess) @@ -1121,93 +1520,9 @@ checksumfs_write_stat(fs_volume* fsVolume, fs_vnode* vnode, // #pragma mark - file operations -/*! Opens the node according to the given open mode (if the permissions allow - that) and creates a file cookie. - The caller must either pass a \a transaction, which is already started and - has the node added to it, or not have a lock to any node at all (this - function will do the locking in this case). -*/ -static status_t -open_file(Volume* volume, Node* node, int openMode, Transaction* transaction, - FileCookie*& _cookie) -{ - // translate the open mode to required permissions - uint32 accessFlags = 0; - switch (openMode & O_RWMASK) { - case O_RDONLY: - accessFlags = R_OK; - break; - case O_WRONLY: - accessFlags = W_OK; - break; - case O_RDWR: - accessFlags = R_OK | W_OK; - break; - } - - // We need to at least read-lock the node. If O_TRUNC is specified, we even - // need a write lock and a transaction. If the caller has supplied a - // transaction, it is already started and the node is locked. - NodeReadLocker nodeReadLocker; - Transaction localTransaction(volume); - - if ((openMode & O_TRUNC) != 0) { - accessFlags |= W_OK; - - if (transaction == NULL) { - transaction = &localTransaction; - status_t error = localTransaction.StartAndAddNode(node); - if (error != B_OK) - RETURN_ERROR(error); - } - } else if (transaction == NULL) - nodeReadLocker.SetTo(node, false); - - // check permissions - if ((accessFlags & W_OK) != 0) { - if (volume->IsReadOnly()) - return B_READ_ONLY_DEVICE; - if (S_ISDIR(node->Mode())) - return B_IS_A_DIRECTORY; - } - - if ((openMode & O_DIRECTORY) != 0 && !S_ISDIR(node->Mode())) - return B_NOT_A_DIRECTORY; - - status_t error = check_access(node, accessFlags); - if (error != B_OK) - return error; - - // TODO: Support O_NOCACHE. - - FileCookie* cookie = new(std::nothrow) FileCookie(openMode); - if (cookie == NULL) - return B_NO_MEMORY; - ObjectDeleter cookieDeleter(cookie); - - // truncate the file, if requested - if ((openMode & O_TRUNC) != 0) { - error = node->Resize(0, false, *transaction); - if (error != B_OK) - return error; - - node->Touched(NODE_MODIFIED); - - if (transaction == &localTransaction) { - error = transaction->Commit(); - if (error != B_OK) - return error; - } - } - - _cookie = cookieDeleter.Detach(); - return B_OK; -} - - static status_t checksumfs_create(fs_volume* fsVolume, fs_vnode* parent, const char* name, - int openMode, int perms, void** _cookie, ino_t* _newVnodeID) + int openMode, int permissions, void** _cookie, ino_t* _newVnodeID) { Volume* volume = (Volume*)fsVolume->private_volume; Directory* directory @@ -1218,124 +1533,17 @@ checksumfs_create(fs_volume* fsVolume, fs_vnode* parent, const char* name, if (volume->IsReadOnly()) return B_READ_ONLY_DEVICE; - Node* childNode; - NodePutter childNodePutter; - - childNode = NULL; - - // look up the entry - NodeWriteLocker directoryLocker(directory); - // We only need a read lock for the lookup, but later we'll need a - // write lock, if we have to create the file. So this is simpler. - - uint64 blockIndex; - status_t error = directory->LookupEntry(name, blockIndex); - if (error == B_OK) { - // the entry already exists - if ((openMode & O_EXCL) != 0) - return B_FILE_EXISTS; - - // get the entry's node - error = volume->GetNode(blockIndex, childNode); - if (error != B_OK) - RETURN_ERROR(error); - childNodePutter.SetTo(childNode); - - directoryLocker.Unlock(); - - FileCookie* cookie; - error = open_file(volume, childNode, openMode, NULL, cookie); - if (error != B_OK) - RETURN_ERROR(error); - - *_cookie = cookie; - *_newVnodeID = childNode->BlockIndex(); - return B_OK; - } - - if (error != B_ENTRY_NOT_FOUND) - RETURN_ERROR(error); - - // The entry doesn't exist yet. We have to create a new file. - - // check the directory write permission - error = check_access(directory, W_OK); - if (error != B_OK) - return error; - - // don't create an entry in an unlinked directory - if (directory->HardLinks() == 0) - RETURN_ERROR(B_ENTRY_NOT_FOUND); - - // start a transaction and attach the directory Transaction transaction(volume); - error = transaction.StartAndAddNode(directory, - TRANSACTION_NODE_ALREADY_LOCKED); - if (error != B_OK) - RETURN_ERROR(error); - - directoryLocker.Detach(); - // write lock transferred to the transaction - - // create a file - File* newFile; - error = volume->CreateFile(perms, transaction, newFile); - if (error != B_OK) - return error; - - // insert the new file - error = directory->InsertEntry(name, newFile->BlockIndex(), transaction); - if (error != B_OK) - return error; - - // open the file FileCookie* cookie; - error = open_file(volume, newFile, openMode & ~O_TRUNC, &transaction, - cookie); - if (error != B_OK) - RETURN_ERROR(error); - ObjectDeleter cookieDeleter(cookie); - - // update stat data - newFile->SetHardLinks(1); - - directory->Touched(NODE_MODIFIED); - - // announce the new vnode, but don't publish it yet - error = volume->NewNode(newFile); + Node* node; + bool created; + status_t error = create_file(volume, directory, name, openMode, permissions, + transaction, true, cookie, node, created); if (error != B_OK) RETURN_ERROR(error); - // create the file cache - error = newFile->InitForVFS(); - if (error != B_OK) { - volume->RemoveNode(newFile); - return error; - } - - // Don't delete the File object when the transaction is committed, since - // it's going to be published. - transaction.KeepNode(newFile); - - // commit the transaction - error = transaction.Commit(EntryCreatedNotification(directory, name, - newFile)); - if (error != B_OK) { - volume->RemoveNode(newFile); - delete newFile; - RETURN_ERROR(error); - } - - // everything is on disk -- publish the vnode, now - error = volume->PublishNode(newFile, 0); - if (error != B_OK) { - // TODO: The file creation succeeded, but the caller will get an error. - // We could try to delete the file again. - RETURN_ERROR(error); - } - - *_cookie = cookieDeleter.Detach(); - *_newVnodeID = newFile->BlockIndex(); + *_cookie = cookie; + *_newVnodeID = node->BlockIndex(); return B_OK; } @@ -1348,8 +1556,14 @@ checksumfs_open(fs_volume* fsVolume, fs_vnode* vnode, int openMode, Volume* volume = (Volume*)fsVolume->private_volume; Node* node = (Node*)vnode->private_node; + // don't allow opening an attribute this way + if ((node->Mode() & S_ATTR) != 0) + RETURN_ERROR(B_BAD_VALUE); + + Transaction transaction(volume); FileCookie* cookie; - status_t error = open_file(volume, node, openMode, NULL, cookie); + status_t error = open_file(volume, node, openMode, transaction, true, + cookie); if (error != B_OK) RETURN_ERROR(error); @@ -1503,6 +1717,12 @@ checksumfs_open_dir(fs_volume* fsVolume, fs_vnode* vnode, void** _cookie) if (directory == NULL) return B_NOT_A_DIRECTORY; + NodeReadLocker nodeLocker(directory); + + // don't allow opening an attribute directory this way + if ((directory->Mode() & S_ATTR_DIR) != 0) + RETURN_ERROR(B_BAD_VALUE); + status_t error = check_access(directory, R_OK); if (error != B_OK) return error; @@ -1559,6 +1779,415 @@ checksumfs_rewind_dir(fs_volume* fsVolume, fs_vnode* vnode, void* _cookie) } +// #pragma mark - attribute directory operations + + +static status_t +checksumfs_open_attr_dir(fs_volume* volume, fs_vnode* vnode, void** _cookie) +{ + Node* node = (Node*)vnode->private_node; + + NodeReadLocker nodeLocker(node); + + status_t error = check_access(node, R_OK); + if (error != B_OK) + return error; + + AttrDirCookie* cookie = new(std::nothrow) AttrDirCookie(node); + if (cookie == NULL) + return B_NO_MEMORY; + + *_cookie = cookie; + return B_OK; +} + + +static status_t +checksumfs_close_attr_dir(fs_volume* volume, fs_vnode* vnode, void* cookie) +{ + return B_OK; +} + + +static status_t +checksumfs_free_attr_dir_cookie(fs_volume* volume, fs_vnode* vnode, + void* _cookie) +{ + AttrDirCookie* cookie = (AttrDirCookie*)_cookie; + delete cookie; + return B_OK; +} + + +static status_t +checksumfs_read_attr_dir(fs_volume* volume, fs_vnode* vnode, void* _cookie, + struct dirent* buffer, size_t bufferSize, uint32* _num) +{ + if (*_num == 0) + return B_OK; + + Node* node = (Node*)vnode->private_node; + AttrDirCookie* cookie = (AttrDirCookie*)_cookie; + + NodeReadLocker nodeLocker(node); + + return cookie->ReadNextEntry(buffer, bufferSize, *_num); +} + + +static status_t +checksumfs_rewind_attr_dir(fs_volume* volume, fs_vnode* vnode, void* _cookie) +{ + Node* node = (Node*)vnode->private_node; + AttrDirCookie* cookie = (AttrDirCookie*)_cookie; + + NodeReadLocker nodeLocker(node); + + cookie->Rewind(); + return B_OK; +} + + +// #pragma mark - attribute operations + + +static status_t +checksumfs_create_attr(fs_volume* fsVolume, fs_vnode* vnode, const char* name, + uint32 type, int openMode, void** _cookie) +{ + Volume* volume = (Volume*)fsVolume->private_volume; + Node* node = (Node*)vnode->private_node; + if (node == NULL) + return B_NOT_A_DIRECTORY; + + if (volume->IsReadOnly()) + return B_READ_ONLY_DEVICE; + + // create the attribute cookie + AttributeCookie* cookie = new(std::nothrow) AttributeCookie(name); + if (cookie == NULL || cookie->name == NULL) { + delete cookie; + return B_NO_MEMORY; + } + ObjectDeleter cookieDeleter(cookie); + + // Start a transaction and lock the node. + // Note: Other than for ordinary nodes the locking order when attributes + // are involved is: node -> attribute directory -> attribute. + Transaction transaction(volume); + status_t error = transaction.StartAndAddNode(node); + if (error != B_OK) + RETURN_ERROR(error); + + // check permissions + error = check_access(node, W_OK); + if (error != B_OK) + return error; + + // get the attribute directory (create, if necessary) + Directory* attributeDirectory; + error = get_attribute_directory(node, &transaction, attributeDirectory); + if (error != B_OK) + RETURN_ERROR(error); + NodePutter attributeDirectoryPutter(attributeDirectory); + + // open/create the attribute + bool created; + error = create_file(volume, attributeDirectory, name, openMode, + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH, transaction, + false, cookie->fileCookie, cookie->attribute, created); + if (error != B_OK) + RETURN_ERROR(error); + + if (created) { + cookie->attribute->SetMode(cookie->attribute->Mode() | S_ATTR); + cookie->attribute->SetAttributeType(type); + } + + // commit the transaction + if (transaction.IsActive()) { + if (created || (openMode & O_TRUNC) != 0) { + node->Touched(NODE_STAT_CHANGED); + + AttributeChangedNotification attributeNotification(node, name, + created ? B_ATTR_CREATED : B_ATTR_CHANGED); + StatChangedNotification statNotification(node, B_STAT_CHANGE_TIME); + error = transaction.Commit(&attributeNotification, + &statNotification); + } else + error = transaction.Commit(); + } + + *_cookie = cookieDeleter.Detach(); + return B_OK; +} + + +static status_t +checksumfs_open_attr(fs_volume* fsVolume, fs_vnode* vnode, const char* name, + int openMode, void** _cookie) +{ + Volume* volume = (Volume*)fsVolume->private_volume; + Node* node = (Node*)vnode->private_node; + + // create the attribute cookie + AttributeCookie* cookie = new(std::nothrow) AttributeCookie(name); + if (cookie == NULL || cookie->name == NULL) { + delete cookie; + return B_NO_MEMORY; + } + ObjectDeleter cookieDeleter(cookie); + + // Get the node's attribute directory (don't create it, if it doesn't exist + // yet). We only need to read-lock the node for that, but when O_TRUNC is + // given, we already start the transaction and write-lock the node, so we + // don't get a locking order inversion later. The locking order when + // attributes are involved is: node -> attribute directory -> attribute. + Transaction transaction(volume); + NodeReadLocker readLocker; + if ((openMode & O_TRUNC) != 0) { + status_t error = transaction.StartAndAddNode(node); + if (error != B_OK) + RETURN_ERROR(error); + } else + readLocker.SetTo(node, false); + + Directory* attributeDirectory; + status_t error = get_attribute_directory(node, NULL, attributeDirectory); + if (error != B_OK) + RETURN_ERROR(error); + NodePutter attributeDirectoryPutter(attributeDirectory); + + // look up the attribute + readLocker.SetTo(attributeDirectory, false); + uint64 blockIndex; + error = attributeDirectory->LookupEntry(name, blockIndex); + if (error != B_OK) + RETURN_ERROR(error); + + error = volume->GetNode(blockIndex, cookie->attribute); + // the vnode reference directly goes to the cookie in case of success + if (error != B_OK) + RETURN_ERROR(error); + + // open the attribute + error = open_file(volume, cookie->attribute, openMode, transaction, false, + cookie->fileCookie); + if (error != B_OK) + RETURN_ERROR(error); + + // commit the transaction + if (transaction.IsActive()) { + if ((openMode & O_TRUNC) != 0) { + node->Touched(NODE_STAT_CHANGED); + + AttributeChangedNotification attributeNotification(node, name, + B_ATTR_CHANGED); + StatChangedNotification statNotification(node, B_STAT_CHANGE_TIME); + error = transaction.Commit(&attributeNotification, + &statNotification); + } else + error = transaction.Commit(); + } + + *_cookie = cookieDeleter.Detach(); + return B_OK; +} + + +static status_t +checksumfs_close_attr(fs_volume* fsVolume, fs_vnode* vnode, void* cookie) +{ + return B_OK; +} + + +static status_t +checksumfs_free_attr_cookie(fs_volume* fsVolume, fs_vnode* vnode, void* _cookie) +{ + AttributeCookie* cookie = (AttributeCookie*)_cookie; + delete cookie; + return B_OK; +} + + +static status_t +checksumfs_read_attr(fs_volume* fsVolume, fs_vnode* vnode, void* _cookie, + off_t pos, void* buffer, size_t* _length) +{ + AttributeCookie* cookie = (AttributeCookie*)_cookie; + + switch (cookie->fileCookie->openMode & O_RWMASK) { + case O_RDONLY: + case O_RDWR: + break; + case O_WRONLY: + default: + RETURN_ERROR(EBADF); + } + + return cookie->attribute->Read(pos, buffer, *_length, *_length); +} + + +static status_t +checksumfs_write_attr(fs_volume* fsVolume, fs_vnode* vnode, void* _cookie, + off_t pos, const void* buffer, size_t* _length) +{ + Volume* volume = (Volume*)fsVolume->private_volume; + Node* node = (Node*)vnode->private_node; + AttributeCookie* cookie = (AttributeCookie*)_cookie; + + switch (cookie->fileCookie->openMode & O_RWMASK) { + case O_WRONLY: + case O_RDWR: + break; + case O_RDONLY: + default: + RETURN_ERROR(EBADF); + } + + if (pos < 0) + RETURN_ERROR(B_BAD_VALUE); + + if ((cookie->fileCookie->openMode & O_APPEND) != 0) { + pos = -1; + // special value handled by Write() + } + + bool sizeChanged; + status_t error = cookie->attribute->Write(pos, buffer, *_length, *_length, + sizeChanged); + if (error != B_OK) + RETURN_ERROR(error); + + // update the node changed time and send out a notifications (don't fail, + // if any of this fails) + Transaction transaction(volume); + if (transaction.StartAndAddNode(node) != B_OK) + return B_OK; + if (transaction.AddNode(cookie->attribute) != B_OK) + return B_OK; + + cookie->attribute->Touched(NODE_MODIFIED); + + // commit the transaction + if (cookie->attribute->ParentDirectory() != 0) { + node->Touched(NODE_STAT_CHANGED); + + AttributeChangedNotification attributeNotification(node, cookie->name, + B_ATTR_CHANGED); + StatChangedNotification statNotification(node, B_STAT_CHANGE_TIME); + transaction.Commit(&attributeNotification, &statNotification); + } else { + // attribute has been removed -- no notifications needed + transaction.Commit(); + } + + return B_OK; +} + + +static status_t +checksumfs_read_attr_stat(fs_volume* fsVolume, fs_vnode* vnode, void* _cookie, + struct stat* st) +{ + AttributeCookie* cookie = (AttributeCookie*)_cookie; + + // not many fields needed ATM + st->st_size = cookie->attribute->Size(); + st->st_type = cookie->attribute->AttributeType(); + + return B_OK; +} + + +static status_t +checksumfs_remove_attr(fs_volume* fsVolume, fs_vnode* vnode, const char* name) +{ + Volume* volume = (Volume*)fsVolume->private_volume; + Node* node = (Node*)vnode->private_node; + + if (volume->IsReadOnly()) + return B_READ_ONLY_DEVICE; + + // start a transaction + Transaction transaction(volume); + status_t error = transaction.StartAndAddNode(node); + if (error != B_OK) + RETURN_ERROR(error); + + // check permissions + error = check_access(node, W_OK); + if (error != B_OK) + RETURN_ERROR(error); + + // get the attribute directory + Directory* attributeDirectory; + error = get_attribute_directory(node, NULL, attributeDirectory); + if (error != B_OK) + RETURN_ERROR(error); + NodePutter attributeDirectoryPutter(attributeDirectory); + + error = transaction.AddNode(attributeDirectory); + if (error != B_OK) + RETURN_ERROR(error); + + // look up the entry + uint64 blockIndex; + error = attributeDirectory->LookupEntry(name, blockIndex); + if (error != B_OK) + RETURN_ERROR(error); + + // get the attribute node + Node* attribute; + error = volume->GetNode(blockIndex, attribute); + if (error != B_OK) + RETURN_ERROR(error); + NodePutter attributePutter(attribute); + + error = transaction.AddNode(attribute); + if (error != B_OK) { + volume->PutNode(attribute); + RETURN_ERROR(error); + } + + // remove the entry + bool attrDirEmpty; + error = attributeDirectory->RemoveEntry(name, transaction, &attrDirEmpty); + if (error != B_OK) + RETURN_ERROR(error); + + // remove the attribute node + error = volume->RemoveNode(attribute); + if (error != B_OK) + return error; + transaction.UpdateNodeFlags(attribute, TRANSACTION_UNREMOVE_NODE_ON_ERROR); + + // if the attribute directory is empty now, remove it too + if (attrDirEmpty) { + error = volume->RemoveNode(attributeDirectory); + if (error != B_OK) + return error; + transaction.UpdateNodeFlags(attributeDirectory, + TRANSACTION_UNREMOVE_NODE_ON_ERROR); + + node->SetAttributeDirectory(0); + } + + // update stat data + attribute->SetHardLinks(0); + + node->Touched(NODE_STAT_CHANGED); + + // commit the transaction + AttributeChangedNotification attributeNotification(node, name, + B_ATTR_REMOVED); + StatChangedNotification statNotification(node, B_STAT_CHANGE_TIME); + return transaction.Commit(&attributeNotification, &statNotification); +} + + // #pragma mark - module @@ -1682,13 +2311,13 @@ fs_vnode_ops gCheckSumFSVnodeOps = { /* asynchronous I/O */ checksumfs_io, - NULL, // checksumfs_cancel_io, + NULL, // cancel_io /* cache file access */ checksumfs_get_file_map, /* common operations */ - NULL, // checksumfs_ioctl, + NULL, // ioctl checksumfs_set_flags, NULL, // select NULL, // deselect @@ -1723,24 +2352,24 @@ fs_vnode_ops gCheckSumFSVnodeOps = { checksumfs_rewind_dir, /* attribute directory operations */ - NULL, // checksumfs_open_attr_dir, - NULL, // checksumfs_close_attr_dir, - NULL, // checksumfs_free_attr_dir_cookie, - NULL, // checksumfs_read_attr_dir, - NULL, // checksumfs_rewind_attr_dir, + checksumfs_open_attr_dir, + checksumfs_close_attr_dir, + checksumfs_free_attr_dir_cookie, + checksumfs_read_attr_dir, + checksumfs_rewind_attr_dir, /* attribute operations */ - NULL, // checksumfs_create_attr, - NULL, // checksumfs_open_attr, - NULL, // checksumfs_close_attr, - NULL, // checksumfs_free_attr_cookie, - NULL, // checksumfs_read_attr, - NULL, // checksumfs_write_attr, + checksumfs_create_attr, + checksumfs_open_attr, + checksumfs_close_attr, + checksumfs_free_attr_cookie, + checksumfs_read_attr, + checksumfs_write_attr, - NULL, // checksumfs_read_attr_stat, - NULL, // checksumfs_write_attr_stat, - NULL, // checksumfs_rename_attr, - NULL, // checksumfs_remove_attr, + checksumfs_read_attr_stat, + NULL, // write_attr_stat + NULL, // rename_attr + checksumfs_remove_attr, /* support for node and FS layers */ NULL, // create_special_node