// ShareVolume.cpp
#include "ShareVolume.h"
#include <new>
#include <unistd.h>
#include <AppDefs.h> // for B_QUERY_UPDATE
#include <AutoDeleter.h>
#include <AutoLocker.h>
#include <HashMap.h>
#include "AuthenticationServer.h"
#include "Compatibility.h"
#include "Connection.h"
#include "ConnectionFactory.h"
#include "DebugSupport.h"
#include "ExtendedServerInfo.h"
#include "Permissions.h"
#include "Node.h"
#include "QueryManager.h"
#include "RequestChannel.h"
#include "RequestConnection.h"
#include "Requests.h"
#include "RootVolume.h"
#include "SendReceiveRequest.h"
#include "ServerConnection.h"
#include "ServerConnectionProvider.h"
#include "ShareAttrDir.h"
#include "ShareAttrDirIterator.h"
#include "ShareNode.h"
#include "Utils.h"
#include "VolumeManager.h"
#include "VolumeSupport.h"
// TODO: Request timeouts!
static const int32 kMaxWriteBufferSize = 64 * 1024; // 64 KB
static const int32 kUserBufferSize = 256;
static const int32 kPasswordBufferSize = 256;
// connection states
enum {
CONNECTION_NOT_INITIALIZED,
CONNECTION_READY,
CONNECTION_CLOSED,
};
// NodeMap
struct ShareVolume::NodeMap : HashMap<HashKey64<ino_t>, ShareNode*> {
};
// EntryKey
//
// NOTE: This class doesn't make a copy of the name string it is constructed
// with. So, when entering the key in a map, one must make sure, that the
// string stays valid as long as the entry is in the map.
struct ShareVolume::EntryKey {
EntryKey() {}
EntryKey(ino_t directoryID, const char* name)
: directoryID(directoryID),
name(name)
{
}
EntryKey(const EntryKey& other)
: directoryID(other.directoryID),
name(other.name)
{
}
uint32 GetHashCode() const
{
uint32 hash = (uint32)directoryID;
hash = 31 * hash + (uint32)(directoryID >> 32);
hash = 31 * hash + string_hash(name);
return hash;
}
EntryKey& operator=(const EntryKey& other)
{
directoryID = other.directoryID;
name = other.name;
return *this;
}
bool operator==(const EntryKey& other) const
{
if (directoryID != other.directoryID)
return false;
if (name)
return (other.name && strcmp(name, other.name) == 0);
return !other.name;
}
bool operator!=(const EntryKey& other) const
{
return !(*this == other);
}
ino_t directoryID;
const char* name;
};
// EntryMap
struct ShareVolume::EntryMap : HashMap<EntryKey, ShareDirEntry*> {
};
// LocalNodeIDMap
struct ShareVolume::LocalNodeIDMap : HashMap<NodeID, ino_t> {
};
// RemoteNodeIDMap
struct ShareVolume::RemoteNodeIDMap : HashMap<HashKey64<ino_t>, NodeID> {
};
// DirCookie
struct ShareVolume::DirCookie {
ShareDirIterator* iterator;
};
// AttrDirCookie
struct ShareVolume::AttrDirCookie {
AttrDirCookie()
: iterator(NULL),
cookie(-1),
rewind(false)
{
}
ShareAttrDirIterator* iterator;
intptr_t cookie;
bool rewind;
};
// AttrDirIteratorMap
struct ShareVolume::AttrDirIteratorMap
: HashMap<HashKey64<ino_t>, DoublyLinkedList<ShareAttrDirIterator>*> {
};
// #pragma mark -
// constructor
ShareVolume::ShareVolume(VolumeManager* volumeManager,
ServerConnectionProvider* connectionProvider,
ExtendedServerInfo* serverInfo, ExtendedShareInfo* shareInfo)
: Volume(volumeManager),
fID(-1),
fFlags(0),
fMountLock("share mount lock"),
fNodes(NULL),
fEntries(NULL),
fAttrDirIterators(NULL),
fLocalNodeIDs(NULL),
fRemoteNodeIDs(NULL),
fServerConnectionProvider(connectionProvider),
fServerInfo(serverInfo),
fShareInfo(shareInfo),
fServerConnection(NULL),
fConnection(NULL),
fSharePermissions(0),
fConnectionState(CONNECTION_NOT_INITIALIZED)
{
fFlags = fVolumeManager->GetMountFlags();
if (fServerConnectionProvider)
fServerConnectionProvider->AcquireReference();
if (fServerInfo)
fServerInfo->AcquireReference();
if (fShareInfo)
fShareInfo->AcquireReference();
}
// destructor
ShareVolume::~ShareVolume()
{
PRINT(("ShareVolume::~ShareVolume()\n"));
// delete the root node
if (fRootNode) {
if (fNodes)
fNodes->Remove(fRootNode->GetID());
delete fRootNode;
fRootNode = NULL;
}
// delete the nodes
if (fNodes) {
// there shouldn't be any more nodes
if (fNodes->Size() > 0) {
WARN("ShareVolume::~ShareVolume(): WARNING: There are still "
"%" B_PRId32 " nodes\n", fNodes->Size());
}
for (NodeMap::Iterator it = fNodes->GetIterator(); it.HasNext();)
delete it.Next().value;
delete fNodes;
}
// delete the entries
if (fEntries) {
// there shouldn't be any more entries
if (fEntries->Size() > 0) {
WARN("ShareVolume::~ShareVolume(): WARNING: There are still "
"%" B_PRId32 " entries\n", fEntries->Size());
}
for (EntryMap::Iterator it = fEntries->GetIterator(); it.HasNext();)
delete it.Next().value;
delete fEntries;
}
delete fLocalNodeIDs;
delete fRemoteNodeIDs;
if (fShareInfo)
fShareInfo->ReleaseReference();
if (fServerInfo)
fServerInfo->ReleaseReference();
if (fServerConnection)
fServerConnection->ReleaseReference();
if (fServerConnectionProvider)
fServerConnectionProvider->ReleaseReference();
}
// GetID
nspace_id
ShareVolume::GetID() const
{
return fID;
}
// IsReadOnly
bool
ShareVolume::IsReadOnly() const
{
return (fFlags & B_MOUNT_READ_ONLY);
}
// SupportsQueries
bool
ShareVolume::SupportsQueries() const
{
return (fSharePermissions & QUERY_SHARE_PERMISSION);
}
// Init
status_t
ShareVolume::Init(const char* name)
{
status_t error = Volume::Init(name);
if (error != B_OK)
return error;
// create node map
fNodes = new(std::nothrow) NodeMap;
if (!fNodes)
RETURN_ERROR(B_NO_MEMORY);
error = fNodes->InitCheck();
if (error != B_OK)
return error;
// create entry map
fEntries = new(std::nothrow) EntryMap;
if (!fEntries)
RETURN_ERROR(B_NO_MEMORY);
error = fEntries->InitCheck();
if (error != B_OK)
return error;
// create attribute iterator map
fAttrDirIterators = new(std::nothrow) AttrDirIteratorMap;
if (!fAttrDirIterators)
RETURN_ERROR(B_NO_MEMORY);
error = fAttrDirIterators->InitCheck();
if (error != B_OK)
return error;
// create local node ID map
fLocalNodeIDs = new(std::nothrow) LocalNodeIDMap;
if (!fLocalNodeIDs)
RETURN_ERROR(B_NO_MEMORY);
error = fLocalNodeIDs->InitCheck();
if (error != B_OK)
return error;
// create remote node ID map
fRemoteNodeIDs = new(std::nothrow) RemoteNodeIDMap;
if (!fRemoteNodeIDs)
RETURN_ERROR(B_NO_MEMORY);
error = fRemoteNodeIDs->InitCheck();
if (error != B_OK)
return error;
// get a local node ID for our root node
vnode_id localID = fVolumeManager->NewNodeID(this);
if (localID < 0)
return localID;
// create the root node
fRootNode = new(std::nothrow) ShareDir(this, localID, NULL);
if (!fRootNode) {
fVolumeManager->RemoveNodeID(localID);
return B_NO_MEMORY;
}
// add the root node to the node map
error = fNodes->Put(localID, fRootNode);
if (error != B_OK)
return error;
return B_OK;
}
// Uninit
void
ShareVolume::Uninit()
{
Volume::Uninit();
}
// GetRootNode
Node*
ShareVolume::GetRootNode() const
{
return fRootNode;
}
// PrepareToUnmount
void
ShareVolume::PrepareToUnmount()
{
PRINT(("ShareVolume::PrepareToUnmount()\n"));
Volume::PrepareToUnmount();
ConnectionClosed();
AutoLocker<Locker> locker(fLock);
// remove all entries and send respective "entry removed" events
for (EntryMap::Iterator it = fEntries->GetIterator(); it.HasNext();) {
ShareDirEntry* entry = it.Next().value;
NotifyListener(B_ENTRY_REMOVED, fVolumeManager->GetID(),
entry->GetDirectory()->GetID(), 0, entry->GetNode()->GetID(),
entry->GetName());
_RemoveEntry(entry);
}
fEntries->Clear();
// get all IDs
int32 count = fNodes->Size();
if (count == 0)
return;
PRINT(" %" B_PRId32 " nodes to remove\n", count);
vnode_id* ids = new(std::nothrow) vnode_id[count];
if (!ids) {
ERROR("ShareVolume::PrepareToUnmount(): ERROR: Insufficient memory to "
"allocate the node ID array!\n");
return;
}
ArrayDeleter<vnode_id> _(ids);
count = 0;
for (NodeMap::Iterator it = fNodes->GetIterator(); it.HasNext();) {
ShareNode* node = it.Next().value;
ids[count++] = node->GetID();
}
// Remove all nodes that are not known to the VFS right away.
// If the netfs is already in the process of being unmounted, GetVNode()
// will fail and the GetVNode(), RemoveVNode(), PutVNode() method won't
// work for removing them.
int32 remainingCount = 0;
for (int32 i = 0; i < count; i++) {
if (Node* node = fNodes->Get(ids[i])) {
if (node->IsKnownToVFS()) {
// node is known to VFS; we need to use the GetVNode(),
// RemoveVNode(), PutVNode() method
ids[remainingCount++] = ids[i];
} else {
// node is not known to VFS; just remove and delete it
fNodes->Remove(node->GetID());
_RemoveLocalNodeID(node->GetID());
if (node != fRootNode)
delete node;
}
}
}
count = remainingCount;
locker.Unlock();
// remove the nodes
for (int32 i = 0; i < count; i++) {
Node* node;
if (GetVNode(ids[i], &node) == B_OK) {
PRINT(" removing node %" B_PRIdINO "\n", ids[i]);
Volume::RemoveVNode(ids[i]);
PutVNode(ids[i]);
}
}
// remove ourselves for the server connection
if (fServerConnection)
fServerConnection->RemoveVolume(this);
// delete all entries
PRINT(("ShareVolume::PrepareToUnmount() done\n"));
}
// RemoveChildVolume
void
ShareVolume::RemoveChildVolume(Volume* volume)
{
// should never be called
WARN("WARNING: ShareVolume::RemoveChildVolume(%p) invoked!\n", volume);
}
// #pragma mark -
// #pragma mark ----- FS -----
// Unmount
status_t
ShareVolume::Unmount()
{
if (_IsConnected()) {
// send the request
UnmountRequest request;
request.volumeID = fID;
fConnection->SendRequest(&request);
}
return B_OK;
}
// Sync
status_t
ShareVolume::Sync()
{
// TODO: Implement?
// We can't implement this without risking an endless recursion. The server
// could only invoke sync(), which would sync all FSs, including a NetFS
// which might be connected with a server running alongside this client.
// We could introduce a sync flag to break the recursion. This might be
// risky though.
return B_OK;
}
// #pragma mark -
// #pragma mark ----- vnodes -----
// ReadVNode
status_t
ShareVolume::ReadVNode(vnode_id vnid, char reenter, Node** _node)
{
// check the map, maybe it's already loaded
ShareNode* node = NULL;
{
AutoLocker<Locker> _(fLock);
node = _GetNodeByLocalID(vnid);
if (node) {
node->SetKnownToVFS(true);
*_node = node;
// add a volume reference for the node
AcquireReference();
return B_OK;
}
}
// not yet loaded: send a request to the server
if (!_EnsureShareMounted())
return ERROR_NOT_CONNECTED;
// get the remote ID
NodeID remoteID;
status_t error = _GetRemoteNodeID(vnid, &remoteID);
if (error != B_OK)
return error;
// prepare the request
ReadVNodeRequest request;
request.volumeID = fID;
request.nodeID = remoteID;
// send the request
ReadVNodeReply* reply;
error = SendRequest(fConnection, &request, &reply);
if (error != B_OK)
RETURN_ERROR(error);
ObjectDeleter<Request> replyDeleter(reply);
if (reply->error != B_OK)
RETURN_ERROR(reply->error);
// add the node
AutoLocker<Locker> _(fLock);
error = _LoadNode(reply->nodeInfo, &node);
if (error != B_OK)
RETURN_ERROR(error);
node->SetKnownToVFS(true);
*_node = node;
// add a volume reference for the node
AcquireReference();
return B_OK;
}
// WriteVNode
status_t
ShareVolume::WriteVNode(Node* node, char reenter)
{
AutoLocker<Locker> locker(fLock);
node->SetKnownToVFS(false);
// surrender the node's volume reference
locker.Unlock();
PutVolume();
return B_OK;
}
// RemoveVNode
status_t
ShareVolume::RemoveVNode(Node* node, char reenter)
{
AutoLocker<Locker> locker(fLock);
node->SetKnownToVFS(false);
fNodes->Remove(node->GetID());
_RemoveLocalNodeID(node->GetID());
if (node != fRootNode)
delete node;
// surrender the node's volume reference
locker.Unlock();
PutVolume();
return B_OK;
}
// #pragma mark -
// #pragma mark ----- nodes -----
// FSync
status_t
ShareVolume::FSync(Node* _node)
{
// TODO: Implement!
return B_BAD_VALUE;
}
// ReadStat
status_t
ShareVolume::ReadStat(Node* _node, struct stat* st)
{
ShareNode* node = dynamic_cast<ShareNode*>(_node);
AutoLocker<Locker> _(fLock);
*st = node->GetNodeInfo().st;
st->st_dev = fVolumeManager->GetID();
st->st_ino = node->GetID();
// we set the UID/GID fields to the one who mounted the FS
st->st_uid = fVolumeManager->GetMountUID();
st->st_gid = fVolumeManager->GetMountGID();
return B_OK;
}
// WriteStat
status_t
ShareVolume::WriteStat(Node* _node, struct stat *st, uint32 mask)
{
ShareNode* node = dynamic_cast<ShareNode*>(_node);
if (!_EnsureShareMounted())
return ERROR_NOT_CONNECTED;
// check read-only
if (IsReadOnly())
return B_PERMISSION_DENIED;
// prepare the request
WriteStatRequest request;
request.volumeID = fID;
request.nodeID = node->GetRemoteID();
request.nodeInfo.st = *st;
request.mask = mask;
// send the request
WriteStatReply* reply;
status_t error = SendRequest(fConnection, &request, &reply);
if (error != B_OK)
RETURN_ERROR(error);
ObjectDeleter<Request> replyDeleter(reply);
// update the node
if (reply->nodeInfoValid)
_UpdateNode(reply->nodeInfo);
if (reply->error != B_OK)
RETURN_ERROR(reply->error);
return B_OK;
}
// Access
status_t
ShareVolume::Access(Node* _node, int mode)
{
// TODO: Implement.
return B_OK;
}
// #pragma mark -
// #pragma mark ----- files -----
// Create
status_t
ShareVolume::Create(Node* _dir, const char* name, int openMode, int mode,
vnode_id* vnid, void** cookie)
{
ShareDir* dir = dynamic_cast<ShareDir*>(_dir);
if (!dir)
return B_BAD_VALUE;
if (!_EnsureShareMounted())
return ERROR_NOT_CONNECTED;
// check permissions
if (IsReadOnly())
return B_PERMISSION_DENIED;
if (IsVNodeRemoved(dir->GetID()) > 0)
RETURN_ERROR(B_NOT_ALLOWED);
// prepare the request
CreateFileRequest request;
request.volumeID = fID;
request.directoryID = dir->GetRemoteID();
request.name.SetTo(name);
request.openMode = openMode;
request.mode = mode;
// send the request
CreateFileReply* reply;
status_t error = SendRequest(fConnection, &request, &reply);
if (error != B_OK)
RETURN_ERROR(error);
ObjectDeleter<Request> replyDeleter(reply);
if (reply->error != B_OK)
RETURN_ERROR(reply->error);
// add/update the entry
vnode_id localID = -1;
EntryInfo* entryInfo = &reply->entryInfo;
while (true) {
// get an up to date entry info
WalkReply* walkReply = NULL;
if (!entryInfo) {
error = _Walk(reply->entryInfo.directoryID,
reply->entryInfo.name.GetString(), false, &walkReply);
if (error != B_OK)
break;
entryInfo = &walkReply->entryInfo;
}
ObjectDeleter<Request> walkReplyDeleter(walkReply);
AutoLocker<Locker> locker(fLock);
// check, if the info is obsolete
if (_IsObsoleteEntryInfo(*entryInfo)) {
entryInfo = NULL;
continue;
}
// load the entry
ShareDirEntry* entry;
error = _LoadEntry(dir, *entryInfo, &entry);
if (error == B_OK)
localID = entry->GetNode()->GetID();
break;
}
if (error == B_OK) {
Node* _node;
error = GetVNode(localID, &_node);
}
// set the results / close the handle on error
if (error == B_OK) {
*vnid = localID;
*cookie = (void*)reply->cookie;
} else
_Close(reply->cookie);
RETURN_ERROR(error);
}
// Open
status_t
ShareVolume::Open(Node* _node, int openMode, void** cookie)
{
ShareNode* node = dynamic_cast<ShareNode*>(_node);
// TODO: Allow opening the root node?
if (!_EnsureShareMounted())
return ERROR_NOT_CONNECTED;
// check the open mode
if (IsReadOnly()) {
if ((openMode & O_RWMASK) == O_WRONLY)
return B_PERMISSION_DENIED;
if (openMode & O_TRUNC)
return B_PERMISSION_DENIED;
if ((openMode & O_RWMASK) == O_RDWR)
openMode = (openMode & ~O_RWMASK) | O_RDONLY;
}
// prepare the request
OpenRequest request;
request.volumeID = fID;
request.nodeID = node->GetRemoteID();
request.openMode = openMode;
// send the request
OpenReply* reply;
status_t error = SendRequest(fConnection, &request, &reply);
if (error != B_OK)
RETURN_ERROR(error);
ObjectDeleter<Request> replyDeleter(reply);
if (reply->error != B_OK)
RETURN_ERROR(reply->error);
// update the node
_UpdateNode(reply->nodeInfo);
*cookie = (void*)reply->cookie;
return B_OK;
}
// Close
status_t
ShareVolume::Close(Node* _node, void* cookie)
{
// no-op: FreeCookie does the job
return B_OK;
}
// FreeCookie
status_t
ShareVolume::FreeCookie(Node* _node, void* cookie)
{
return _Close((intptr_t)cookie);
}
// Read
status_t
ShareVolume::Read(Node* _node, void* cookie, off_t pos, void* _buffer,
size_t bufferSize, size_t* _bytesRead)
{
if (!_EnsureShareMounted())
return ERROR_NOT_CONNECTED;
*_bytesRead = 0;
if (bufferSize == 0)
return B_OK;
uint8* buffer = (uint8*)_buffer;
// prepare the request
ReadRequest request;
request.volumeID = fID;
request.cookie = (intptr_t)cookie;
request.pos = pos;
request.size = bufferSize;
struct ReadRequestHandler : public RequestHandler {
uint8* buffer;
off_t pos;
int32 bufferSize;
int32 bytesRead;
ReadRequestHandler(uint8* buffer, off_t pos, int32 bufferSize)
: buffer(buffer),
pos(pos),
bufferSize(bufferSize),
bytesRead(0)
{
}
virtual status_t HandleRequest(Request* _reply, RequestChannel* channel)
{
// the passed request is deleted by the request port
ReadReply* reply = dynamic_cast<ReadReply*>(_reply);
if (!reply)
RETURN_ERROR(B_BAD_DATA);
// process the reply
status_t error = ProcessReply(reply);
if (error != B_OK)
return error;
bool moreToCome = reply->moreToCome;
while (moreToCome) {
// receive a reply
error = ReceiveRequest(channel, &reply);
if (error != B_OK)
RETURN_ERROR(error);
moreToCome = reply->moreToCome;
ObjectDeleter<Request> replyDeleter(reply);
// process the reply
error = ProcessReply(reply);
if (error != B_OK)
return error;
}
return B_OK;
}
status_t ProcessReply(ReadReply* reply)
{
if (reply->error != B_OK)
RETURN_ERROR(reply->error);
// check the fields
if (reply->pos != pos)
RETURN_ERROR(B_BAD_DATA);
int32 bytesRead = reply->data.GetSize();
if (bytesRead > (int32)bufferSize)
RETURN_ERROR(B_BAD_DATA);
// copy the data into the buffer
if (bytesRead > 0)
memcpy(buffer, reply->data.GetData(), bytesRead);
pos += bytesRead;
buffer += bytesRead;
bufferSize -= bytesRead;
this->bytesRead += bytesRead;
return B_OK;
}
} requestHandler(buffer, pos, bufferSize);
// send the request
status_t error = fConnection->SendRequest(&request, &requestHandler);
if (error != B_OK)
RETURN_ERROR(error);
*_bytesRead = requestHandler.bytesRead;
return B_OK;
}
// Write
status_t
ShareVolume::Write(Node* _node, void* cookie, off_t pos, const void* _buffer,
size_t bufferSize, size_t* bytesWritten)
{
if (!_EnsureShareMounted())
return ERROR_NOT_CONNECTED;
// check permissions
if (IsReadOnly())
return B_PERMISSION_DENIED;
*bytesWritten = 0;
off_t bytesLeft = bufferSize;
const char* buffer = (const char*)_buffer;
while (bytesLeft > 0) {
off_t toWrite = bytesLeft;
if (toWrite > kMaxWriteBufferSize)
toWrite = kMaxWriteBufferSize;
// prepare the request
WriteRequest request;
request.volumeID = fID;
request.cookie = (intptr_t)cookie;
request.pos = pos;
request.data.SetTo(buffer, toWrite);
// send the request
WriteReply* reply;
status_t error = SendRequest(fConnection, &request, &reply);
if (error != B_OK)
RETURN_ERROR(error);
ObjectDeleter<Request> replyDeleter(reply);
if (reply->error != B_OK)
RETURN_ERROR(reply->error);
bytesLeft -= toWrite;
pos += toWrite;
buffer += toWrite;
// TODO: We should probably add an "up to date" flag for ShareNode (just as
// done for ShareAttrDir) and clear it at this point. Currently continuity
// inconsistencies could occur (e.g. a stat() after a write() returns
// obsolete data), depending on when the node monitoring update arrives.
}
*bytesWritten = bufferSize;
return B_OK;
}
// #pragma mark -
// #pragma mark ----- hard links / symlinks -----
// Link
status_t
ShareVolume::Link(Node* _dir, const char* name, Node* _node)
{
ShareNode* dir = dynamic_cast<ShareNode*>(_dir);
ShareNode* node = dynamic_cast<ShareNode*>(_node);
if (!node || node->GetVolume() != this)
return B_NOT_ALLOWED;
if (!_EnsureShareMounted())
return ERROR_NOT_CONNECTED;
// check permissions
if (IsReadOnly())
return B_PERMISSION_DENIED;
if (IsVNodeRemoved(dir->GetID()) > 0)
RETURN_ERROR(B_NOT_ALLOWED);
if (IsVNodeRemoved(node->GetID()) > 0)
RETURN_ERROR(B_NOT_ALLOWED);
// prepare the request
CreateLinkRequest request;
request.volumeID = fID;
request.directoryID = dir->GetRemoteID();
request.name.SetTo(name);
request.nodeID = node->GetRemoteID();
// send the request
CreateLinkReply* reply;
status_t error = SendRequest(fConnection, &request, &reply);
if (error != B_OK)
RETURN_ERROR(error);
ObjectDeleter<Request> replyDeleter(reply);
RETURN_ERROR(reply->error);
}
// Unlink
status_t
ShareVolume::Unlink(Node* _dir, const char* name)
{
ShareNode* dir = dynamic_cast<ShareNode*>(_dir);
if (!_EnsureShareMounted())
return ERROR_NOT_CONNECTED;
// check permissions
if (IsReadOnly())
return B_PERMISSION_DENIED;
// prepare the request
UnlinkRequest request;
request.volumeID = fID;
request.directoryID = dir->GetRemoteID();
request.name.SetTo(name);
// send the request
UnlinkReply* reply;
status_t error = SendRequest(fConnection, &request, &reply);
if (error != B_OK)
RETURN_ERROR(error);
ObjectDeleter<Request> replyDeleter(reply);
RETURN_ERROR(reply->error);
}
// Symlink
status_t
ShareVolume::Symlink(Node* _dir, const char* name, const char* target)
{
ShareNode* dir = dynamic_cast<ShareNode*>(_dir);
if (!_EnsureShareMounted())
return ERROR_NOT_CONNECTED;
// check permissions
if (IsReadOnly())
return B_PERMISSION_DENIED;
if (IsVNodeRemoved(dir->GetID()) > 0)
RETURN_ERROR(B_NOT_ALLOWED);
// prepare the request
CreateSymlinkRequest request;
request.volumeID = fID;
request.directoryID = dir->GetRemoteID();
request.name.SetTo(name);
request.target.SetTo(target);
// send the request
CreateSymlinkReply* reply;
status_t error = SendRequest(fConnection, &request, &reply);
if (error != B_OK)
RETURN_ERROR(error);
ObjectDeleter<Request> replyDeleter(reply);
RETURN_ERROR(reply->error);
}
// ReadLink
status_t
ShareVolume::ReadLink(Node* _node, char* buffer, size_t bufferSize,
size_t* bytesRead)
{
ShareNode* node = dynamic_cast<ShareNode*>(_node);
if (!_EnsureShareMounted())
return ERROR_NOT_CONNECTED;
*bytesRead = 0;
// prepare the request
ReadLinkRequest request;
request.volumeID = fID;
request.nodeID = node->GetRemoteID();
request.maxSize = bufferSize;
// send the request
ReadLinkReply* reply;
status_t error = SendRequest(fConnection, &request, &reply);
if (error != B_OK)
RETURN_ERROR(error);
ObjectDeleter<Request> replyDeleter(reply);
if (reply->error != B_OK)
RETURN_ERROR(reply->error);
if (reply->data.GetSize() > (int32)bufferSize)
RETURN_ERROR(B_BAD_DATA);
*bytesRead = reply->data.GetSize();
if (*bytesRead > 0)
memcpy(buffer, reply->data.GetData(), *bytesRead);
_UpdateNode(reply->nodeInfo);
return B_OK;
}
// Rename
status_t
ShareVolume::Rename(Node* _oldDir, const char* oldName, Node* _newDir,
const char* newName)
{
ShareNode* oldDir = dynamic_cast<ShareNode*>(_oldDir);
ShareNode* newDir = dynamic_cast<ShareNode*>(_newDir);
if (!newDir || newDir->GetVolume() != this)
return B_NOT_ALLOWED;
if (!_EnsureShareMounted())
return ERROR_NOT_CONNECTED;
// check permissions
if (IsReadOnly())
return B_PERMISSION_DENIED;
if (IsVNodeRemoved(newDir->GetID()) > 0)
RETURN_ERROR(B_NOT_ALLOWED);
// prepare the request
RenameRequest request;
request.volumeID = fID;
request.oldDirectoryID = oldDir->GetRemoteID();
request.oldName.SetTo(oldName);
request.newDirectoryID = newDir->GetRemoteID();
request.newName.SetTo(newName);
// send the request
RenameReply* reply;
status_t error = SendRequest(fConnection, &request, &reply);
if (error != B_OK)
RETURN_ERROR(error);
ObjectDeleter<Request> replyDeleter(reply);
RETURN_ERROR(reply->error);
}
// #pragma mark -
// #pragma mark ----- directories -----
// MkDir
status_t
ShareVolume::MkDir(Node* _dir, const char* name, int mode)
{
ShareNode* dir = dynamic_cast<ShareNode*>(_dir);
if (!_EnsureShareMounted())
return ERROR_NOT_CONNECTED;
// check permissions
if (IsReadOnly())
return B_PERMISSION_DENIED;
if (IsVNodeRemoved(dir->GetID()) > 0)
RETURN_ERROR(B_NOT_ALLOWED);
// prepare the request
MakeDirRequest request;
request.volumeID = fID;
request.directoryID = dir->GetRemoteID();
request.name.SetTo(name);
request.mode = mode;
// send the request
MakeDirReply* reply;
status_t error = SendRequest(fConnection, &request, &reply);
if (error != B_OK)
RETURN_ERROR(error);
ObjectDeleter<Request> replyDeleter(reply);
RETURN_ERROR(reply->error);
}
// RmDir
status_t
ShareVolume::RmDir(Node* _dir, const char* name)
{
ShareNode* dir = dynamic_cast<ShareNode*>(_dir);
if (!_EnsureShareMounted())
return ERROR_NOT_CONNECTED;
// check permissions
if (IsReadOnly())
return B_PERMISSION_DENIED;
// prepare the request
RemoveDirRequest request;
request.volumeID = fID;
request.directoryID = dir->GetRemoteID();
request.name.SetTo(name);
// send the request
RemoveDirReply* reply;
status_t error = SendRequest(fConnection, &request, &reply);
if (error != B_OK)
RETURN_ERROR(error);
ObjectDeleter<Request> replyDeleter(reply);
RETURN_ERROR(reply->error);
}
// OpenDir
status_t
ShareVolume::OpenDir(Node* _node, void** _cookie)
{
// we opendir() only directories
ShareDir* node = dynamic_cast<ShareDir*>(_node);
if (!node)
return B_NOT_ALLOWED;
// TODO: Allow opening the root node?
if (!_EnsureShareMounted())
return ERROR_NOT_CONNECTED;
// allocate a dir cookie
DirCookie* cookie = new(std::nothrow) DirCookie;
if (!cookie)
RETURN_ERROR(B_NO_MEMORY);
ObjectDeleter<DirCookie> cookieDeleter(cookie);
// if the directory is fully cached, we allocate a local iterator
{
AutoLocker<Locker> locker(fLock);
if (node->IsComplete()) {
// create a local dir iterator
LocalShareDirIterator* iterator
= new(std::nothrow) LocalShareDirIterator();
if (!iterator)
RETURN_ERROR(B_NO_MEMORY);
iterator->SetDirectory(node);
// init the cookie
cookie->iterator = iterator;
*_cookie = cookie;
cookieDeleter.Detach();
return B_OK;
}
}
// allocate a remote dir iterator
RemoteShareDirIterator* iterator = new(std::nothrow) RemoteShareDirIterator;
if (!iterator)
RETURN_ERROR(B_NO_MEMORY);
ObjectDeleter<RemoteShareDirIterator> iteratorDeleter(iterator);
// prepare the request
OpenDirRequest request;
request.volumeID = fID;
request.nodeID = node->GetRemoteID();
// send the request
OpenDirReply* reply;
status_t error = SendRequest(fConnection, &request, &reply);
if (error != B_OK)
RETURN_ERROR(error);
ObjectDeleter<Request> replyDeleter(reply);
if (reply->error != B_OK)
{
PRINT("OpenDir() failed: node: %" B_PRIdINO ", remote: (%" B_PRIdDEV
", %" B_PRIdINO ")\n", node->GetID(), node->GetRemoteID().volumeID,
node->GetRemoteID().nodeID);
RETURN_ERROR(reply->error);
}
// update the node
_UpdateNode(reply->nodeInfo);
// init the cookie
iterator->SetCookie(reply->cookie);
cookie->iterator = iterator;
*_cookie = cookie;
cookieDeleter.Detach();
iteratorDeleter.Detach();
return B_OK;
}
// CloseDir
status_t
ShareVolume::CloseDir(Node* _node, void* cookie)
{
// no-op: FreeDirCookie does the job
return B_OK;
}
// FreeDirCookie
status_t
ShareVolume::FreeDirCookie(Node* _node, void* _cookie)
{
DirCookie* cookie = (DirCookie*)_cookie;
ObjectDeleter<DirCookie> _(cookie);
ShareDirIterator* iterator = cookie->iterator;
status_t error = B_OK;
if (RemoteShareDirIterator* remoteIterator
= dynamic_cast<RemoteShareDirIterator*>(iterator)) {
// prepare the request
CloseRequest request;
request.volumeID = fID;
request.cookie = remoteIterator->GetCookie();
// send the request
if (!_EnsureShareMounted())
error = ERROR_NOT_CONNECTED;
if (error == B_OK) {
CloseReply* reply;
error = SendRequest(fConnection, &request, &reply);
if (error == B_OK) {
error = reply->error;
delete reply;
}
}
}
// delete the iterator
AutoLocker<Locker> locker(fLock);
delete iterator;
return error;
}
// ReadDir
status_t
ShareVolume::ReadDir(Node* _dir, void* _cookie, struct dirent* buffer,
size_t bufferSize, int32 count, int32* countRead)
{
ShareDir* directory = dynamic_cast<ShareDir*>(_dir);
if (!_EnsureShareMounted())
return ERROR_NOT_CONNECTED;
*countRead = 0;
if (count <= 0)
return B_OK;
DirCookie* cookie = (DirCookie*)_cookie;
ShareDirIterator* iterator = cookie->iterator;
while (true) {
status_t error;
AutoLocker<Locker> locker(fLock);
while (ShareDirEntry* entry = iterator->GetCurrentEntry()) {
// re-get the entry -- it might already have been removed
const char* name = entry->GetName();
entry = _GetEntryByLocalID(directory->GetID(), name);
if (entry) {
// set the name: this also checks the size of the buffer
error = set_dirent_name(buffer, bufferSize, name,
strlen(name));
if (error != B_OK) {
// if something has been read at all, we're content
if (*countRead > 0)
return B_OK;
RETURN_ERROR(error);
}
// fill in the other fields
buffer->d_pdev = fVolumeManager->GetID();
buffer->d_pino = directory->GetID();
buffer->d_dev = fVolumeManager->GetID();
buffer->d_ino = entry->GetNode()->GetID();
// if the entry is the parent of the share root, we need to
// fix the node ID
if (directory == fRootNode && strcmp(name, "..") == 0) {
if (Volume* parentVolume = GetParentVolume())
buffer->d_ino = parentVolume->GetRootID();
}
iterator->NextEntry();
(*countRead)++;
if (*countRead >= count || !next_dirent(buffer, bufferSize))
return B_OK;
} else
iterator->NextEntry();
}
// no more entries: check, if we're completely through
if (iterator->IsDone())
return B_OK;
// we need to actually get entries from the server
locker.Unlock();
if (RemoteShareDirIterator* remoteIterator
= dynamic_cast<RemoteShareDirIterator*>(iterator)) {
error = _ReadRemoteDir(directory, remoteIterator);
if (error != B_OK)
return error;
}
}
}
// RewindDir
status_t
ShareVolume::RewindDir(Node* _node, void* _cookie)
{
DirCookie* cookie = (DirCookie*)_cookie;
ShareDirIterator* iterator = cookie->iterator;
AutoLocker<Locker> _(fLock);
iterator->Rewind();
return B_OK;
}
// Walk
status_t
ShareVolume::Walk(Node* _dir, const char* entryName, char** resolvedPath,
vnode_id* vnid)
{
ShareDir* dir = dynamic_cast<ShareDir*>(_dir);
if (!dir)
return B_BAD_VALUE;
// we always resolve "." and ".." of the root node
if (dir == fRootNode) {
if (strcmp(entryName, ".") == 0 || strcmp(entryName, "..") == 0) {
AutoLocker<Locker> _(fLock);
if (strcmp(entryName, ".") == 0) {
*vnid = fRootNode->GetID();
} else if (Volume* parentVolume = GetParentVolume()) {
*vnid = parentVolume->GetRootID();
} else
*vnid = fRootNode->GetID();
Node* node;
return GetVNode(*vnid, &node);
}
}
if (!_EnsureShareMounted())
return ERROR_NOT_CONNECTED;
// check, if the entry is already known
{
AutoLocker<Locker> _(fLock);
ShareDirEntry* entry = _GetEntryByLocalID(dir->GetID(), entryName);
if (entry) {
*vnid = entry->GetNode()->GetID();
Node* node;
return GetVNode(*vnid, &node);
} else if (dir->IsComplete())
return B_ENTRY_NOT_FOUND;
}
WalkReply* reply;
while (true) {
// send the request
status_t error = _Walk(dir->GetRemoteID(), entryName, resolvedPath,
&reply);
if (error != B_OK)
RETURN_ERROR(error);
ObjectDeleter<Request> replyDeleter(reply);
AutoLocker<Locker> locker(fLock);
// check, if the returned info is obsolete
if (_IsObsoleteEntryInfo(reply->entryInfo))
continue;
// load the entry
ShareDirEntry* entry;
error = _LoadEntry(dir, reply->entryInfo, &entry);
if (error != B_OK)
RETURN_ERROR(error);
*vnid = entry->GetNode()->GetID();
// deal with symlinks
if (reply->linkPath.GetString() && resolvedPath) {
*resolvedPath = strdup(reply->linkPath.GetString());
if (!*resolvedPath)
RETURN_ERROR(B_NO_MEMORY);
return B_OK;
}
break;
}
// no symlink or we shall not resolve it: get the node
Node* _node;
RETURN_ERROR(GetVNode(*vnid, &_node));
}
// #pragma mark -
// #pragma mark ----- attributes -----
// OpenAttrDir
status_t
ShareVolume::OpenAttrDir(Node* _node, void** _cookie)
{
ShareNode* node = dynamic_cast<ShareNode*>(_node);
// TODO: Allow opening the root node?
if (!_EnsureShareMounted())
return ERROR_NOT_CONNECTED;
// allocate a dir cookie
AttrDirCookie* cookie = new(std::nothrow) AttrDirCookie;
if (!cookie)
RETURN_ERROR(B_NO_MEMORY);
ObjectDeleter<AttrDirCookie> cookieDeleter(cookie);
AutoLocker<Locker> locker(fLock);
if (!node->GetAttrDir() || !node->GetAttrDir()->IsUpToDate()) {
// prepare the request
OpenAttrDirRequest request;
request.volumeID = fID;
request.nodeID = node->GetRemoteID();
locker.Unlock();
// send the request
OpenAttrDirReply* reply;
status_t error = SendRequest(fConnection, &request, &reply);
if (error != B_OK)
RETURN_ERROR(error);
ObjectDeleter<Request> replyDeleter(reply);
if (reply->error != B_OK)
RETURN_ERROR(reply->error);
// If no AttrDirInfo was supplied, we just save the cookie and be done.
// This usually happens when the attr dir is too big to be cached.
if (!reply->attrDirInfo.isValid) {
cookie->cookie = reply->cookie;
cookie->rewind = false;
*_cookie = cookie;
cookieDeleter.Detach();
return B_OK;
}
locker.SetTo(fLock, false);
// a AttrDirInfo has been supplied: load the attr dir
error = _LoadAttrDir(node, reply->attrDirInfo);
if (error != B_OK)
return error;
}
// we have a valid attr dir: create an attr dir iterator
ShareAttrDirIterator* iterator = new(std::nothrow) ShareAttrDirIterator;
if (!iterator)
return B_NO_MEMORY;
iterator->SetAttrDir(node->GetAttrDir());
// add the iterator
status_t error = _AddAttrDirIterator(node, iterator);
if (error != B_OK) {
delete iterator;
return error;
}
cookie->iterator = iterator;
*_cookie = cookie;
cookieDeleter.Detach();
return B_OK;
}
// CloseAttrDir
status_t
ShareVolume::CloseAttrDir(Node* _node, void* cookie)
{
// no-op: FreeAttrDirCookie does the job
return B_OK;
}
// FreeAttrDirCookie
status_t
ShareVolume::FreeAttrDirCookie(Node* _node, void* _cookie)
{
ShareNode* node = dynamic_cast<ShareNode*>(_node);
AttrDirCookie* cookie = (AttrDirCookie*)_cookie;
ObjectDeleter<AttrDirCookie> _(cookie);
// if this is a local iterator, we just delete it and be done
if (cookie->iterator) {
AutoLocker<Locker> locker(fLock);
// remove and delete the iterator
_RemoveAttrDirIterator(node, cookie->iterator);
delete cookie->iterator;
return B_OK;
}
// prepare the request
CloseRequest request;
request.volumeID = fID;
request.cookie = cookie->cookie;
// send the request
if (!_EnsureShareMounted())
return ERROR_NOT_CONNECTED;
CloseReply* reply;
status_t error = SendRequest(fConnection, &request, &reply);
if (error != B_OK)
RETURN_ERROR(error);
ObjectDeleter<Request> replyDeleter(reply);
if (reply->error != B_OK)
RETURN_ERROR(reply->error);
return B_OK;
}
// ReadAttrDir
status_t
ShareVolume::ReadAttrDir(Node* _node, void* _cookie, struct dirent* buffer,
size_t bufferSize, int32 count, int32* countRead)
{
if (!_EnsureShareMounted())
return ERROR_NOT_CONNECTED;
*countRead = 0;
AttrDirCookie* cookie = (AttrDirCookie*)_cookie;
// if we have a local iterator, things are easy
if (ShareAttrDirIterator* iterator = cookie->iterator) {
AutoLocker<Locker> locker(fLock);
// get the current attribute
Attribute* attribute = iterator->GetCurrentAttribute();
if (!attribute)
return B_OK;
// set the name: this also checks the size of the buffer
const char* name = attribute->GetName();
status_t error = set_dirent_name(buffer, bufferSize, name,
strlen(name));
if (error != B_OK)
RETURN_ERROR(error);
// fill in the other fields
buffer->d_dev = fVolumeManager->GetID();
buffer->d_ino = -1;
*countRead = 1;
iterator->NextAttribute();
return B_OK;
}
// prepare the request
ReadAttrDirRequest request;
request.volumeID = fID;
request.cookie = cookie->cookie;
request.count = 1;
request.rewind = cookie->rewind;
// send the request
ReadAttrDirReply* reply;
status_t error = SendRequest(fConnection, &request, &reply);
if (error != B_OK)
RETURN_ERROR(error);
ObjectDeleter<Request> replyDeleter(reply);
if (reply->error != B_OK)
RETURN_ERROR(reply->error);
cookie->rewind = false;
// check, if anything has been read at all
if (reply->count == 0) {
*countRead = 0;
return B_OK;
}
const char* name = reply->name.GetString();
int32 nameLen = reply->name.GetSize();
if (!name || nameLen < 2)
return B_BAD_DATA;
// set the name: this also checks the size of the buffer
error = set_dirent_name(buffer, bufferSize, name, nameLen - 1);
if (error != B_OK)
RETURN_ERROR(error);
// fill in the other fields
buffer->d_dev = fVolumeManager->GetID();
buffer->d_ino = -1;
*countRead = 1;
return B_OK;
}
// RewindAttrDir
status_t
ShareVolume::RewindAttrDir(Node* _node, void* _cookie)
{
AttrDirCookie* cookie = (AttrDirCookie*)_cookie;
// if we have a local iterator, rewind it
if (ShareAttrDirIterator* iterator = cookie->iterator) {
AutoLocker<Locker> locker(fLock);
iterator->Rewind();
} else
cookie->rewind = true;
return B_OK;
}
// ReadAttr
status_t
ShareVolume::ReadAttr(Node* _node, const char* name, int type, off_t pos,
void* _buffer, size_t bufferSize, size_t* _bytesRead)
{
ShareNode* node = dynamic_cast<ShareNode*>(_node);
if (!_EnsureShareMounted())
return ERROR_NOT_CONNECTED;
*_bytesRead = 0;
if (bufferSize == 0)
return B_OK;
uint8* buffer = (uint8*)_buffer;
// if we have the attribute directory cached, we can first check, if the
// attribute exists at all -- maybe its data are cached, too
{
AutoLocker<Locker> locker(fLock);
ShareAttrDir* attrDir = node->GetAttrDir();
if (attrDir && attrDir->IsUpToDate()) {
// get the attribute
Attribute* attribute = attrDir->GetAttribute(name);
if (!attribute)
return B_ENTRY_NOT_FOUND;
// get the data
if (const void* data = attribute->GetData()) {
// first check, if we can read anything at all
if (pos < 0)
pos = 0;
int32 size = attribute->GetSize();
if (pos >= size)
return B_OK;
// get the data
bufferSize = min(bufferSize, size_t(size - pos));
memcpy(buffer, data, bufferSize);
*_bytesRead = bufferSize;
return B_OK;
}
}
}
// prepare the request
ReadAttrRequest request;
request.volumeID = fID;
request.nodeID = node->GetRemoteID();
request.name.SetTo(name);
request.type = type;
request.pos = pos;
request.size = bufferSize;
struct ReadRequestHandler : public RequestHandler {
uint8* buffer;
off_t pos;
int32 bufferSize;
int32 bytesRead;
ReadRequestHandler(uint8* buffer, off_t pos, int32 bufferSize)
: buffer(buffer),
pos(pos),
bufferSize(bufferSize),
bytesRead(0)
{
}
virtual status_t HandleRequest(Request* _reply, RequestChannel* channel)
{
// the passed request is deleted by the request port
ReadAttrReply* reply = dynamic_cast<ReadAttrReply*>(_reply);
if (!reply)
RETURN_ERROR(B_BAD_DATA);
// process the reply
status_t error = ProcessReply(reply);
if (error != B_OK)
return error;
bool moreToCome = reply->moreToCome;
while (moreToCome) {
// receive a reply
error = ReceiveRequest(channel, &reply);
if (error != B_OK)
RETURN_ERROR(error);
moreToCome = reply->moreToCome;
ObjectDeleter<Request> replyDeleter(reply);
// process the reply
error = ProcessReply(reply);
if (error != B_OK)
return error;
}
return B_OK;
}
status_t ProcessReply(ReadAttrReply* reply)
{
if (reply->error != B_OK)
RETURN_ERROR(reply->error);
// check the fields
if (reply->pos != pos)
RETURN_ERROR(B_BAD_DATA);
int32 bytesRead = reply->data.GetSize();
if (bytesRead > (int32)bufferSize)
RETURN_ERROR(B_BAD_DATA);
// copy the data into the buffer
if (bytesRead > 0)
memcpy(buffer, reply->data.GetData(), bytesRead);
pos += bytesRead;
buffer += bytesRead;
bufferSize -= bytesRead;
this->bytesRead += bytesRead;
return B_OK;
}
} requestHandler(buffer, pos, bufferSize);
// send the request
status_t error = fConnection->SendRequest(&request, &requestHandler);
if (error != B_OK)
RETURN_ERROR(error);
*_bytesRead = requestHandler.bytesRead;
return B_OK;
}
// WriteAttr
status_t
ShareVolume::WriteAttr(Node* _node, const char* name, int type, off_t pos,
const void* _buffer, size_t bufferSize, size_t* bytesWritten)
{
ShareNode* node = dynamic_cast<ShareNode*>(_node);
if (!_EnsureShareMounted())
return ERROR_NOT_CONNECTED;
// check permissions
if (IsReadOnly())
return B_PERMISSION_DENIED;
*bytesWritten = 0;
off_t bytesLeft = bufferSize;
const char* buffer = (const char*)_buffer;
while (bytesLeft > 0) {
// store the current attibute dir revision for reference below
int64 attrDirRevision = -1;
{
AutoLocker<Locker> _(fLock);
if (ShareAttrDir* attrDir = node->GetAttrDir()) {
if (attrDir->IsUpToDate())
attrDirRevision = attrDir->GetRevision();
}
}
off_t toWrite = bytesLeft;
if (toWrite > kMaxWriteBufferSize)
toWrite = kMaxWriteBufferSize;
// prepare the request
WriteAttrRequest request;
request.volumeID = fID;
request.nodeID = node->GetRemoteID();
request.name.SetTo(name);
request.type = type;
request.pos = pos;
request.data.SetTo(buffer, toWrite);
// send the request
WriteAttrReply* reply;
status_t error = SendRequest(fConnection, &request, &reply);
if (error != B_OK)
RETURN_ERROR(error);
ObjectDeleter<Request> replyDeleter(reply);
if (reply->error != B_OK)
RETURN_ERROR(reply->error);
bytesLeft -= toWrite;
pos += toWrite;
buffer += toWrite;
// If the request was successful, we consider the cached attr dir
// no longer up to date.
if (attrDirRevision >= 0) {
AutoLocker<Locker> _(fLock);
ShareAttrDir* attrDir = node->GetAttrDir();
if (attrDir && attrDir->GetRevision() == attrDirRevision)
attrDir->SetUpToDate(false);
}
}
*bytesWritten = bufferSize;
return B_OK;
}
// RemoveAttr
status_t
ShareVolume::RemoveAttr(Node* _node, const char* name)
{
ShareNode* node = dynamic_cast<ShareNode*>(_node);
if (!_EnsureShareMounted())
return ERROR_NOT_CONNECTED;
// check permissions
if (IsReadOnly())
return B_PERMISSION_DENIED;
// store the current attibute dir revision for reference below
int64 attrDirRevision = -1;
{
AutoLocker<Locker> _(fLock);
if (ShareAttrDir* attrDir = node->GetAttrDir()) {
if (attrDir->IsUpToDate())
attrDirRevision = attrDir->GetRevision();
}
}
// prepare the request
RemoveAttrRequest request;
request.volumeID = fID;
request.nodeID = node->GetRemoteID();
request.name.SetTo(name);
// send the request
RemoveAttrReply* reply;
status_t error = SendRequest(fConnection, &request, &reply);
if (error != B_OK)
RETURN_ERROR(error);
ObjectDeleter<Request> replyDeleter(reply);
// If the request was successful, we consider the cached attr dir
// no longer up to date.
if (reply->error == B_OK && attrDirRevision >= 0) {
AutoLocker<Locker> _(fLock);
ShareAttrDir* attrDir = node->GetAttrDir();
if (attrDir && attrDir->GetRevision() == attrDirRevision)
attrDir->SetUpToDate(false);
}
RETURN_ERROR(reply->error);
}
// RenameAttr
status_t
ShareVolume::RenameAttr(Node* _node, const char* oldName, const char* newName)
{
ShareNode* node = dynamic_cast<ShareNode*>(_node);
if (!_EnsureShareMounted())
return ERROR_NOT_CONNECTED;
// check permissions
if (IsReadOnly())
return B_PERMISSION_DENIED;
// store the current attibute dir revision for reference below
int64 attrDirRevision = -1;
{
AutoLocker<Locker> _(fLock);
if (ShareAttrDir* attrDir = node->GetAttrDir()) {
if (attrDir->IsUpToDate())
attrDirRevision = attrDir->GetRevision();
}
}
// prepare the request
RenameAttrRequest request;
request.volumeID = fID;
request.nodeID = node->GetRemoteID();
request.oldName.SetTo(oldName);
request.newName.SetTo(newName);
// send the request
RenameAttrReply* reply;
status_t error = SendRequest(fConnection, &request, &reply);
if (error != B_OK)
RETURN_ERROR(error);
ObjectDeleter<Request> replyDeleter(reply);
// If the request was successful, we consider the cached attr dir
// no longer up to date.
if (reply->error == B_OK && attrDirRevision >= 0) {
AutoLocker<Locker> _(fLock);
ShareAttrDir* attrDir = node->GetAttrDir();
if (attrDir && attrDir->GetRevision() == attrDirRevision)
attrDir->SetUpToDate(false);
}
RETURN_ERROR(reply->error);
}
// StatAttr
status_t
ShareVolume::StatAttr(Node* _node, const char* name, struct attr_info* attrInfo)
{
ShareNode* node = dynamic_cast<ShareNode*>(_node);
if (!_EnsureShareMounted())
return ERROR_NOT_CONNECTED;
// if we have the attribute directory cached, get the info from there
{
AutoLocker<Locker> locker(fLock);
ShareAttrDir* attrDir = node->GetAttrDir();
if (attrDir && attrDir->IsUpToDate()) {
// get the attribute
Attribute* attribute = attrDir->GetAttribute(name);
if (!attribute)
return B_ENTRY_NOT_FOUND;
attribute->GetInfo(attrInfo);
return B_OK;
}
}
// prepare the request
StatAttrRequest request;
request.volumeID = fID;
request.nodeID = node->GetRemoteID();
request.name.SetTo(name);
// send the request
StatAttrReply* reply;
status_t error = SendRequest(fConnection, &request, &reply);
if (error != B_OK)
RETURN_ERROR(error);
ObjectDeleter<Request> replyDeleter(reply);
if (reply->error != B_OK)
RETURN_ERROR(reply->error);
// set the result
*attrInfo = reply->attrInfo.info;
return B_OK;
}
// #pragma mark -
// #pragma mark ----- queries -----
// GetQueryEntry
status_t
ShareVolume::GetQueryEntry(const EntryInfo& entryInfo,
const NodeInfo& dirInfo, struct dirent* buffer, size_t bufferSize,
int32* countRead)
{
status_t error = B_OK;
const char* name = entryInfo.name.GetString();
int32 nameLen = entryInfo.name.GetSize();
if (!name || nameLen < 2)
RETURN_ERROR(B_BAD_DATA);
// load the directory
vnode_id localDirID = -1;
{
AutoLocker<Locker> _(fLock);
ShareNode* node;
error = _LoadNode(dirInfo, &node);
if (error != B_OK)
RETURN_ERROR(error);
ShareDir* directory = dynamic_cast<ShareDir*>(node);
if (!directory)
RETURN_ERROR(B_ENTRY_NOT_FOUND);
localDirID = directory->GetID();
}
// add/update the entry
vnode_id localNodeID = -1;
const EntryInfo* resolvedEntryInfo = &entryInfo;
while (error == B_OK) {
// get an up to date entry info
WalkReply* walkReply = NULL;
if (!resolvedEntryInfo) {
error = _Walk(entryInfo.directoryID, name, false,
&walkReply);
if (error != B_OK)
RETURN_ERROR(error);
resolvedEntryInfo = &walkReply->entryInfo;
}
ObjectDeleter<Request> walkReplyDeleter(walkReply);
AutoLocker<Locker> locker(fLock);
// check, if the info is obsolete
if (_IsObsoleteEntryInfo(*resolvedEntryInfo)) {
resolvedEntryInfo = NULL;
continue;
}
// get the directory
ShareDir* dir = dynamic_cast<ShareDir*>(_GetNodeByLocalID(localDirID));
if (!dir)
RETURN_ERROR(B_ERROR);
// load the entry
ShareDirEntry* entry;
error = _LoadEntry(dir, *resolvedEntryInfo, &entry);
if (error == B_OK)
localNodeID = entry->GetNode()->GetID();
break;
}
// set the name: this also checks the size of the buffer
error = set_dirent_name(buffer, bufferSize, name, nameLen - 1);
if (error != B_OK)
RETURN_ERROR(error);
// fill in the other fields
buffer->d_pdev = fVolumeManager->GetID();
buffer->d_pino = localDirID;
buffer->d_dev = fVolumeManager->GetID();
buffer->d_ino = localNodeID;
*countRead = 1;
PRINT(" entry: d_dev: %" B_PRIdDEV ", d_pdev: %" B_PRIdDEV ", d_ino: %"
B_PRIdINO ", d_pino: %" B_PRIdINO ", d_reclen: %hu, d_name: `%s'\n",
buffer->d_dev, buffer->d_pdev, buffer->d_ino, buffer->d_pino,
buffer->d_reclen, buffer->d_name);
return B_OK;
}
// #pragma mark -
// ProcessNodeMonitoringRequest
void
ShareVolume::ProcessNodeMonitoringRequest(NodeMonitoringRequest* request)
{
switch (request->opcode) {
case B_ENTRY_CREATED:
_HandleEntryCreatedRequest(
dynamic_cast<EntryCreatedRequest*>(request));
break;
case B_ENTRY_REMOVED:
_HandleEntryRemovedRequest(
dynamic_cast<EntryRemovedRequest*>(request));
break;
case B_ENTRY_MOVED:
_HandleEntryMovedRequest(
dynamic_cast<EntryMovedRequest*>(request));
break;
case B_STAT_CHANGED:
_HandleStatChangedRequest(
dynamic_cast<StatChangedRequest*>(request));
break;
case B_ATTR_CHANGED:
_HandleAttributeChangedRequest(
dynamic_cast<AttributeChangedRequest*>(request));
break;
}
}
// ConnectionClosed
void
ShareVolume::ConnectionClosed()
{
AutoLocker<Locker> _(fMountLock);
fConnectionState = CONNECTION_CLOSED;
}
// #pragma mark -
// _ReadRemoteDir
status_t
ShareVolume::_ReadRemoteDir(ShareDir* directory,
RemoteShareDirIterator* iterator)
{
// prepare the request
fLock.Lock();
ReadDirRequest request;
request.volumeID = fID;
request.cookie = iterator->GetCookie();
request.count = iterator->GetCapacity();
request.rewind = iterator->GetRewind();
fLock.Unlock();
// send the request
ReadDirReply* reply;
status_t error = SendRequest(fConnection, &request, &reply);
if (error != B_OK)
RETURN_ERROR(error);
ObjectDeleter<Request> replyDeleter(reply);
if (reply->error != B_OK)
RETURN_ERROR(reply->error);
RequestMemberArray<EntryInfo>* entryInfos = &reply->entryInfos;
while (true) {
// get up to date entry infos
MultiWalkReply* walkReply = NULL;
if (!entryInfos) {
error = _MultiWalk(reply->entryInfos, &walkReply);
if (error != B_OK)
RETURN_ERROR(error);
entryInfos = &walkReply->entryInfos;
}
ObjectDeleter<Request> walkReplyDeleter(walkReply);
AutoLocker<Locker> locker(fLock);
// check, if any info is obsolete
int32 count = entryInfos->CountElements();
for (int32 i = 0; i < count; i++) {
const EntryInfo& entryInfo = entryInfos->GetElements()[i];
if (_IsObsoleteEntryInfo(entryInfo)) {
entryInfos = NULL;
continue;
}
}
// init the iterator's revision, if it's new or has been rewinded
if (request.rewind || iterator->GetRevision() < 0)
iterator->SetRevision(reply->revision);
iterator->Clear();
iterator->SetDone(reply->done);
// iterate through the entries
for (int32 i = 0; i < count; i++) {
const EntryInfo& entryInfo = entryInfos->GetElements()[i];
const char* name = entryInfo.name.GetString();
int32 nameLen = entryInfo.name.GetSize();
if (!name || nameLen < 2)
return B_BAD_DATA;
// load the node/entry
ShareDirEntry* entry;
error = _LoadEntry(directory, entryInfo, &entry);
if (error != B_OK)
RETURN_ERROR(error);
// add the entry
if (!iterator->AddEntry(entry)) {
ERROR("ShareVolume::_ReadRemoteDir(): ERROR: Failed to add "
"entry to remote iterator!\n");
}
}
// directory now complete?
if (reply->done && directory->GetEntryRemovedEventRevision()
< iterator->GetRevision()) {
directory->SetComplete(true);
}
break;
}
return B_OK;
}
// _HandleEntryCreatedRequest
void
ShareVolume::_HandleEntryCreatedRequest(EntryCreatedRequest* request)
{
if (!request)
return;
nspace_id nsid = fVolumeManager->GetID();
const char* name = request->name.GetString();
// translate the node IDs
vnode_id vnida = 0;
vnode_id vnidb = 0;
vnode_id vnidc = 0;
status_t error = _GetLocalNodeID(request->directoryID, &vnida, true);
if (error == B_OK)
error = _GetLocalNodeID(request->nodeID, &vnidc, true);
PRINT("ShareVolume::_HandleEntryCreatedRequest(): error: 0x%" B_PRIx32
", name: \"%s\", dir: %" B_PRIdINO " (remote: (%" B_PRIdDEV ", %"
B_PRIdINO ")), node: %" B_PRIdINO " (remote: (%" B_PRIdDEV ", %"
B_PRIdINO "))\n", error, name, vnida,
request->directoryID.volumeID, request->directoryID.nodeID, vnidc,
request->nodeID.volumeID, request->nodeID.nodeID);
// send notifications / do additional processing
if (request->queryUpdate) {
if (error == B_OK) {
SendNotification(request->port, request->token,
B_QUERY_UPDATE, request->opcode, nsid, 0, vnida, vnidb,
vnidc, name);
}
} else {
_EntryCreated(request->directoryID, name,
(request->entryInfoValid ? &request->entryInfo : NULL),
request->revision);
if (error == B_OK)
NotifyListener(request->opcode, nsid, vnida, vnidb, vnidc, name);
}
}
// _HandleEntryRemovedRequest
void
ShareVolume::_HandleEntryRemovedRequest(EntryRemovedRequest* request)
{
if (!request)
return;
nspace_id nsid = fVolumeManager->GetID();
const char* name = request->name.GetString();
// translate the node IDs
vnode_id vnida = 0;
vnode_id vnidb = 0;
vnode_id vnidc = 0;
status_t error = _GetLocalNodeID(request->directoryID, &vnida, true);
if (error == B_OK)
error = _GetLocalNodeID(request->nodeID, &vnidc, false);
// TODO: We don't enter a node ID mapping here, which might cause
// undesired behavior in some cases. Queries should usually be
// fine, since, if the entry was in the query set, then the
// respective node ID should definately be known at this point.
// If an entry is removed and the node monitoring event comes
// before a live query event for the same entry, the former one
// will cause the ID to be removed, so that it is already gone
// when the latter one arrives. I haven't observed this yet,
// though. The query events always seem to be sent before the
// node monitoring events (and the server processes them in a
// single-threaded way). I guess, this is FS implementation
// dependent, though.
// A node monitoring event that will always get lost, is when the
// FS user watches a directory that hasn't been read before. Its
// entries will not be known yet and thus "entry removed" events
// will be dropped here. I guess, it's arguable whether this is
// a practical problem (why should the watcher care that an entry
// has been removed, when they didn't know what entries were in
// the directory in the first place?).
// A possible solution would be to never remove node ID mappings.
// We would only need to take care that the cached node info is
// correctly flushed, so that e.g. a subsequent ReadVNode() has to
// ask the server and doesn't work with obsolete data. We would
// also enter a yet unknown ID into the node ID map here. The only
// problem is that the ID maps will keep growing, especially when
// there's a lot of FS activity on the server.
// send notifications / do additional processing
if (request->queryUpdate) {
if (error == B_OK) {
SendNotification(request->port, request->token,
B_QUERY_UPDATE, request->opcode, nsid, 0, vnida, vnidb,
vnidc, name);
}
} else {
_EntryRemoved(request->directoryID, name, request->revision);
_NodeRemoved(request->nodeID);
if (error == B_OK)
NotifyListener(request->opcode, nsid, vnida, vnidb, vnidc, name);
}
}
// _HandleEntryMovedRequest
void
ShareVolume::_HandleEntryMovedRequest(EntryMovedRequest* request)
{
if (!request)
return;
nspace_id nsid = fVolumeManager->GetID();
const char* oldName = request->fromName.GetString();
const char* name = request->toName.GetString();
// translate the node IDs
vnode_id vnida = 0;
vnode_id vnidb = 0;
vnode_id vnidc = 0;
status_t error = _GetLocalNodeID(request->fromDirectoryID, &vnida, true);
if (error == B_OK)
error = _GetLocalNodeID(request->toDirectoryID, &vnidb, true);
if (error == B_OK)
error = _GetLocalNodeID(request->nodeID, &vnidc, true);
// send notifications / do additional processing
if (!request->queryUpdate) {
_EntryMoved(request->fromDirectoryID, oldName, request->toDirectoryID,
name, (request->entryInfoValid ? &request->entryInfo : NULL),
request->revision);
if (error == B_OK)
NotifyListener(request->opcode, nsid, vnida, vnidb, vnidc, name);
}
}
// _HandleStatChangedRequest
void
ShareVolume::_HandleStatChangedRequest(StatChangedRequest* request)
{
if (!request)
return;
nspace_id nsid = fVolumeManager->GetID();
// translate the node IDs
vnode_id vnida = 0;
vnode_id vnidb = 0;
vnode_id vnidc = 0;
status_t error = _GetLocalNodeID(request->nodeID, &vnidc, true);
// send notifications / do additional processing
if (!request->queryUpdate) {
_UpdateNode(request->nodeInfo);
if (error == B_OK)
NotifyListener(request->opcode, nsid, vnida, vnidb, vnidc, NULL);
}
}
// _HandleAttributeChangedRequest
void
ShareVolume::_HandleAttributeChangedRequest(AttributeChangedRequest* request)
{
if (!request)
return;
nspace_id nsid = fVolumeManager->GetID();
const char* name = request->attrInfo.name.GetString();
// translate the node IDs
vnode_id vnida = 0;
vnode_id vnidb = 0;
vnode_id vnidc = 0;
status_t error = _GetLocalNodeID(request->nodeID, &vnidc, true);
// send notifications / do additional processing
if (!request->queryUpdate) {
_UpdateAttrDir(request->nodeID, request->attrDirInfo);
if (error == B_OK)
NotifyListener(request->opcode, nsid, vnida, vnidb, vnidc, name);
}
}
// _GetLocalNodeID
status_t
ShareVolume::_GetLocalNodeID(NodeID remoteID, ino_t* _localID, bool enter)
{
AutoLocker<Locker> _(fLock);
// if the ID is already know, just return it
if (fLocalNodeIDs->ContainsKey(remoteID)) {
*_localID = fLocalNodeIDs->Get(remoteID);
return B_OK;
}
// ID not yet known
// enter it? Do this only, if requested and we're not already unmounting.
if (!enter)
return B_ENTRY_NOT_FOUND;
if (fUnmounting)
return ERROR_NOT_CONNECTED;
// get a fresh ID from the volume manager
vnode_id localID = fVolumeManager->NewNodeID(this);
if (localID < 0)
return localID;
// put the IDs into local map
status_t error = fLocalNodeIDs->Put(remoteID, localID);
if (error != B_OK) {
fVolumeManager->RemoveNodeID(localID);
return error;
}
// put the IDs into remote map
error = fRemoteNodeIDs->Put(localID, remoteID);
if (error != B_OK) {
fLocalNodeIDs->Remove(remoteID);
fVolumeManager->RemoveNodeID(localID);
return error;
}
PRINT("ShareVolume(%" B_PRId32 "): added node ID mapping: local: %"
B_PRIdINO " -> remote: (%" B_PRIdDEV ", %" B_PRIdINO ")\n", fID,
localID, remoteID.volumeID, remoteID.nodeID);
*_localID = localID;
return B_OK;
}
// _GetRemoteNodeID
status_t
ShareVolume::_GetRemoteNodeID(ino_t localID, NodeID* remoteID)
{
AutoLocker<Locker> _(fLock);
// check, if the ID is known
if (!fRemoteNodeIDs->ContainsKey(localID))
return B_ENTRY_NOT_FOUND;
*remoteID = fRemoteNodeIDs->Get(localID);
return B_OK;
}
// _RemoveLocalNodeID
void
ShareVolume::_RemoveLocalNodeID(ino_t localID)
{
AutoLocker<Locker> _(fLock);
// check, if the ID is known
if (!fRemoteNodeIDs->ContainsKey(localID))
return;
// remove from ID maps
NodeID remoteID = fRemoteNodeIDs->Get(localID);
PRINT("ShareVolume::_RemoveLocalNodeID(%" B_PRIdINO "): remote: (%"
B_PRIdDEV ", %" B_PRIdINO ")\n", localID, remoteID.volumeID,
remoteID.nodeID);
fRemoteNodeIDs->Remove(localID);
fLocalNodeIDs->Remove(remoteID);
// remove from volume manager
fVolumeManager->RemoveNodeID(localID);
}
// _GetNodeByLocalID
ShareNode*
ShareVolume::_GetNodeByLocalID(ino_t localID)
{
AutoLocker<Locker> _(fLock);
return fNodes->Get(localID);
}
// _GetNodeByRemoteID
ShareNode*
ShareVolume::_GetNodeByRemoteID(NodeID remoteID)
{
AutoLocker<Locker> _(fLock);
ino_t localID;
if (_GetLocalNodeID(remoteID, &localID, false) == B_OK)
return fNodes->Get(localID);
return NULL;
}
// _LoadNode
status_t
ShareVolume::_LoadNode(const NodeInfo& nodeInfo, ShareNode** _node)
{
AutoLocker<Locker> _(fLock);
// check, if the node is already known
ShareNode* node = _GetNodeByRemoteID(nodeInfo.GetID());
if (node) {
node->Update(nodeInfo);
} else {
// don't load the node when already unmounting
if (fUnmounting)
return B_ERROR;
// get a local node ID
vnode_id localID;
status_t error = _GetLocalNodeID(nodeInfo.GetID(), &localID, true);
if (error != B_OK)
return error;
// create a new node
if (S_ISDIR(nodeInfo.st.st_mode))
node = new(std::nothrow) ShareDir(this, localID, &nodeInfo);
else
node = new(std::nothrow) ShareNode(this, localID, &nodeInfo);
if (!node) {
_RemoveLocalNodeID(localID);
return B_NO_MEMORY;
}
// add it
error = fNodes->Put(node->GetID(), node);
if (error != B_OK) {
_RemoveLocalNodeID(localID);
delete node;
return error;
}
PRINT("ShareVolume: added node: %" B_PRIdINO ": remote: (%" B_PRIdDEV
", %" B_PRIdINO "), localID: %" B_PRIdINO "\n", node->GetID(),
node->GetRemoteID().volumeID,
node->GetRemoteID().nodeID, localID);
}
if (_node)
*_node = node;
return B_OK;
}
// _UpdateNode
status_t
ShareVolume::_UpdateNode(const NodeInfo& nodeInfo)
{
AutoLocker<Locker> _(fLock);
if (fUnmounting)
return ERROR_NOT_CONNECTED;
ShareNode* node = _GetNodeByRemoteID(nodeInfo.GetID());
if (node) {
node->Update(nodeInfo);
return B_OK;
}
return B_ENTRY_NOT_FOUND;
}
// _GetEntryByLocalID
ShareDirEntry*
ShareVolume::_GetEntryByLocalID(ino_t localDirID, const char* name)
{
if (!name)
return NULL;
AutoLocker<Locker> _(fLock);
return fEntries->Get(EntryKey(localDirID, name));
}
// _GetEntryByRemoteID
ShareDirEntry*
ShareVolume::_GetEntryByRemoteID(NodeID remoteDirID, const char* name)
{
if (!name)
return NULL;
AutoLocker<Locker> _(fLock);
ino_t localDirID;
if (_GetLocalNodeID(remoteDirID, &localDirID, false) == B_OK)
return fEntries->Get(EntryKey(localDirID, name));
return NULL;
}
// _LoadEntry
//
// If _entry is supplied, fLock should be held, otherwise the returned entry
// might as well be deleted.
status_t
ShareVolume::_LoadEntry(ShareDir* directory, const EntryInfo& entryInfo,
ShareDirEntry** _entry)
{
const char* name = entryInfo.name.GetString();
if (!directory || !name)
return B_BAD_VALUE;
AutoLocker<Locker> _(fLock);
ShareDirEntry* entry = _GetEntryByLocalID(directory->GetID(), name);
if (entry) {
if (entryInfo.nodeInfo.revision > entry->GetRevision()) {
if (entryInfo.nodeInfo.GetID() != entry->GetNode()->GetRemoteID()) {
// The node the existing entry refers to is not the node it
// should refer to. Remove the old entry and create a new one.
_EntryRemoved(directory->GetRemoteID(), name,
entryInfo.nodeInfo.revision);
_EntryCreated(directory->GetRemoteID(), name, &entryInfo,
entryInfo.nodeInfo.revision);
// re-get the entry and check, if everything is fine
entry = _GetEntryByLocalID(directory->GetID(), name);
if (!entry)
return B_ERROR;
if (entryInfo.nodeInfo.GetID()
!= entry->GetNode()->GetRemoteID()) {
return B_ERROR;
}
} else {
entry->SetRevision(entryInfo.nodeInfo.revision);
_UpdateNode(entryInfo.nodeInfo);
}
}
} else {
// entry not known yet: create it
// don't load the entry when already unmounting
if (fUnmounting)
return B_ERROR;
// load the node
ShareNode* node;
status_t error = _LoadNode(entryInfo.nodeInfo, &node);
if (error != B_OK)
return error;
// if the directory or the node are marked remove, we don't create the
// entry
if (IsVNodeRemoved(directory->GetID()) > 0
|| IsVNodeRemoved(node->GetID()) > 0) {
return B_NOT_ALLOWED;
}
// create the entry
entry = new(std::nothrow) ShareDirEntry(directory, name, node);
if (!entry)
return B_NO_MEMORY;
ObjectDeleter<ShareDirEntry> entryDeleter(entry);
error = entry->InitCheck();
if (error != B_OK)
return error;
// add the entry
error = fEntries->Put(EntryKey(directory->GetID(), entry->GetName()),
entry);
if (error != B_OK)
return error;
// set the entry revision
entry->SetRevision(entryInfo.nodeInfo.revision);
// add the entry to the directory and the node
directory->AddEntry(entry);
entry->GetNode()->AddReferringEntry(entry);
// everything went fine
entryDeleter.Detach();
}
if (_entry)
*_entry = entry;
return B_OK;
}
// _RemoveEntry
//
// fLock must be held.
void
ShareVolume::_RemoveEntry(ShareDirEntry* entry)
{
fEntries->Remove(EntryKey(entry->GetDirectory()->GetID(),
entry->GetName()));
entry->GetDirectory()->RemoveEntry(entry);
entry->GetNode()->RemoveReferringEntry(entry);
entry->ReleaseReference();
}
// _IsObsoleteEntryInfo
//
// fLock must be held.
bool
ShareVolume::_IsObsoleteEntryInfo(const EntryInfo& entryInfo)
{
// get the directory
ShareDir* dir
= dynamic_cast<ShareDir*>(_GetNodeByRemoteID(entryInfo.directoryID));
if (!dir)
return false;
return (entryInfo.nodeInfo.revision <= dir->GetEntryRemovedEventRevision());
}
// _LoadAttrDir
status_t
ShareVolume::_LoadAttrDir(ShareNode* node, const AttrDirInfo& attrDirInfo)
{
if (!node || !attrDirInfo.isValid)
return B_BAD_VALUE;
AutoLocker<Locker> _(fLock);
if (fUnmounting)
return ERROR_NOT_CONNECTED;
ShareAttrDir* attrDir = node->GetAttrDir();
if (attrDir) {
if (attrDir->GetRevision() > attrDirInfo.revision)
return B_OK;
// update the attr dir
return attrDir->Update(attrDirInfo,
fAttrDirIterators->Get(node->GetID()));
} else {
// no attribute directory yet: create one
attrDir = new(std::nothrow) ShareAttrDir;
if (!attrDir)
return B_NO_MEMORY;
ObjectDeleter<ShareAttrDir> attrDirDeleter(attrDir);
// initialize it
status_t error = attrDir->Init(attrDirInfo);
if (error != B_OK)
return error;
// set it
node->SetAttrDir(attrDir);
attrDirDeleter.Detach();
return B_OK;
}
}
// _UpdateAttrDir
status_t
ShareVolume::_UpdateAttrDir(NodeID remoteID, const AttrDirInfo& attrDirInfo)
{
AutoLocker<Locker> _(fLock);
// get the node
ShareNode* node = _GetNodeByRemoteID(remoteID);
if (!node)
return B_ENTRY_NOT_FOUND;
if (!attrDirInfo.isValid || _LoadAttrDir(node, attrDirInfo) != B_OK) {
// updating/creating the attr dir failed; if existing, we mark it
// obsolete
if (ShareAttrDir* attrDir = node->GetAttrDir())
attrDir->SetUpToDate(false);
}
return B_OK;
}
// _AddAttrDirIterator
status_t
ShareVolume::_AddAttrDirIterator(ShareNode* node,
ShareAttrDirIterator* iterator)
{
if (!node || !iterator)
return B_BAD_VALUE;
AutoLocker<Locker> locker(fLock);
// get the iterator list
DoublyLinkedList<ShareAttrDirIterator>* iteratorList
= fAttrDirIterators->Get(node->GetID());
if (!iteratorList) {
// no list for the node yet: create one
iteratorList = new(std::nothrow) DoublyLinkedList<ShareAttrDirIterator>;
if (!iteratorList)
return B_NO_MEMORY;
// add it
status_t error = fAttrDirIterators->Put(node->GetID(), iteratorList);
if (error != B_OK) {
delete iteratorList;
return error;
}
}
// add the iterator
iteratorList->Insert(iterator);
return B_OK;
}
// _RemoveAttrDirIterator
void
ShareVolume::_RemoveAttrDirIterator(ShareNode* node,
ShareAttrDirIterator* iterator)
{
if (!node || !iterator)
return;
AutoLocker<Locker> locker(fLock);
// get the iterator list
DoublyLinkedList<ShareAttrDirIterator>* iteratorList
= fAttrDirIterators->Get(node->GetID());
if (!iteratorList) {
WARN("ShareVolume::_RemoveAttrDirIterator(): Iterator list not "
"found: node: %" B_PRIdINO "\n", node->GetID());
return;
}
// remove the iterator
iteratorList->Remove(iterator);
// if the list is empty now, discard it
if (!iteratorList->First()) {
fAttrDirIterators->Remove(node->GetID());
delete iteratorList;
}
}
// _NodeRemoved
void
ShareVolume::_NodeRemoved(NodeID remoteID)
{
AutoLocker<Locker> locker(fLock);
ShareNode* node = _GetNodeByRemoteID(remoteID);
if (!node)
return;
// if the node still has referring entries, we do nothing
if (node->GetActualReferringEntry())
return;
// if the node is a directory, we remove its entries first
if (ShareDir* dir = dynamic_cast<ShareDir*>(node)) {
while (ShareDirEntry* entry = dir->GetFirstEntry())
_RemoveEntry(entry);
}
// remove all entries still referring to the node
while (ShareDirEntry* entry = node->GetFirstReferringEntry())
_RemoveEntry(entry);
ino_t localID = node->GetID();
// Remove the node ID in all cases -- even, if the node is still
// known to the VFS. Otherwise there could be a race condition, that the
// server re-uses the remote ID and we have it still associated with an
// obsolete local ID.
_RemoveLocalNodeID(localID);
if (node->IsKnownToVFS()) {
Node* _node;
if (GetVNode(localID, &_node) != B_OK)
return;
Volume::RemoveVNode(localID);
locker.Unlock();
PutVNode(localID);
} else {
fNodes->Remove(localID);
delete node;
}
}
// _EntryCreated
void
ShareVolume::_EntryCreated(NodeID remoteDirID, const char* name,
const EntryInfo* entryInfo, int64 revision)
{
if (!name)
return;
AutoLocker<Locker> locker(fLock);
// get the directory
ShareDir* dir = dynamic_cast<ShareDir*>(_GetNodeByRemoteID(remoteDirID));
if (!dir)
return;
// load the entry, if possible
ShareDirEntry* entry;
bool entryLoaded = false;
if (entryInfo && _LoadEntry(dir, *entryInfo, &entry) == B_OK)
entryLoaded = true;
// if the entry could not be loaded, we have to mark the dir incomplete
// and update its revision counter
if (!entryLoaded) {
dir->UpdateEntryCreatedEventRevision(revision);
dir->SetComplete(false);
}
}
// _EntryRemoved
void
ShareVolume::_EntryRemoved(NodeID remoteDirID, const char* name, int64 revision)
{
if (!name)
return;
AutoLocker<Locker> locker(fLock);
// get the directory
ShareDir* dir = dynamic_cast<ShareDir*>(_GetNodeByRemoteID(remoteDirID));
if (!dir)
return;
// update the directory's "entry removed" event revision
dir->UpdateEntryRemovedEventRevision(revision);
// get the entry
ShareDirEntry* entry = _GetEntryByRemoteID(remoteDirID, name);
if (!entry)
return;
// check the entry revision
if (entry->GetRevision() > revision)
return;
// remove the entry
_RemoveEntry(entry);
}
// _EntryMoved
void
ShareVolume::_EntryMoved(NodeID remoteOldDirID, const char* oldName,
NodeID remoteNewDirID, const char* name, const EntryInfo* entryInfo,
int64 revision)
{
AutoLocker<Locker> locker(fLock);
_EntryRemoved(remoteOldDirID, oldName, revision);
_EntryCreated(remoteNewDirID, name, entryInfo, revision);
}
// _Walk
status_t
ShareVolume::_Walk(NodeID remoteDirID, const char* entryName, bool resolveLink,
WalkReply** _reply)
{
// prepare the request
WalkRequest request;
request.volumeID = fID;
request.nodeID = remoteDirID;
request.name.SetTo(entryName);
request.resolveLink = resolveLink;
// send the request
WalkReply* reply;
status_t error = SendRequest(fConnection, &request, &reply);
if (error != B_OK)
RETURN_ERROR(error);
ObjectDeleter<Request> replyDeleter(reply);
if (reply->error != B_OK)
RETURN_ERROR(reply->error);
replyDeleter.Detach();
*_reply = reply;
return B_OK;
}
// _MultiWalk
status_t
ShareVolume::_MultiWalk(RequestMemberArray<EntryInfo>& _entryInfos,
MultiWalkReply** _reply)
{
int32 count = _entryInfos.CountElements();
if (!_reply || count == 0)
return B_BAD_VALUE;
EntryInfo* entryInfos = _entryInfos.GetElements();
// prepare the request
MultiWalkRequest request;
request.volumeID = fID;
request.nodeID = entryInfos[0].directoryID;
// add the names
for (int32 i = 0; i < count; i++) {
StringData name;
name.SetTo(entryInfos[i].name.GetString());
status_t error = request.names.Append(name);
if (error != B_OK)
return error;
}
// send the request
MultiWalkReply* reply;
status_t error = SendRequest(fConnection, &request, &reply);
if (error != B_OK)
RETURN_ERROR(error);
ObjectDeleter<Request> replyDeleter(reply);
if (reply->error != B_OK)
RETURN_ERROR(reply->error);
replyDeleter.Detach();
*_reply = reply;
return B_OK;
}
// _Close
status_t
ShareVolume::_Close(intptr_t cookie)
{
if (!_EnsureShareMounted())
return ERROR_NOT_CONNECTED;
// prepare the request
CloseRequest request;
request.volumeID = fID;
request.cookie = cookie;
// send the request
CloseReply* reply;
status_t error = SendRequest(fConnection, &request, &reply);
if (error != B_OK)
RETURN_ERROR(error);
ObjectDeleter<Request> replyDeleter(reply);
if (reply->error != B_OK)
RETURN_ERROR(reply->error);
return B_OK;
}
// _GetConnectionState
//
// Must not be called with fLock being held!
uint32
ShareVolume::_GetConnectionState()
{
AutoLocker<Locker> _(fMountLock);
return fConnectionState;
}
// _IsConnected
//
// Must not be called with fLock being held!
bool
ShareVolume::_IsConnected()
{
return (_GetConnectionState() == CONNECTION_READY);
}
// _EnsureShareMounted
//
// Must not be called with fLock being held!
bool
ShareVolume::_EnsureShareMounted()
{
AutoLocker<Locker> _(fMountLock);
if (fConnectionState == CONNECTION_NOT_INITIALIZED)
_MountShare();
return (fConnectionState == CONNECTION_READY);
}
// _MountShare
//
// fMountLock must be held.
status_t
ShareVolume::_MountShare()
{
// get references to the server and share info
AutoLocker<Locker> locker(fLock);
BReference<ExtendedServerInfo> serverInfoReference(fServerInfo);
BReference<ExtendedShareInfo> shareInfoReference(fShareInfo);
ExtendedServerInfo* serverInfo = fServerInfo;
ExtendedShareInfo* shareInfo = fShareInfo;
locker.Unlock();
// get server address as string
HashString serverAddressString;
status_t error = serverInfo->GetAddress().GetString(&serverAddressString,
false);
if (error != B_OK)
return error;
const char* server = serverAddressString.GetString();
// get the server name
const char* serverName = serverInfo->GetServerName();
if (serverName && strlen(serverName) == 0)
serverName = NULL;
// get the share name
const char* share = shareInfo->GetShareName();
PRINT("ShareVolume::_MountShare(%s, %s)\n", server, share);
// init a connection to the authentication server
AuthenticationServer authenticationServer;
error = authenticationServer.InitCheck();
if (error != B_OK)
RETURN_ERROR(error);
// get the server connection
fConnectionState = CONNECTION_CLOSED;
if (!fServerConnection) {
status_t error = fServerConnectionProvider->GetServerConnection(
&fServerConnection);
if (error != B_OK)
return error;
fConnection = fServerConnection->GetRequestConnection();
}
// the mount loop
bool badPassword = false;
MountReply* reply = NULL;
do {
// get the user and password from the authentication server
char user[kUserBufferSize];
char password[kPasswordBufferSize];
bool cancelled;
error = authenticationServer.GetAuthentication("netfs",
(serverName ? serverName : server), share,
fVolumeManager->GetMountUID(), badPassword,
&cancelled, user, sizeof(user), password, sizeof(password));
if (cancelled || error != B_OK)
RETURN_ERROR(error);
// prepare the request
MountRequest request;
request.share.SetTo(share);
request.user.SetTo(user);
request.password.SetTo(password);
// send the request
error = SendRequest(fConnection, &request, &reply);
if (error != B_OK)
RETURN_ERROR(error);
ObjectDeleter<Request> replyDeleter(reply);
// if no permission, try again
badPassword = reply->noPermission;
if (!badPassword) {
if (reply->error != B_OK)
RETURN_ERROR(reply->error);
fSharePermissions = reply->sharePermissions;
}
} while (badPassword);
AutoLocker<Locker> _(fLock);
fID = reply->volumeID;
// update the root node and enter its ID
fRootNode->Update(reply->nodeInfo);
// put the IDs into local map
error = fLocalNodeIDs->Put(fRootNode->GetRemoteID(), fRootNode->GetID());
if (error != B_OK)
RETURN_ERROR(error);
// put the IDs into remote map
error = fRemoteNodeIDs->Put(fRootNode->GetID(), fRootNode->GetRemoteID());
if (error != B_OK) {
fLocalNodeIDs->Remove(fRootNode->GetRemoteID());
RETURN_ERROR(error);
}
PRINT("ShareVolume::_MountShare(): root node: local: %" B_PRIdINO
", remote: (%" B_PRIdDEV ", %" B_PRIdINO ")\n", fRootNode->GetID(),
fRootNode->GetRemoteID().volumeID,
fRootNode->GetRemoteID().nodeID);
// Add ourselves to the server connection, so that we can receive
// node monitoring events. There a race condition: We might already
// have missed events for the root node.
error = fServerConnection->AddVolume(this);
if (error != B_OK) {
_RemoveLocalNodeID(fRootNode->GetID());
return error;
}
fConnectionState = CONNECTION_READY;
return B_OK;
}
↑ V730 Not all members of a class are initialized inside the constructor. Consider inspecting: fRootNode.