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