haiku/src/add-ons/kernel/file_systems/cdda/kernel_interface.cpp
Axel Dörfler e8113cabe0 * Implemented attribute handling - you can change them, but they are not stored yet.
* Implemented CDDB ID computation (that's what BeOS's cdda-fs is using).
* Some other minor changes.


git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@21144 a95241bf-73f2-0310-859d-f6bbb57e9c96
2007-05-15 17:33:05 +00:00

1462 lines
29 KiB
C++

/*
* Copyright 2007, Axel Dörfler, axeld@pinc-software.de.
* Distributed under the terms of the MIT License.
*/
#include "cdda.h"
#include "Lock.h"
#include <fs_info.h>
#include <fs_interface.h>
#include <KernelExport.h>
#include <Mime.h>
#include <TypeConstants.h>
#include <util/kernel_cpp.h>
#include <util/DoublyLinkedList.h>
#include <dirent.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
//#define TRACE_CDDA
#ifdef TRACE_CDDA
# define TRACE(x) dprintf x
#else
# define TRACE(x)
#endif
class Attribute;
class Inode;
struct attr_cookie;
struct dir_cookie;
typedef DoublyLinkedList<Attribute> AttributeList;
typedef DoublyLinkedList<attr_cookie> AttrCookieList;
class Volume {
public:
Volume(mount_id id);
~Volume();
status_t InitCheck();
mount_id ID() const { return fID; }
Inode &RootNode() const { return *fRootNode; }
status_t Mount(const char* device);
int Device() const { return fDevice; }
vnode_id GetNextNodeID() { return fNextID++; }
const char *Name() const { return fName; }
status_t SetName(const char *name);
Semaphore &Lock();
Inode *Find(vnode_id id);
Inode *Find(const char *name);
Inode *FirstEntry() const { return fFirstEntry; }
off_t NumBlocks() const { return fNumBlocks; }
private:
Inode *_CreateNode(Inode *parent, const char *name,
off_t start, off_t frames, int32 type);
Semaphore fLock;
int fDevice;
mount_id fID;
Inode *fRootNode;
vnode_id fNextID;
char *fName;
off_t fNumBlocks;
// root directory contents - we don't support other directories
Inode *fFirstEntry;
};
class Attribute : public DoublyLinkedListLinkImpl<Attribute> {
public:
Attribute(const char *name, type_code type);
~Attribute();
status_t InitCheck() const { return fName != NULL ? B_OK : B_NO_MEMORY; }
status_t SetTo(const char *name, type_code type);
void SetType(type_code type) { fType = type; }
status_t ReadAt(off_t offset, uint8 *buffer, size_t *_length);
status_t WriteAt(off_t offset, const uint8 *buffer, size_t *_length);
void Truncate();
const char *Name() const { return fName; }
size_t Size() const { return fSize; }
type_code Type() const { return fType; }
private:
char *fName;
type_code fType;
uint8 *fData;
size_t fSize;
};
class Inode {
public:
Inode(Volume *volume, Inode *parent, const char *name, off_t start,
off_t frames, int32 type);
~Inode();
status_t InitCheck();
vnode_id ID() const { return fID; }
const char *Name() const { return fName; }
status_t SetName(const char* name);
int32 Type() const
{ return fType; }
gid_t GroupID() const
{ return fGroupID; }
uid_t UserID() const
{ return fUserID; }
time_t CreationTime() const
{ return fCreationTime; }
time_t ModificationTime() const
{ return fModificationTime; }
off_t StartFrame() const
{ return fStartFrame; }
off_t FrameCount() const
{ return fFrameCount; }
off_t Size() const
{ return fFrameCount * kFrameSize /* + WAV header */; }
Attribute *FindAttribute(const char *name) const;
status_t AddAttribute(const char *name, type_code type,
const uint8 *data = 0, size_t length = 0);
status_t AddAttribute(const char *name, type_code type,
const char *string);
status_t AddAttribute(const char *name, int32 value);
status_t RemoveAttribute(const char *name);
void AddAttrCookie(attr_cookie *cookie);
void RemoveAttrCookie(attr_cookie *cookie);
void RewindAttrCookie(attr_cookie *cookie);
Inode *Next() const { return fNext; }
void SetNext(Inode *inode) { fNext = inode; }
private:
Inode *fNext;
vnode_id fID;
int32 fType;
char *fName;
gid_t fGroupID;
uid_t fUserID;
time_t fCreationTime;
time_t fModificationTime;
off_t fStartFrame;
off_t fFrameCount;
AttributeList fAttributes;
AttrCookieList fAttrCookies;
};
struct dir_cookie {
Inode *current;
int state; // iteration state
};
// directory iteration states
enum {
ITERATION_STATE_DOT = 0,
ITERATION_STATE_DOT_DOT = 1,
ITERATION_STATE_OTHERS = 2,
ITERATION_STATE_BEGIN = ITERATION_STATE_DOT,
};
struct attr_cookie : DoublyLinkedListLinkImpl<attr_cookie> {
Attribute *current;
};
struct file_cookie {
int open_mode;
};
Volume::Volume(mount_id id)
:
fLock("cdda"),
fDevice(-1),
fID(id),
fRootNode(NULL),
fNextID(1),
fName(NULL),
fNumBlocks(0),
fFirstEntry(NULL)
{
// create the root vnode
fRootNode = _CreateNode(NULL, "", 0, 0, S_IFDIR | 0777);
}
Volume::~Volume()
{
close(fDevice);
// put_vnode on the root to release the ref to it
if (fRootNode)
put_vnode(ID(), fRootNode->ID());
Inode *inode, *next;
for (inode = fFirstEntry; inode != NULL; inode = next) {
next = inode->Next();
delete inode;
}
free(fName);
}
status_t
Volume::InitCheck()
{
if (fLock.InitCheck() < B_OK
|| fRootNode == NULL)
return B_ERROR;
return B_OK;
}
status_t
Volume::Mount(const char* device)
{
fDevice = open(device, O_RDONLY);
if (fDevice < 0)
return errno;
scsi_toc_toc *toc = (scsi_toc_toc *)malloc(1024);
if (toc == NULL)
return B_NO_MEMORY;
status_t status = read_table_of_contents(fDevice, toc, 1024);
if (status < B_OK) {
free(toc);
return status;
}
cdtext text;
if (read_cdtext(fDevice, text) < B_OK)
dprintf("CDDA: no CD-Text found.\n");
int32 trackCount = toc->last_track + 1 - toc->first_track;
char title[256];
for (int32 i = 0; i < trackCount; i++) {
scsi_cd_msf& next = toc->tracks[i + 1].start.time;
// the last track is always lead-out
scsi_cd_msf& start = toc->tracks[i].start.time;
int32 track = i + 1;
off_t startFrame = start.minute * kFramesPerMinute
+ start.second * kFramesPerSecond + start.frame;
off_t frames = next.minute * kFramesPerMinute
+ next.second * kFramesPerSecond + next.frame
- startFrame;
const char *artist = text.artists[i] != NULL
? text.artists[i] : text.artist;
if (text.titles[i] != NULL) {
if (artist != NULL) {
snprintf(title, sizeof(title), "%02ld. %s - %s.wav", track,
text.artists[i], text.titles[i]);
} else {
snprintf(title, sizeof(title), "%02ld. %s.wav", track,
text.titles[i]);
}
} else
snprintf(title, sizeof(title), "%02ld.wav", track);
// remove '/' from title
for (int32 j = 0; title[j]; j++) {
if (title[j] == '/')
title[j] = '-';
}
Inode *inode = _CreateNode(fRootNode, title, startFrame, frames,
S_IFREG | 0444);
if (inode == NULL)
continue;
// add attributes
inode->AddAttribute("Audio:Artist", B_STRING_TYPE, artist);
inode->AddAttribute("Audio:Title", B_STRING_TYPE, text.titles[i]);
inode->AddAttribute("Audio:Genre", B_STRING_TYPE, text.genre);
inode->AddAttribute("Audio:Track", track);
snprintf(title, sizeof(title), "%02lu:%02lu",
uint32(inode->FrameCount() / kFramesPerMinute),
uint32((inode->FrameCount() % kFramesPerMinute) / kFramesPerSecond));
inode->AddAttribute("Audio:Length", B_STRING_TYPE, title);
inode->AddAttribute("BEOS:TYPE", B_MIME_STRING_TYPE, "audio/x-wav");
}
free(toc);
// determine volume title
if (text.artist != NULL && text.album != NULL)
snprintf(title, sizeof(title), "%s - %s", text.artist, text.album);
else if (text.artist != NULL || text.album != NULL) {
snprintf(title, sizeof(title), "%s", text.artist != NULL
? text.artist : text.album);
} else
strcpy(title, "Audio CD");
fName = strdup(title);
if (fName == NULL)
return B_NO_MEMORY;
return B_OK;
}
Semaphore&
Volume::Lock()
{
return fLock;
}
Inode *
Volume::_CreateNode(Inode *parent, const char *name, off_t start, off_t frames,
int32 type)
{
Inode *inode = new Inode(this, parent, name, start, frames, type);
if (inode == NULL)
return NULL;
if (inode->InitCheck() != B_OK) {
delete inode;
return NULL;
}
if (S_ISREG(type)) {
inode->SetNext(fFirstEntry);
fFirstEntry = inode;
}
publish_vnode(ID(), inode->ID(), inode);
return inode;
}
Inode *
Volume::Find(vnode_id id)
{
for (Inode *inode = fFirstEntry; inode != NULL; inode = inode->Next()) {
if (inode->ID() == id)
return inode;
}
return NULL;
}
Inode *
Volume::Find(const char *name)
{
if (!strcmp(name, ".")
|| !strcmp(name, ".."))
return fRootNode;
for (Inode *inode = fFirstEntry; inode != NULL; inode = inode->Next()) {
if (!strcmp(inode->Name(), name))
return inode;
}
return NULL;
}
status_t
Volume::SetName(const char *name)
{
if (name == NULL || !name[0])
return B_BAD_VALUE;
name = strdup(name);
if (name == NULL)
return B_NO_MEMORY;
free(fName);
fName = (char *)name;
return B_OK;
}
// #pragma mark -
Attribute::Attribute(const char *name, type_code type)
:
fName(NULL),
fType(0),
fData(NULL),
fSize(0)
{
SetTo(name, type);
}
Attribute::~Attribute()
{
free(fName);
free(fData);
}
status_t
Attribute::SetTo(const char *name, type_code type)
{
if (name == NULL || !name[0])
return B_BAD_VALUE;
name = strdup(name);
if (name == NULL)
return B_NO_MEMORY;
free(fName);
fName = (char *)name;
fType = type;
return B_OK;
}
status_t
Attribute::ReadAt(off_t offset, uint8 *buffer, size_t *_length)
{
size_t length = *_length;
if (offset < 0)
return B_BAD_VALUE;
if (offset >= length) {
*_length = 0;
return B_OK;
}
if (offset + length > fSize)
length = fSize - offset;
if (user_memcpy(buffer, fData + offset, length) < B_OK)
return B_BAD_ADDRESS;
*_length = length;
return B_OK;
}
status_t
Attribute::WriteAt(off_t offset, const uint8 *buffer, size_t *_length)
{
size_t length = *_length;
if (offset < 0)
return B_BAD_VALUE;
// we limit the attribute size to something reasonable
off_t end = offset + length;
if (end > 65536) {
end = 65536;
length = end - offset;
}
if (offset > end) {
*_length = 0;
return E2BIG;
}
if (end > fSize) {
// make room in the data stream
uint8 *data = (uint8 *)realloc(fData, end);
if (data == NULL)
return B_NO_MEMORY;
if (fSize < offset)
memset(data + fSize, 0, offset - fSize);
fData = data;
fSize = end;
}
if (user_memcpy(fData + offset, buffer, length) < B_OK)
return B_BAD_ADDRESS;
*_length = length;
return B_OK;
}
void
Attribute::Truncate()
{
free(fData);
fData = NULL;
fSize = 0;
}
// #pragma mark -
Inode::Inode(Volume *volume, Inode *parent, const char *name, off_t start,
off_t frames, int32 type)
:
fNext(NULL)
{
fName = strdup(name);
if (fName == NULL)
return;
fID = volume->GetNextNodeID();
fType = type;
fStartFrame = start;
fFrameCount = frames;
fUserID = geteuid();
fGroupID = parent ? parent->GroupID() : getegid();
fCreationTime = fModificationTime = time(NULL);
}
Inode::~Inode()
{
free(const_cast<char *>(fName));
}
status_t
Inode::InitCheck()
{
if (fName == NULL)
return B_NO_MEMORY;
return B_OK;
}
status_t
Inode::SetName(const char* name)
{
if (name == NULL || !name[0])
return B_BAD_VALUE;
name = strdup(name);
if (name == NULL)
return B_NO_MEMORY;
free(fName);
fName = (char *)name;
return B_OK;
}
Attribute *
Inode::FindAttribute(const char *name) const
{
if (name == NULL || !name[0])
return NULL;
AttributeList::ConstIterator iterator = fAttributes.GetIterator();
while (iterator.HasNext()) {
Attribute *attribute = iterator.Next();
if (!strcmp(attribute->Name(), name))
return attribute;
}
return NULL;
}
status_t
Inode::AddAttribute(const char *name, type_code type,
const uint8 *data, size_t length)
{
if (FindAttribute(name) != NULL)
return B_NAME_IN_USE;
Attribute *attribute = new Attribute(name, type);
status_t status = attribute != NULL ? B_OK : B_NO_MEMORY;
if (status == B_OK)
status = attribute->InitCheck();
if (status == B_OK && data != NULL && length != 0)
status = attribute->WriteAt(0, data, &length);
if (status < B_OK) {
delete attribute;
return status;
}
fAttributes.Add(attribute);
return B_OK;
}
status_t
Inode::AddAttribute(const char *name, type_code type,
const char *string)
{
if (string == NULL)
return NULL;
return AddAttribute(name, type, (const uint8 *)string,
strlen(string));
}
status_t
Inode::AddAttribute(const char *name, int32 value)
{
return AddAttribute(name, B_INT32_TYPE, (const uint8 *)&value,
sizeof(int32));
}
status_t
Inode::RemoveAttribute(const char *name)
{
if (name == NULL || !name[0])
return B_ENTRY_NOT_FOUND;
AttributeList::Iterator iterator = fAttributes.GetIterator();
while (iterator.HasNext()) {
Attribute *attribute = iterator.Next();
if (!strcmp(attribute->Name(), name)) {
// look for attribute in cookies
AttrCookieList::Iterator i = fAttrCookies.GetIterator();
while (i.HasNext()) {
attr_cookie *cookie = i.Next();
if (cookie->current == attribute)
cookie->current = attribute->GetDoublyLinkedListLink()->next;
}
iterator.Remove();
delete attribute;
return B_OK;
}
}
return B_ENTRY_NOT_FOUND;
}
void
Inode::AddAttrCookie(attr_cookie *cookie)
{
fAttrCookies.Add(cookie);
RewindAttrCookie(cookie);
}
void
Inode::RemoveAttrCookie(attr_cookie *cookie)
{
fAttrCookies.Remove(cookie);
}
void
Inode::RewindAttrCookie(attr_cookie *cookie)
{
cookie->current = fAttributes.First();
}
// #pragma mark -
void
fill_stat_buffer(Volume *volume, Inode *inode, Attribute *attribute,
struct stat &stat)
{
stat.st_dev = volume->ID();
stat.st_ino = inode->ID();
if (attribute != NULL) {
stat.st_size = attribute->Size();
stat.st_mode = S_ATTR | 0666;
stat.st_type = attribute->Type();
} else {
stat.st_size = inode->Size();
stat.st_mode = inode->Type();
stat.st_type = 0;
}
stat.st_nlink = 1;
stat.st_blksize = 2048;
stat.st_uid = inode->UserID();
stat.st_gid = inode->GroupID();
stat.st_atime = time(NULL);
stat.st_mtime = stat.st_ctime = inode->ModificationTime();
stat.st_crtime = inode->CreationTime();
}
// #pragma mark - module API
static status_t
cdda_mount(mount_id id, const char *device, uint32 flags, const char *args,
fs_volume *_volume, vnode_id *_rootVnodeID)
{
TRACE(("cdda_mount: entry\n"));
Volume *volume = new Volume(id);
if (volume == NULL)
return B_NO_MEMORY;
status_t status = volume->InitCheck();
if (status == B_OK)
status = volume->Mount(device);
if (status < B_OK) {
delete volume;
return status;
}
*_rootVnodeID = volume->RootNode().ID();
*_volume = volume;
return B_OK;
}
static status_t
cdda_unmount(fs_volume _volume)
{
struct Volume *volume = (struct Volume *)_volume;
TRACE(("cdda_unmount: entry fs = %p\n", _volume));
delete volume;
return 0;
}
static status_t
cdda_read_fs_stat(fs_volume _volume, struct fs_info *info)
{
Volume *volume = (Volume *)_volume;
Locker locker(volume->Lock());
// File system flags.
info->flags = B_FS_IS_PERSISTENT | B_FS_HAS_ATTR | B_FS_HAS_MIME
| B_FS_IS_REMOVABLE;
info->io_size = 65536;
info->block_size = 2048;
info->total_blocks = volume->NumBlocks();
info->free_blocks = 0;
// Volume name
strlcpy(info->volume_name, volume->Name(), sizeof(info->volume_name));
// File system name
strlcpy(info->fsh_name, "cdda", sizeof(info->fsh_name));
return B_OK;
}
static status_t
cdda_write_fs_stat(fs_volume _volume, const struct fs_info *info, uint32 mask)
{
Volume *volume = (Volume *)_volume;
Locker locker(volume->Lock());
status_t status = B_BAD_VALUE;
if (mask & FS_WRITE_FSINFO_NAME)
status = volume->SetName(info->volume_name);
return status;
}
static status_t
cdda_sync(fs_volume fs)
{
TRACE(("cdda_sync: entry\n"));
return 0;
}
static status_t
cdda_lookup(fs_volume _volume, fs_vnode _dir, const char *name, vnode_id *_id, int *_type)
{
Volume *volume = (Volume *)_volume;
status_t status;
TRACE(("cdda_lookup: entry dir %p, name '%s'\n", _dir, name));
Inode *directory = (Inode *)_dir;
if (!S_ISDIR(directory->Type()))
return B_NOT_A_DIRECTORY;
Locker _(volume->Lock());
Inode *inode = volume->Find(name);
if (inode == NULL)
return B_ENTRY_NOT_FOUND;
Inode *dummy;
status = get_vnode(volume->ID(), inode->ID(), (fs_vnode *)&dummy);
if (status < B_OK)
return status;
*_id = inode->ID();
*_type = inode->Type();
return B_OK;
}
static status_t
cdda_get_vnode_name(fs_volume _volume, fs_vnode _node, char *buffer, size_t bufferSize)
{
Volume *volume = (Volume *)_volume;
Inode *inode = (Inode *)_node;
TRACE(("cdda_get_vnode_name(): inode = %p\n", inode));
Locker _(volume->Lock());
strlcpy(buffer, inode->Name(), bufferSize);
return B_OK;
}
static status_t
cdda_get_vnode(fs_volume _volume, vnode_id id, fs_vnode *_inode, bool reenter)
{
Volume *volume = (Volume *)_volume;
Inode *inode;
TRACE(("cdda_getvnode: asking for vnode 0x%Lx, r %d\n", id, reenter));
inode = volume->Find(id);
if (inode == NULL)
return B_ENTRY_NOT_FOUND;
*_inode = inode;
return B_OK;
}
static status_t
cdda_put_vnode(fs_volume _volume, fs_vnode _node, bool reenter)
{
return B_OK;
}
static status_t
cdda_open(fs_volume _volume, fs_vnode _node, int openMode, fs_cookie *_cookie)
{
TRACE(("cdda_open(): node = %p, openMode = %d\n", _node, openMode));
file_cookie *cookie = (file_cookie *)malloc(sizeof(file_cookie));
if (cookie == NULL)
return B_NO_MEMORY;
TRACE((" open cookie = %p\n", cookie));
cookie->open_mode = openMode;
*_cookie = (void *)cookie;
return B_OK;
}
static status_t
cdda_close(fs_volume _volume, fs_vnode _node, fs_cookie _cookie)
{
return B_OK;
}
static status_t
cdda_free_cookie(fs_volume _volume, fs_vnode _node, fs_cookie _cookie)
{
file_cookie *cookie = (file_cookie *)_cookie;
TRACE(("cdda_freecookie: entry vnode %p, cookie %p\n", _node, _cookie));
free(cookie);
return B_OK;
}
static status_t
cdda_fsync(fs_volume _volume, fs_vnode _v)
{
return B_OK;
}
static status_t
cdda_read(fs_volume _volume, fs_vnode _node, fs_cookie _cookie, off_t offset,
void *buffer, size_t *_length)
{
file_cookie *cookie = (file_cookie *)_cookie;
Inode *inode = (Inode *)_node;
TRACE(("cdda_read(vnode = %p, offset %Ld, length = %lu, mode = %d)\n",
_node, offset, *_length, cookie->open_mode));
if (S_ISDIR(inode->Type()))
return B_IS_A_DIRECTORY;
if ((cookie->open_mode & O_RWMASK) != O_RDONLY)
return B_NOT_ALLOWED;
// TODO: read!
*_length = 0;
return B_OK;
}
static bool
cdda_can_page(fs_volume _volume, fs_vnode _v, fs_cookie cookie)
{
return false;
}
static status_t
cdda_read_pages(fs_volume _volume, fs_vnode _v, fs_cookie cookie, off_t pos,
const iovec *vecs, size_t count, size_t *_numBytes, bool reenter)
{
return EPERM;
}
static status_t
cdda_write_pages(fs_volume _volume, fs_vnode _v, fs_cookie cookie, off_t pos,
const iovec *vecs, size_t count, size_t *_numBytes, bool reenter)
{
return EPERM;
}
static status_t
cdda_read_stat(fs_volume _volume, fs_vnode _node, struct stat *stat)
{
Volume *volume = (Volume *)_volume;
Inode *inode = (Inode *)_node;
TRACE(("cdda_read_stat: vnode %p (0x%Lx), stat %p\n", inode, inode->ID(), stat));
fill_stat_buffer(volume, inode, NULL, *stat);
return B_OK;
}
status_t
cdda_rename(fs_volume _volume, void *_oldDir, const char *oldName, void *_newDir,
const char *newName)
{
if (_volume == NULL || _oldDir == NULL || _newDir == NULL
|| oldName == NULL || *oldName == '\0'
|| newName == NULL || *newName == '\0'
|| !strcmp(oldName, ".") || !strcmp(oldName, "..")
|| !strcmp(newName, ".") || !strcmp(newName, "..")
|| strchr(newName, '/') != NULL)
return B_BAD_VALUE;
// we only have a single directory which simplifies things a bit :-)
Volume *volume = (Volume *)_volume;
Locker _(volume->Lock());
Inode *inode = volume->Find(oldName);
if (inode == NULL)
return B_ENTRY_NOT_FOUND;
if (volume->Find(newName) != NULL)
return B_NAME_IN_USE;
return inode->SetName(newName);
}
// #pragma mark - directory functions
static status_t
cdda_open_dir(fs_volume _volume, fs_vnode _node, fs_cookie *_cookie)
{
Volume *volume = (Volume *)_volume;
TRACE(("cdda_open_dir(): vnode = %p\n", _node));
Inode *inode = (Inode *)_node;
if (!S_ISDIR(inode->Type()))
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;
cookie->current = volume->FirstEntry();
cookie->state = ITERATION_STATE_BEGIN;
*_cookie = (void *)cookie;
return B_OK;
}
static status_t
cdda_read_dir(fs_volume _volume, fs_vnode _node, fs_cookie _cookie,
struct dirent *dirent, size_t bufferSize, uint32 *_num)
{
Volume *volume = (Volume *)_volume;
Inode *inode = (Inode *)_node;
status_t status = 0;
TRACE(("cdda_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;
Locker _(volume->Lock());
dir_cookie *cookie = (dir_cookie *)_cookie;
Inode *childNode = NULL;
const char *name = NULL;
Inode *nextChildNode = NULL;
int nextState = cookie->state;
switch (cookie->state) {
case ITERATION_STATE_DOT:
childNode = inode;
name = ".";
nextChildNode = volume->FirstEntry();
nextState = cookie->state + 1;
break;
case ITERATION_STATE_DOT_DOT:
childNode = inode; // parent of the root node is the root node
name = "..";
nextChildNode = volume->FirstEntry();
nextState = cookie->state + 1;
break;
default:
childNode = cookie->current;
if (childNode) {
name = childNode->Name();
nextChildNode = childNode->Next();
}
break;
}
if (!childNode) {
// we're at the end of the directory
*_num = 0;
return B_OK;
}
dirent->d_dev = volume->ID();
dirent->d_ino = inode->ID();
dirent->d_reclen = strlen(name) + sizeof(struct dirent);
if (dirent->d_reclen > bufferSize)
return ENOBUFS;
status = user_strlcpy(dirent->d_name, name, bufferSize);
if (status < B_OK)
return status;
cookie->current = nextChildNode;
cookie->state = nextState;
return B_OK;
}
static status_t
cdda_rewind_dir(fs_volume _volume, fs_vnode _vnode, fs_cookie _cookie)
{
Volume *volume = (Volume *)_volume;
dir_cookie *cookie = (dir_cookie *)_cookie;
cookie->current = volume->FirstEntry();
cookie->state = ITERATION_STATE_BEGIN;
return B_OK;
}
static status_t
cdda_close_dir(fs_volume _volume, fs_vnode _node, fs_cookie _cookie)
{
TRACE(("cdda_close: entry vnode %p, cookie %p\n", _node, _cookie));
return 0;
}
static status_t
cdda_free_dir_cookie(fs_volume _volume, fs_vnode _vnode, fs_cookie _cookie)
{
dir_cookie *cookie = (dir_cookie *)_cookie;
TRACE(("cdda_freecookie: entry vnode %p, cookie %p\n", _vnode, cookie));
free(cookie);
return 0;
}
// #pragma mark - attribute functions
static status_t
cdda_open_attr_dir(fs_volume _volume, fs_vnode _vnode, fs_cookie *_cookie)
{
Volume *volume = (Volume *)_volume;
Inode *inode = (Inode *)_vnode;
attr_cookie *cookie = new attr_cookie;
if (cookie == NULL)
return B_NO_MEMORY;
Locker _(volume->Lock());
inode->AddAttrCookie(cookie);
*_cookie = cookie;
return B_OK;
}
static status_t
cdda_close_attr_dir(fs_volume _volume, fs_vnode _vnode, fs_cookie _cookie)
{
return B_OK;
}
static status_t
cdda_free_attr_dir_cookie(fs_volume _volume, fs_vnode _vnode, fs_cookie _cookie)
{
Volume *volume = (Volume *)_volume;
Inode *inode = (Inode *)_vnode;
attr_cookie *cookie = (attr_cookie *)_cookie;
Locker _(volume->Lock());
inode->RemoveAttrCookie(cookie);
delete cookie;
return B_OK;
}
static status_t
cdda_rewind_attr_dir(fs_volume _volume, fs_vnode _vnode, fs_cookie _cookie)
{
Volume *volume = (Volume *)_volume;
Inode *inode = (Inode *)_vnode;
attr_cookie *cookie = (attr_cookie *)_cookie;
Locker _(volume->Lock());
inode->RewindAttrCookie(cookie);
return B_OK;
}
static status_t
cdda_read_attr_dir(fs_volume _volume, fs_vnode _vnode, fs_cookie _cookie,
struct dirent *dirent, size_t bufferSize, uint32 *_num)
{
Volume *volume = (Volume *)_volume;
Inode *inode = (Inode *)_vnode;
attr_cookie *cookie = (attr_cookie *)_cookie;
Locker _(volume->Lock());
Attribute *attribute = cookie->current;
if (attribute == NULL) {
*_num = 0;
return B_OK;
}
size_t length = strlcpy(dirent->d_name, attribute->Name(), bufferSize);
dirent->d_dev = volume->ID();
dirent->d_ino = inode->ID();
dirent->d_reclen = sizeof(struct dirent) + length;
cookie->current = attribute->GetDoublyLinkedListLink()->next;
*_num = 1;
return B_OK;
}
static status_t
cdda_create_attr(fs_volume _volume, fs_vnode _node, const char *name,
uint32 type, int openMode, fs_cookie *_cookie)
{
Volume *volume = (Volume *)_volume;
Inode *inode = (Inode *)_node;
Locker _(volume->Lock());
Attribute *attribute = inode->FindAttribute(name);
if (attribute == NULL) {
status_t status = inode->AddAttribute(name, type);
if (status < B_OK)
return status;
} else if ((openMode & O_EXCL) == 0) {
attribute->SetType(type);
} else
return B_FILE_EXISTS;
*_cookie = strdup(name);
if (*_cookie == NULL)
return B_NO_MEMORY;
return B_OK;
}
static status_t
cdda_open_attr(fs_volume _volume, fs_vnode _node, const char *name,
int openMode, fs_cookie *_cookie)
{
Volume *volume = (Volume *)_volume;
Inode *inode = (Inode *)_node;
Locker _(volume->Lock());
Attribute *attribute = inode->FindAttribute(name);
if (attribute == NULL)
return B_ENTRY_NOT_FOUND;
*_cookie = strdup(name);
if (*_cookie == NULL)
return B_NO_MEMORY;
return B_OK;
}
static status_t
cdda_close_attr(fs_volume _fs, fs_vnode _file, fs_cookie cookie)
{
return B_OK;
}
static status_t
cdda_free_attr_cookie(fs_volume _fs, fs_vnode _file, fs_cookie cookie)
{
free(cookie);
return B_OK;
}
static status_t
cdda_read_attr(fs_volume _volume, fs_vnode _node, fs_cookie _cookie,
off_t offset, void *buffer, size_t *_length)
{
Volume *volume = (Volume *)_volume;
Inode *inode = (Inode *)_node;
Locker _(volume->Lock());
Attribute *attribute = inode->FindAttribute((const char *)_cookie);
if (attribute == NULL)
return B_ENTRY_NOT_FOUND;
return attribute->ReadAt(offset, (uint8 *)buffer, _length);
}
static status_t
cdda_write_attr(fs_volume _volume, fs_vnode _file, fs_cookie _cookie,
off_t offset, const void *buffer, size_t *_length)
{
Volume *volume = (Volume *)_volume;
Inode *inode = (Inode *)_file;
Locker _(volume->Lock());
Attribute *attribute = inode->FindAttribute((const char *)_cookie);
if (attribute == NULL)
return B_ENTRY_NOT_FOUND;
return attribute->WriteAt(offset, (uint8 *)buffer, _length);
}
static status_t
cdda_read_attr_stat(fs_volume _volume, fs_vnode _file, fs_cookie _cookie,
struct stat *stat)
{
Volume *volume = (Volume *)_volume;
Inode *inode = (Inode *)_file;
Locker _(volume->Lock());
Attribute *attribute = inode->FindAttribute((const char *)_cookie);
if (attribute == NULL)
return B_ENTRY_NOT_FOUND;
fill_stat_buffer(volume, inode, attribute, *stat);
return B_OK;
}
static status_t
cdda_write_attr_stat(fs_volume _volume, fs_vnode file, fs_cookie cookie,
const struct stat *stat, int statMask)
{
return EOPNOTSUPP;
}
static status_t
cdda_remove_attr(fs_volume _volume, fs_vnode _node, const char *name)
{
if (name == NULL)
return B_BAD_VALUE;
Volume *volume = (Volume *)_volume;
Inode *inode = (Inode *)_node;
Locker _(volume->Lock());
return inode->RemoveAttribute(name);
}
static status_t
cdda_std_ops(int32 op, ...)
{
switch (op) {
case B_MODULE_INIT:
return B_OK;
case B_MODULE_UNINIT:
return B_OK;
default:
return B_ERROR;
}
}
static file_system_module_info sCDDAFileSystem = {
{
"file_systems/cdda" B_CURRENT_FS_API_VERSION,
0,
cdda_std_ops,
},
"CDDA File System",
NULL, // identify_partition()
NULL, // scan_partition()
NULL, // free_identify_partition_cookie()
NULL, // free_partition_content_cookie()
&cdda_mount,
&cdda_unmount,
&cdda_read_fs_stat,
&cdda_write_fs_stat,
&cdda_sync,
&cdda_lookup,
&cdda_get_vnode_name,
&cdda_get_vnode,
&cdda_put_vnode,
NULL, // fs_remove_vnode()
&cdda_can_page,
&cdda_read_pages,
&cdda_write_pages,
NULL, // get_file_map()
// common
NULL, // fs_ioctl()
NULL, // fs_set_flags()
NULL, // fs_select()
NULL, // fs_deselect()
&cdda_fsync,
NULL, // fs_read_link()
NULL, // fs_symlink()
NULL, // fs_link()
NULL, // fs_unlink()
&cdda_rename,
NULL, // fs_access()
&cdda_read_stat,
NULL, // fs_write_stat()
// file
NULL, // fs_create()
&cdda_open,
&cdda_close,
&cdda_free_cookie,
&cdda_read,
NULL, // fs_write()
// directory
NULL, // fs_create_dir()
NULL, // fs_remove_dir()
&cdda_open_dir,
&cdda_close_dir,
&cdda_free_dir_cookie,
&cdda_read_dir,
&cdda_rewind_dir,
// attribute directory operations
&cdda_open_attr_dir,
&cdda_close_attr_dir,
&cdda_free_attr_dir_cookie,
&cdda_read_attr_dir,
&cdda_rewind_attr_dir,
// attribute operations
&cdda_create_attr,
&cdda_open_attr,
&cdda_close_attr,
&cdda_free_attr_cookie,
&cdda_read_attr,
&cdda_write_attr,
&cdda_read_attr_stat,
&cdda_write_attr_stat,
NULL, // fs_rename_attr()
&cdda_remove_attr,
// the other operations are not yet supported (indices, queries)
NULL,
};
module_info *modules[] = {
(module_info *)&sCDDAFileSystem,
NULL,
};