33aae10b67
a chained buffer provided by cbuf. Only select support is missing now. git-svn-id: file:///srv/svn/repos/haiku/trunk/current@3736 a95241bf-73f2-0310-859d-f6bbb57e9c96
1286 lines
24 KiB
C++
1286 lines
24 KiB
C++
/*
|
|
** Copyright 2003, Axel Dörfler, axeld@pinc-software.de. All rights reserved.
|
|
** Distributed under the terms of the OpenBeOS License.
|
|
*/
|
|
|
|
|
|
#include <KernelExport.h>
|
|
#include <NodeMonitor.h>
|
|
|
|
#include <util/kernel_cpp.h>
|
|
#include <cbuf.h>
|
|
#include <vfs.h>
|
|
#include <debug.h>
|
|
#include <khash.h>
|
|
#include <lock.h>
|
|
#include <vm.h>
|
|
|
|
#include <malloc.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <sys/stat.h>
|
|
|
|
#include "builtin_fs.h"
|
|
|
|
// ToDo: handles file names suboptimally - it has all file names
|
|
// in a single linked list, no hash lookups or whatever.
|
|
|
|
#define PIPEFS_TRACE 0
|
|
|
|
#if PIPEFS_TRACE
|
|
# define TRACE(x) dprintf x
|
|
#else
|
|
# define TRACE(x)
|
|
#endif
|
|
|
|
|
|
namespace pipefs {
|
|
|
|
class Volume;
|
|
class Inode;
|
|
struct dir_cookie;
|
|
|
|
struct read_request {
|
|
read_request *prev;
|
|
read_request *next;
|
|
void *buffer;
|
|
size_t buffer_size;
|
|
size_t bytes_read;
|
|
|
|
size_t SpaceLeft() const { return buffer_size - bytes_read; }
|
|
void Fill(Inode *inode);
|
|
status_t PutBuffer(const void **_buffer, size_t *_bufferSize);
|
|
};
|
|
|
|
class ReadRequests {
|
|
public:
|
|
ReadRequests();
|
|
~ReadRequests();
|
|
|
|
void Lock();
|
|
void Unlock();
|
|
|
|
status_t Add(read_request &request);
|
|
status_t Remove(read_request &request);
|
|
|
|
read_request *GetCurrent() const { return fCurrent; }
|
|
void SkipCurrent() { if (fCurrent) fCurrent = fCurrent->next; }
|
|
|
|
private:
|
|
mutex fLock;
|
|
read_request *fFirst, *fCurrent, *fLast;
|
|
};
|
|
|
|
class Volume {
|
|
public:
|
|
Volume(mount_id id);
|
|
~Volume();
|
|
|
|
status_t InitCheck();
|
|
mount_id ID() const { return fID; }
|
|
Inode &RootNode() const { return *fRootNode; }
|
|
|
|
void Lock();
|
|
void Unlock();
|
|
|
|
Inode *Lookup(vnode_id id);
|
|
Inode *FindNode(const char *name);
|
|
Inode *CreateNode(const char *name, int32 type);
|
|
status_t DeleteNode(Inode *inode, bool forceDelete = false);
|
|
status_t RemoveNode(Inode *directory, const char *name);
|
|
|
|
vnode_id GetNextNodeID() { return fNextNodeID++; }
|
|
ReadRequests &GetReadRequests() { return fReadRequests; }
|
|
|
|
Inode *FirstEntry() const { return fFirstEntry; }
|
|
|
|
dir_cookie *FirstDirCookie() const { return fFirstDirCookie; }
|
|
void InsertCookie(dir_cookie *cookie);
|
|
|
|
private:
|
|
void RemoveCookie(dir_cookie *cookie);
|
|
void UpdateDirCookies(Inode *inode);
|
|
|
|
status_t RemoveNode(Inode *inode);
|
|
status_t InsertNode(Inode *inode);
|
|
|
|
ReadRequests fReadRequests;
|
|
mutex fLock;
|
|
mount_id fID;
|
|
Inode *fRootNode;
|
|
vnode_id fNextNodeID;
|
|
hash_table *fNodeHash;
|
|
|
|
// root directory contents - we don't support other directories
|
|
Inode *fFirstEntry;
|
|
dir_cookie *fFirstDirCookie;
|
|
};
|
|
|
|
class Inode {
|
|
public:
|
|
Inode(Volume *volume, const char *name, int32 type);
|
|
~Inode();
|
|
|
|
status_t InitCheck();
|
|
vnode_id ID() const { return fID; }
|
|
const char *Name() const { return fName; }
|
|
int32 Type() const { return fType; }
|
|
|
|
Inode *Next() const { return fNext; }
|
|
void SetNext(Inode *inode) { fNext = inode; }
|
|
|
|
sem_id ReadLock() { return fReadLock; }
|
|
mutex *WriteMutex() { return &fWriteMutex; }
|
|
|
|
status_t WriteBufferToChain(const void *buffer, size_t bufferSize);
|
|
status_t ReadBufferFromChain(void *buffer, size_t *_bufferSize);
|
|
|
|
static int32 HashNextOffset();
|
|
static uint32 hash_func(void *_node, const void *_key, uint32 range);
|
|
static int compare_func(void *_node, const void *_key);
|
|
|
|
private:
|
|
Inode *fNext;
|
|
Inode *fHashNext;
|
|
vnode_id fID;
|
|
int32 fType;
|
|
const char *fName;
|
|
|
|
cbuf *fBufferChain;
|
|
|
|
sem_id fReadLock;
|
|
mutex fWriteMutex;
|
|
};
|
|
|
|
|
|
struct dir_cookie {
|
|
dir_cookie *next;
|
|
dir_cookie *prev;
|
|
Inode *current;
|
|
};
|
|
|
|
struct file_cookie {
|
|
int open_mode;
|
|
};
|
|
|
|
|
|
#define PIPEFS_HASH_SIZE 16
|
|
|
|
|
|
Volume::Volume(mount_id id)
|
|
:
|
|
fID(id),
|
|
fRootNode(NULL),
|
|
fNextNodeID(0),
|
|
fNodeHash(NULL),
|
|
fFirstEntry(NULL),
|
|
fFirstDirCookie(NULL)
|
|
{
|
|
if (mutex_init(&fLock, "pipefs_mutex") < B_OK)
|
|
return;
|
|
|
|
fNodeHash = hash_init(PIPEFS_HASH_SIZE, Inode::HashNextOffset(),
|
|
&Inode::compare_func, &Inode::hash_func);
|
|
if (fNodeHash == NULL)
|
|
return;
|
|
|
|
// create the root vnode
|
|
fRootNode = CreateNode("", S_IFDIR);
|
|
if (fRootNode == NULL)
|
|
return;
|
|
}
|
|
|
|
|
|
Volume::~Volume()
|
|
{
|
|
// put_vnode on the root to release the ref to it
|
|
if (fRootNode)
|
|
vfs_put_vnode(ID(), fRootNode->ID());
|
|
|
|
if (fNodeHash != NULL) {
|
|
// delete all of the inodes
|
|
struct hash_iterator i;
|
|
hash_open(fNodeHash, &i);
|
|
|
|
Inode *inode;
|
|
while ((inode = (Inode *)hash_next(fNodeHash, &i)) != NULL) {
|
|
DeleteNode(inode, true);
|
|
}
|
|
|
|
hash_close(fNodeHash, &i, false);
|
|
|
|
hash_uninit(fNodeHash);
|
|
}
|
|
mutex_destroy(&fLock);
|
|
}
|
|
|
|
|
|
status_t
|
|
Volume::InitCheck()
|
|
{
|
|
if (fLock.sem < B_OK
|
|
|| fRootNode == NULL)
|
|
return B_ERROR;
|
|
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
void
|
|
Volume::Lock()
|
|
{
|
|
mutex_lock(&fLock);
|
|
}
|
|
|
|
|
|
void
|
|
Volume::Unlock()
|
|
{
|
|
mutex_unlock(&fLock);
|
|
}
|
|
|
|
|
|
Inode *
|
|
Volume::CreateNode(const char *name, int32 type)
|
|
{
|
|
Inode *inode = new Inode(this, name, type);
|
|
if (inode == NULL)
|
|
return NULL;
|
|
|
|
if (inode->InitCheck() != B_OK) {
|
|
delete inode;
|
|
return NULL;
|
|
}
|
|
|
|
if (type == S_IFIFO)
|
|
InsertNode(inode);
|
|
|
|
hash_insert(fNodeHash, inode);
|
|
vfs_new_vnode(ID(), inode->ID(), inode);
|
|
|
|
return inode;
|
|
}
|
|
|
|
|
|
status_t
|
|
Volume::DeleteNode(Inode *inode, bool forceDelete)
|
|
{
|
|
// remove it from the global hash table
|
|
hash_remove(fNodeHash, inode);
|
|
|
|
delete inode;
|
|
return 0;
|
|
}
|
|
|
|
|
|
void
|
|
Volume::InsertCookie(dir_cookie *cookie)
|
|
{
|
|
cookie->next = fFirstDirCookie;
|
|
fFirstDirCookie = cookie;
|
|
cookie->prev = NULL;
|
|
}
|
|
|
|
|
|
void
|
|
Volume::RemoveCookie(dir_cookie *cookie)
|
|
{
|
|
if (cookie->next)
|
|
cookie->next->prev = cookie->prev;
|
|
|
|
if (cookie->prev)
|
|
cookie->prev->next = cookie->next;
|
|
|
|
if (fFirstDirCookie == cookie)
|
|
fFirstDirCookie = cookie->next;
|
|
|
|
cookie->prev = cookie->next = NULL;
|
|
}
|
|
|
|
|
|
/* makes sure none of the dircookies point to the vnode passed in */
|
|
|
|
void
|
|
Volume::UpdateDirCookies(Inode *inode)
|
|
{
|
|
dir_cookie *cookie;
|
|
|
|
for (cookie = fFirstDirCookie; cookie; cookie = cookie->next) {
|
|
if (cookie->current == inode)
|
|
cookie->current = inode->Next();
|
|
}
|
|
}
|
|
|
|
|
|
Inode *
|
|
Volume::Lookup(vnode_id id)
|
|
{
|
|
return (Inode *)hash_lookup(fNodeHash, &id);
|
|
}
|
|
|
|
|
|
Inode *
|
|
Volume::FindNode(const char *name)
|
|
{
|
|
if (!strcmp(name, ".")
|
|
|| !strcmp(name, ".."))
|
|
return fRootNode;
|
|
|
|
for (Inode *inode = fFirstEntry; inode; inode = inode->Next()) {
|
|
if (!strcmp(inode->Name(), name))
|
|
return inode;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
status_t
|
|
Volume::InsertNode(Inode *inode)
|
|
{
|
|
inode->SetNext(fFirstEntry);
|
|
fFirstEntry = inode;
|
|
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
status_t
|
|
Volume::RemoveNode(Inode *findNode)
|
|
{
|
|
Inode *inode, *last;
|
|
|
|
for (inode = fFirstEntry, last = NULL; inode; last = inode, inode = inode->Next()) {
|
|
if (inode == findNode) {
|
|
// make sure no dir cookies point to this node
|
|
UpdateDirCookies(inode);
|
|
|
|
if (last)
|
|
last->SetNext(inode->Next());
|
|
else
|
|
fFirstEntry = inode->Next();
|
|
|
|
inode->SetNext(NULL);
|
|
return B_OK;
|
|
}
|
|
}
|
|
return B_ENTRY_NOT_FOUND;
|
|
}
|
|
|
|
|
|
status_t
|
|
Volume::RemoveNode(Inode *directory, const char *name)
|
|
{
|
|
Lock();
|
|
|
|
status_t status = B_OK;
|
|
|
|
Inode *inode = FindNode(name);
|
|
if (inode == NULL)
|
|
status = B_ENTRY_NOT_FOUND;
|
|
else if (inode->Type() == S_IFDIR)
|
|
status = B_NOT_ALLOWED;
|
|
|
|
if (status < B_OK)
|
|
goto err;
|
|
|
|
RemoveNode(inode);
|
|
notify_listener(B_ENTRY_REMOVED, ID(), directory->ID(), 0, inode->ID(), name);
|
|
|
|
// schedule this vnode to be removed when it's ref goes to zero
|
|
vfs_remove_vnode(ID(), inode->ID());
|
|
|
|
err:
|
|
Unlock();
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
// #pragma mark -
|
|
|
|
|
|
Inode::Inode(Volume *volume, const char *name, int32 type)
|
|
:
|
|
fNext(NULL),
|
|
fHashNext(NULL),
|
|
fBufferChain(NULL)
|
|
{
|
|
fName = strdup(name);
|
|
if (fName == NULL)
|
|
return;
|
|
|
|
fID = volume->GetNextNodeID();
|
|
fType = type;
|
|
|
|
if (type == S_IFIFO) {
|
|
fReadLock = create_sem(0, "pipe read");
|
|
mutex_init(&fWriteMutex, "pipe write");
|
|
}
|
|
}
|
|
|
|
|
|
Inode::~Inode()
|
|
{
|
|
free(const_cast<char *>(fName));
|
|
|
|
if (fType == S_IFIFO) {
|
|
delete_sem(fReadLock);
|
|
mutex_destroy(&fWriteMutex);
|
|
}
|
|
}
|
|
|
|
|
|
status_t
|
|
Inode::InitCheck()
|
|
{
|
|
if (fName == NULL
|
|
|| fType == S_IFIFO && (fReadLock < B_OK || fWriteMutex.sem < B_OK))
|
|
return B_ERROR;
|
|
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
/** Adds the specified buffer to the inode's buffer chain by copying
|
|
* its contents. The Inode::WriteLock() must be held when calling
|
|
* this method.
|
|
* Releases the reader sem if necessary, so that blocking
|
|
* readers will get started.
|
|
* Returns B_NO_MEMORY if the chain couldn't be allocated, B_BAD_ADDRESS
|
|
* if copying from the buffer failed, and B_OK on success.
|
|
*/
|
|
|
|
status_t
|
|
Inode::WriteBufferToChain(const void *buffer, size_t bufferSize)
|
|
{
|
|
cbuf *chain = cbuf_get_chain(bufferSize);
|
|
if (chain == NULL)
|
|
return B_NO_MEMORY;
|
|
|
|
if (cbuf_user_memcpy_to_chain(chain, 0, buffer, bufferSize) < B_OK)
|
|
return B_BAD_ADDRESS;
|
|
|
|
// join this chain with our already existing chain (if any)
|
|
|
|
chain = cbuf_merge_chains(fBufferChain, chain);
|
|
|
|
// let waiting readers go on
|
|
if (fBufferChain == NULL)
|
|
release_sem(ReadLock());
|
|
|
|
fBufferChain = chain;
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
/** Reads the contents of the buffer chain to the specified
|
|
* buffer, if any.
|
|
* Unblocks other waiting readers if there would be still
|
|
* some bytes left in the stream.
|
|
*/
|
|
|
|
status_t
|
|
Inode::ReadBufferFromChain(void *buffer, size_t *_bufferSize)
|
|
{
|
|
if (fBufferChain == NULL) {
|
|
*_bufferSize = 0;
|
|
return B_OK;
|
|
}
|
|
|
|
size_t length = cbuf_get_length(fBufferChain);
|
|
|
|
// we read *_bufferSize bytes at maximum - but never
|
|
// more than there are in the chain
|
|
if (*_bufferSize > length)
|
|
*_bufferSize = length;
|
|
else {
|
|
length = *_bufferSize;
|
|
|
|
// we unlock another reader here, so that it can read
|
|
// the rest of the pending bytes
|
|
release_sem(ReadLock());
|
|
}
|
|
|
|
if (cbuf_user_memcpy_from_chain(buffer, fBufferChain, 0, length) < B_OK)
|
|
return B_BAD_ADDRESS;
|
|
|
|
if (cbuf_truncate_head(fBufferChain, length) < B_OK) {
|
|
// if that call fails, the next read will duplicate the input
|
|
dprintf("pipefs: cbuf_truncate_head() failed for inode %Ld\n", ID());
|
|
}
|
|
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
int32
|
|
Inode::HashNextOffset()
|
|
{
|
|
Inode *inode;
|
|
return (addr)&inode->fHashNext - (addr)inode;
|
|
}
|
|
|
|
|
|
uint32
|
|
Inode::hash_func(void *_node, const void *_key, uint32 range)
|
|
{
|
|
Inode *inode = (Inode *)_node;
|
|
const vnode_id *key = (const vnode_id *)_key;
|
|
|
|
if (inode != NULL)
|
|
return inode->ID() % range;
|
|
|
|
return (*key) % range;
|
|
}
|
|
|
|
|
|
int
|
|
Inode::compare_func(void *_node, const void *_key)
|
|
{
|
|
Inode *inode = (Inode *)_node;
|
|
const vnode_id *key = (const vnode_id *)_key;
|
|
|
|
if (inode->ID() == *key)
|
|
return 0;
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
// #pragma mark -
|
|
|
|
|
|
void
|
|
read_request::Fill(Inode *inode)
|
|
{
|
|
size_t length = SpaceLeft();
|
|
|
|
if (inode->ReadBufferFromChain(buffer, &length) < B_OK) {
|
|
// ToDo: should add status to read_request
|
|
dprintf("reading from chain failed!");
|
|
} else
|
|
bytes_read += length;
|
|
}
|
|
|
|
|
|
status_t
|
|
read_request::PutBuffer(const void **_buffer, size_t *_bufferSize)
|
|
{
|
|
TRACE(("pipefs: read_request::PutUserBuffer(buffer = %p, size = %lu)\n", *_buffer, *_bufferSize));
|
|
|
|
size_t bytes = *_bufferSize;
|
|
if (bytes > SpaceLeft())
|
|
bytes = SpaceLeft();
|
|
|
|
uint8 *source = (uint8 *)*_buffer;
|
|
|
|
if (user_memcpy((uint8 *)buffer + bytes_read, source, bytes) < B_OK)
|
|
return B_BAD_ADDRESS;
|
|
|
|
bytes_read += bytes;
|
|
*_buffer = (void *)(source + bytes);
|
|
*_bufferSize -= bytes;
|
|
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
// #pragma mark -
|
|
|
|
|
|
ReadRequests::ReadRequests()
|
|
:
|
|
fFirst(NULL),
|
|
fCurrent(NULL),
|
|
fLast(NULL)
|
|
{
|
|
mutex_init(&fLock, "pipefs read requests");
|
|
}
|
|
|
|
|
|
ReadRequests::~ReadRequests()
|
|
{
|
|
mutex_destroy(&fLock);
|
|
}
|
|
|
|
|
|
void
|
|
ReadRequests::Lock()
|
|
{
|
|
mutex_lock(&fLock);
|
|
}
|
|
|
|
|
|
void
|
|
ReadRequests::Unlock()
|
|
{
|
|
mutex_unlock(&fLock);
|
|
}
|
|
|
|
|
|
status_t
|
|
ReadRequests::Add(read_request &request)
|
|
{
|
|
if (fLast != NULL)
|
|
fLast->next = &request;
|
|
|
|
if (fFirst == NULL)
|
|
fFirst = &request;
|
|
|
|
// ToDo: could directly skip full requests
|
|
if (fCurrent == NULL)
|
|
fCurrent = &request;
|
|
|
|
request.prev = fLast;
|
|
request.next = NULL;
|
|
fLast = &request;
|
|
}
|
|
|
|
|
|
status_t
|
|
ReadRequests::Remove(read_request &request)
|
|
{
|
|
if (request.next != NULL)
|
|
request.next->prev = request.prev;
|
|
if (request.prev != NULL)
|
|
request.prev->next = request.next;
|
|
|
|
// update pointers
|
|
|
|
if (fCurrent == &request)
|
|
fCurrent = fCurrent->next;
|
|
|
|
if (fLast == &request)
|
|
fLast = request.prev;
|
|
|
|
if (fFirst == &request)
|
|
fFirst = request.next;
|
|
}
|
|
|
|
|
|
// #pragma mark -
|
|
|
|
|
|
static status_t
|
|
pipefs_mount(mount_id id, const char *device, void *args, fs_volume *_volume, vnode_id *_rootVnodeID)
|
|
{
|
|
TRACE(("pipefs_mount: entry\n"));
|
|
|
|
Volume *volume = new Volume(id);
|
|
if (volume == NULL)
|
|
return B_NO_MEMORY;
|
|
|
|
status_t status = volume->InitCheck();
|
|
if (status < B_OK) {
|
|
delete volume;
|
|
return status;
|
|
}
|
|
|
|
*_rootVnodeID = volume->RootNode().ID();
|
|
*_volume = volume;
|
|
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
static status_t
|
|
pipefs_unmount(fs_volume _volume)
|
|
{
|
|
struct Volume *volume = (struct Volume *)_volume;
|
|
|
|
TRACE(("pipefs_unmount: entry fs = %p\n", _volume));
|
|
delete volume;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static status_t
|
|
pipefs_sync(fs_volume fs)
|
|
{
|
|
TRACE(("pipefs_sync: entry\n"));
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static status_t
|
|
pipefs_lookup(fs_volume _volume, fs_vnode _dir, const char *name, vnode_id *_id, int *_type)
|
|
{
|
|
Volume *volume = (Volume *)_volume;
|
|
status_t status;
|
|
|
|
TRACE(("pipefs_lookup: entry dir %p, name '%s'\n", _dir, name));
|
|
|
|
Inode *directory = (Inode *)_dir;
|
|
if (directory->Type() != S_IFDIR)
|
|
return B_NOT_A_DIRECTORY;
|
|
|
|
volume->Lock();
|
|
|
|
// look it up
|
|
Inode *inode = volume->FindNode(name);
|
|
if (inode == NULL) {
|
|
status = B_ENTRY_NOT_FOUND;
|
|
goto err;
|
|
}
|
|
|
|
Inode *dummy;
|
|
status = vfs_get_vnode(volume->ID(), inode->ID(), (fs_vnode *)&dummy);
|
|
if (status < B_OK)
|
|
goto err;
|
|
|
|
*_id = inode->ID();
|
|
*_type = inode->Type();
|
|
|
|
err:
|
|
volume->Unlock();
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
static status_t
|
|
pipefs_get_vnode_name(fs_volume _volume, fs_vnode _node, char *buffer, size_t bufferSize)
|
|
{
|
|
Inode *inode = (Inode *)_node;
|
|
|
|
TRACE(("pipefs_get_vnode_name(): inode = %p\n", inode));
|
|
|
|
strlcpy(buffer, inode->Name(), bufferSize);
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
static status_t
|
|
pipefs_get_vnode(fs_volume _volume, vnode_id id, fs_vnode *_inode, bool reenter)
|
|
{
|
|
Volume *volume = (Volume *)_volume;
|
|
|
|
TRACE(("pipefs_getvnode: asking for vnode 0x%Lx, r %d\n", id, reenter));
|
|
|
|
if (!reenter)
|
|
volume->Lock();
|
|
|
|
*_inode = volume->Lookup(id);
|
|
|
|
if (!reenter)
|
|
volume->Unlock();
|
|
|
|
TRACE(("pipefs_getnvnode: looked it up at %p\n", *_inode));
|
|
|
|
if (*_inode)
|
|
return B_OK;
|
|
|
|
return B_ENTRY_NOT_FOUND;
|
|
}
|
|
|
|
|
|
static status_t
|
|
pipefs_put_vnode(fs_volume _volume, fs_vnode _node, bool reenter)
|
|
{
|
|
Inode *inode = (Inode *)_node;
|
|
|
|
TRACE(("pipefs_putvnode: entry on vnode 0x%Lx, r %d\n", inode->ID(), reenter));
|
|
|
|
// ToDo: delete pipe - it isn't needed anymore!
|
|
|
|
//delete inode;
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
static status_t
|
|
pipefs_remove_vnode(fs_volume _volume, fs_vnode _node, bool reenter)
|
|
{
|
|
Volume *volume = (Volume *)_volume;
|
|
Inode *inode = (Inode *)_node;
|
|
|
|
TRACE(("pipefs_removevnode: remove %p (0x%Lx), r %d\n", inode, inode->ID(), reenter));
|
|
|
|
if (!reenter)
|
|
volume->Lock();
|
|
|
|
if (inode->Next()) {
|
|
// can't remove node if it's linked to the dir
|
|
panic("pipefs_remove_vnode(): vnode %p asked to be removed is present in dir\n", inode);
|
|
}
|
|
|
|
volume->DeleteNode(inode);
|
|
|
|
if (!reenter)
|
|
volume->Unlock();
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static status_t
|
|
pipefs_create(fs_volume _volume, fs_vnode _dir, const char *name, int openMode, int perms,
|
|
fs_cookie *_cookie, vnode_id *_newVnodeID)
|
|
{
|
|
Volume *volume = (Volume *)_volume;
|
|
|
|
TRACE(("pipefs_create(): dir = %p, name = '%s', perms = %d, &id = %p\n",
|
|
_dir, name, perms, _newVnodeID));
|
|
|
|
file_cookie *cookie = (file_cookie *)malloc(sizeof(file_cookie));
|
|
if (cookie == NULL)
|
|
return B_NO_MEMORY;
|
|
|
|
volume->Lock();
|
|
|
|
Inode *directory = (Inode *)_dir;
|
|
status_t status = B_OK;
|
|
|
|
Inode *inode = volume->FindNode(name);
|
|
if (inode != NULL) {
|
|
status = B_FILE_EXISTS;
|
|
goto err;
|
|
}
|
|
|
|
inode = volume->CreateNode(name, S_IFIFO);
|
|
if (inode == NULL) {
|
|
status = B_NO_MEMORY;
|
|
goto err;
|
|
}
|
|
|
|
volume->Unlock();
|
|
|
|
cookie->open_mode = openMode;
|
|
|
|
*_cookie = (void *)cookie;
|
|
*_newVnodeID = inode->ID();
|
|
notify_listener(B_ENTRY_CREATED, volume->ID(), directory->ID(), 0, inode->ID(), name);
|
|
|
|
return B_OK;
|
|
|
|
err:
|
|
volume->Unlock();
|
|
free(cookie);
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
static status_t
|
|
pipefs_open(fs_volume _volume, fs_vnode _node, int openMode, fs_cookie *_cookie)
|
|
{
|
|
// allow to open the file, but it can't be done anything with it
|
|
|
|
file_cookie *cookie = (file_cookie *)malloc(sizeof(file_cookie));
|
|
if (cookie == NULL)
|
|
return B_NO_MEMORY;
|
|
|
|
cookie->open_mode = openMode;
|
|
|
|
*_cookie = (void *)cookie;
|
|
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
static status_t
|
|
pipefs_close(fs_volume _volume, fs_vnode _vnode, fs_cookie _cookie)
|
|
{
|
|
TRACE(("pipefs_close: entry vnode %p, cookie %p\n", _vnode, _cookie));
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static status_t
|
|
pipefs_free_cookie(fs_volume _volume, fs_vnode _node, fs_cookie _cookie)
|
|
{
|
|
file_cookie *cookie = (file_cookie *)_cookie;
|
|
|
|
TRACE(("pipefs_freecookie: entry vnode %p, cookie %p\n", _node, _cookie));
|
|
|
|
free(cookie);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static status_t
|
|
pipefs_fsync(fs_volume _volume, fs_vnode _v)
|
|
{
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
static ssize_t
|
|
pipefs_read(fs_volume _volume, fs_vnode _node, fs_cookie _cookie, off_t pos,
|
|
void *buffer, size_t *_length)
|
|
{
|
|
file_cookie *cookie = (file_cookie *)_cookie;
|
|
Volume *volume = (Volume *)_volume;
|
|
|
|
(void)pos;
|
|
TRACE(("pipefs_read: vnode %p, cookie %p, pos 0x%Lx , len 0x%lx\n", _node, cookie, pos, *_length));
|
|
|
|
if ((cookie->open_mode & O_RWMASK) != O_RDONLY)
|
|
return B_NOT_ALLOWED;
|
|
|
|
// issue read request
|
|
|
|
read_request request;
|
|
request.buffer = buffer;
|
|
request.buffer_size = *_length;
|
|
request.bytes_read = 0;
|
|
|
|
ReadRequests &requests = volume->GetReadRequests();
|
|
|
|
requests.Lock();
|
|
requests.Add(request);
|
|
requests.Unlock();
|
|
|
|
// ToDo: here is the race condition that another reader issues
|
|
// its read request after this one, but locks earlier; this
|
|
// will currently lead to a dead-lock or failure - that could
|
|
// be solved by attaching a thread to the request
|
|
|
|
// wait for it to be filled
|
|
|
|
Inode *inode = (Inode *)_node;
|
|
status_t status = acquire_sem_etc(inode->ReadLock(), 1,
|
|
(cookie->open_mode & O_NONBLOCK ? B_TIMEOUT : 0 ) | B_CAN_INTERRUPT, 0);
|
|
|
|
requests.Lock();
|
|
|
|
if (status == B_OK)
|
|
request.Fill(inode);
|
|
|
|
requests.Remove(request);
|
|
requests.Unlock();
|
|
|
|
if (status == B_TIMED_OUT || B_INTERRUPTED && request.bytes_read > 0)
|
|
status = B_OK;
|
|
|
|
if (status == B_OK)
|
|
*_length = request.bytes_read;
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
static ssize_t
|
|
pipefs_write(fs_volume _volume, fs_vnode _node, fs_cookie _cookie, off_t pos,
|
|
const void *buffer, size_t *_length)
|
|
{
|
|
file_cookie *cookie = (file_cookie *)_cookie;
|
|
Volume *volume = (Volume *)_volume;
|
|
Inode *inode = (Inode *)_node;
|
|
|
|
TRACE(("pipefs_write: vnode %p, cookie %p, pos 0x%Lx , len 0x%lx\n", _node, cookie, pos, *_length));
|
|
|
|
if ((cookie->open_mode & O_RWMASK) != O_WRONLY)
|
|
return B_NOT_ALLOWED;
|
|
|
|
mutex_lock(inode->WriteMutex());
|
|
|
|
ReadRequests &requests = volume->GetReadRequests();
|
|
requests.Lock();
|
|
|
|
size_t bytesLeft = *_length;
|
|
read_request *request = NULL;
|
|
|
|
do {
|
|
request = requests.GetCurrent();
|
|
if (request != NULL) {
|
|
// fill this request
|
|
|
|
request->Fill(inode);
|
|
if (request->SpaceLeft() > 0) {
|
|
// place our data into that buffer
|
|
request->PutBuffer(&buffer, &bytesLeft);
|
|
if (bytesLeft == 0) {
|
|
release_sem(inode->ReadLock());
|
|
break;
|
|
}
|
|
}
|
|
|
|
requests.SkipCurrent();
|
|
}
|
|
} while (request != NULL);
|
|
|
|
status_t status;
|
|
|
|
if (request == NULL) {
|
|
// there is no read request pending, so we have to put
|
|
// our data in a temporary buffer
|
|
|
|
status = inode->WriteBufferToChain(buffer, bytesLeft);
|
|
if (status != B_OK)
|
|
*_length -= bytesLeft;
|
|
} else {
|
|
// could write everything without the need to copy!
|
|
status = B_OK;
|
|
}
|
|
|
|
requests.Unlock();
|
|
mutex_unlock(inode->WriteMutex());
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
static off_t
|
|
pipefs_seek(fs_volume _volume, fs_vnode _vnode, fs_cookie _cookie, off_t pos, int seekType)
|
|
{
|
|
return ESPIPE;
|
|
}
|
|
|
|
|
|
static status_t
|
|
pipefs_create_dir(fs_volume _volume, fs_vnode _dir, const char *name, int perms, vnode_id *_newID)
|
|
{
|
|
return ENOSYS;
|
|
}
|
|
|
|
|
|
static status_t
|
|
pipefs_remove_dir(fs_volume _volume, fs_vnode _dir, const char *name)
|
|
{
|
|
return EPERM;
|
|
}
|
|
|
|
|
|
static status_t
|
|
pipefs_open_dir(fs_volume _volume, fs_vnode _node, fs_cookie *_cookie)
|
|
{
|
|
Volume *volume = (Volume *)_volume;
|
|
|
|
TRACE(("pipefs_open_dir(): vnode = %p\n", _node));
|
|
|
|
Inode *inode = (Inode *)_node;
|
|
if (inode->Type() != S_IFDIR)
|
|
return B_BAD_VALUE;
|
|
|
|
if (inode != &volume->RootNode())
|
|
panic("pipefs: found directory that's not the root!");
|
|
|
|
dir_cookie *cookie = (dir_cookie *)malloc(sizeof(dir_cookie));
|
|
if (cookie == NULL)
|
|
return B_NO_MEMORY;
|
|
|
|
volume->Lock();
|
|
|
|
cookie->current = volume->FirstEntry();
|
|
volume->InsertCookie(cookie);
|
|
|
|
volume->Unlock();
|
|
|
|
*_cookie = (void *)cookie;
|
|
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
static status_t
|
|
pipefs_read_dir(fs_volume _volume, fs_vnode _node, fs_cookie _cookie,
|
|
struct dirent *dirent, size_t bufferSize, uint32 *_num)
|
|
{
|
|
Volume *volume = (Volume *)_volume;
|
|
status_t status = 0;
|
|
|
|
TRACE(("pipefs_read_dir: vnode %p, cookie %p, buffer = %p, bufferSize = %ld, num = %p\n", _node, _cookie, dirent, bufferSize,_num));
|
|
|
|
if (_node != &volume->RootNode())
|
|
return B_BAD_VALUE;
|
|
|
|
volume->Lock();
|
|
|
|
dir_cookie *cookie = (dir_cookie *)_cookie;
|
|
if (cookie->current == NULL) {
|
|
// we're at the end of the directory
|
|
*_num = 0;
|
|
status = B_OK;
|
|
goto err;
|
|
}
|
|
|
|
dirent->d_dev = volume->ID();
|
|
dirent->d_ino = cookie->current->ID();
|
|
dirent->d_reclen = strlen(cookie->current->Name()) + sizeof(struct dirent);
|
|
|
|
if (dirent->d_reclen > bufferSize) {
|
|
status = ENOBUFS;
|
|
goto err;
|
|
}
|
|
|
|
status = user_strlcpy(dirent->d_name, cookie->current->Name(), bufferSize);
|
|
if (status < 0)
|
|
goto err;
|
|
|
|
cookie->current = cookie->current->Next();
|
|
|
|
err:
|
|
volume->Unlock();
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
static status_t
|
|
pipefs_rewind_dir(fs_volume _volume, fs_vnode _vnode, fs_cookie _cookie)
|
|
{
|
|
Volume *volume = (Volume *)_volume;
|
|
volume->Lock();
|
|
|
|
dir_cookie *cookie = (dir_cookie *)_cookie;
|
|
cookie->current = volume->FirstEntry();
|
|
|
|
volume->Unlock();
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
static status_t
|
|
pipefs_close_dir(fs_volume _volume, fs_vnode _node, fs_cookie _cookie)
|
|
{
|
|
TRACE(("pipefs_close: entry vnode %p, cookie %p\n", _node, _cookie));
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static status_t
|
|
pipefs_free_dir_cookie(fs_volume _volume, fs_vnode _vnode, fs_cookie _cookie)
|
|
{
|
|
dir_cookie *cookie = (dir_cookie *)_cookie;
|
|
|
|
TRACE(("pipefs_freecookie: entry vnode %p, cookie %p\n", _vnode, cookie));
|
|
|
|
free(cookie);
|
|
return 0;
|
|
}
|
|
|
|
|
|
static status_t
|
|
pipefs_ioctl(fs_volume _volume, fs_vnode _vnode, fs_cookie _cookie, ulong op,
|
|
void *buffer, size_t length)
|
|
{
|
|
TRACE(("pipefs_ioctl: vnode %p, cookie %p, op %ld, buf %p, len %ld\n",
|
|
_vnode, _cookie, op, buffer, length));
|
|
|
|
return EINVAL;
|
|
}
|
|
|
|
|
|
static status_t
|
|
pipefs_can_page(fs_volume _volume, fs_vnode _v)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
|
|
static ssize_t
|
|
pipefs_read_pages(fs_volume _volume, fs_vnode _v, iovecs *vecs, off_t pos)
|
|
{
|
|
return EPERM;
|
|
}
|
|
|
|
|
|
static ssize_t
|
|
pipefs_write_pages(fs_volume _volume, fs_vnode _v, iovecs *vecs, off_t pos)
|
|
{
|
|
return EPERM;
|
|
}
|
|
|
|
|
|
static status_t
|
|
pipefs_unlink(fs_volume _volume, fs_vnode _dir, const char *name)
|
|
{
|
|
Volume *volume = (Volume *)_volume;
|
|
Inode *directory = (Inode *)_dir;
|
|
|
|
TRACE(("pipefs_unlink: dir %p (0x%Lx), name '%s'\n", _dir, directory->ID(), name));
|
|
|
|
return volume->RemoveNode(directory, name);
|
|
}
|
|
|
|
|
|
static status_t
|
|
pipefs_read_stat(fs_volume _volume, fs_vnode _node, struct stat *stat)
|
|
{
|
|
Volume *volume = (Volume *)_volume;
|
|
Inode *inode = (Inode *)_node;
|
|
|
|
TRACE(("pipefs_rstat: vnode %p (0x%Lx), stat %p\n", inode, inode->ID(), stat));
|
|
|
|
stat->st_dev = volume->ID();
|
|
stat->st_ino = inode->ID();
|
|
stat->st_size = 0; // ToDo: oughta get me!
|
|
stat->st_mode = inode->Type() | 0777;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
// #pragma mark -
|
|
|
|
|
|
static struct fs_ops pipefs_ops = {
|
|
&pipefs_mount,
|
|
&pipefs_unmount,
|
|
NULL,
|
|
NULL,
|
|
&pipefs_sync,
|
|
|
|
&pipefs_lookup,
|
|
&pipefs_get_vnode_name,
|
|
|
|
&pipefs_get_vnode,
|
|
&pipefs_put_vnode,
|
|
&pipefs_remove_vnode,
|
|
|
|
&pipefs_can_page,
|
|
&pipefs_read_pages,
|
|
&pipefs_write_pages,
|
|
|
|
/* common */
|
|
&pipefs_ioctl,
|
|
&pipefs_fsync,
|
|
|
|
NULL, // fs_read_link()
|
|
NULL, // fs_write_link()
|
|
NULL, // fs_symlink()
|
|
NULL, // fs_link()
|
|
&pipefs_unlink,
|
|
NULL, // fs_rename()
|
|
|
|
NULL, // fs_access()
|
|
&pipefs_read_stat,
|
|
NULL, // fs_write_stat()
|
|
|
|
/* file */
|
|
&pipefs_create,
|
|
&pipefs_open,
|
|
&pipefs_close,
|
|
&pipefs_free_cookie,
|
|
&pipefs_read,
|
|
&pipefs_write,
|
|
|
|
/* directory */
|
|
&pipefs_create_dir,
|
|
&pipefs_remove_dir,
|
|
&pipefs_open_dir,
|
|
&pipefs_close_dir,
|
|
&pipefs_free_dir_cookie,
|
|
&pipefs_read_dir,
|
|
&pipefs_rewind_dir,
|
|
|
|
// the other operations are not supported (attributes, indices, queries)
|
|
NULL,
|
|
};
|
|
|
|
} // namespace pipefs
|
|
|
|
extern "C" status_t
|
|
bootstrap_pipefs(void)
|
|
{
|
|
TRACE(("bootstrap_pipefs: entry\n"));
|
|
|
|
return vfs_register_filesystem("pipefs", &pipefs::pipefs_ops);
|
|
}
|