/*
* Copyright 2007-2016, Axel Dörfler, axeld@pinc-software.de.
* Distributed under the terms of the MIT License.
*/
#include <dirent.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <FindDirectory.h>
#include <fs_info.h>
#include <fs_interface.h>
#include <KernelExport.h>
#include <Mime.h>
#include <NodeMonitor.h>
#include <TypeConstants.h>
#include <AutoDeleter.h>
#include <util/SinglyLinkedList.h>
#include <util/DoublyLinkedList.h>
#include "cdda.h"
#include "cddb.h"
#include "Lock.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 SinglyLinkedList<attr_cookie> AttrCookieList;
struct riff_header {
uint32 magic;
uint32 length;
uint32 id;
} _PACKED;
struct riff_chunk {
uint32 fourcc;
uint32 length;
} _PACEKD;
struct wav_format_chunk : riff_chunk {
uint16 format_tag;
uint16 channels;
uint32 samples_per_second;
uint32 average_bytes_per_second;
uint16 block_align;
uint16 bits_per_sample;
} _PACKED;
struct wav_header {
riff_header header;
wav_format_chunk format;
riff_chunk data;
} _PACKED;
enum attr_mode {
kDiscIDAttributes,
kSharedAttributes,
kDeviceAttributes
};
class Volume {
public:
Volume(fs_volume* fsVolume);
~Volume();
status_t InitCheck();
fs_volume* FSVolume() const { return fFSVolume; }
dev_t ID() const { return fFSVolume->id; }
uint32 DiscID() const { return fDiscID; }
Inode& RootNode() const { return *fRootNode; }
status_t Mount(const char* device);
int Device() const { return fDevice; }
ino_t GetNextNodeID() { return fNextID++; }
const char* Name() const { return fName; }
status_t SetName(const char* name);
Semaphore& Lock();
Inode* Find(ino_t id);
Inode* Find(const char* name);
Inode* FirstEntry() const { return fFirstEntry; }
off_t NumBlocks() const { return fNumBlocks; }
size_t BufferSize() const { return 32 * kFrameSize; }
// TODO: for now
void SetCDDBLookupsEnabled(bool doLookup);
static void DetermineName(uint32 cddbId, int device, char* name,
size_t length);
private:
Inode* _CreateNode(Inode* parent, const char* name,
uint64 start, uint64 frames, int32 type);
int _OpenAttributes(int mode,
enum attr_mode attrMode = kDiscIDAttributes);
void _RestoreAttributes();
void _RestoreAttributes(int fd);
void _StoreAttributes();
void _RestoreSharedAttributes();
void _StoreSharedAttributes();
Semaphore fLock;
fs_volume* fFSVolume;
int fDevice;
uint32 fDiscID;
Inode* fRootNode;
ino_t fNextID;
char* fName;
off_t fNumBlocks;
bool fIgnoreCDDBLookupChanges;
// 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();
status_t SetSize(off_t size);
const char* Name() const { return fName; }
off_t Size() const { return fSize; }
type_code Type() const { return fType; }
uint8* Data() const { return fData; }
bool IsProtectedNamespace();
static bool IsProtectedNamespace(const char* name);
private:
char* fName;
type_code fType;
uint8* fData;
off_t fSize;
};
class Inode {
public:
Inode(Volume* volume, Inode* parent,
const char* name, uint64 start, uint64 frames,
int32 type);
~Inode();
status_t InitCheck();
ino_t 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; }
uint64 StartFrame() const
{ return fStartFrame; }
uint64 FrameCount() const
{ return fFrameCount; }
uint64 Size() const
{ return fFrameCount * kFrameSize; }
// does not include the WAV header
Attribute* FindAttribute(const char* name) const;
status_t AddAttribute(Attribute* attribute, bool overwrite);
status_t AddAttribute(const char* name, type_code type,
bool overwrite, const uint8* data,
size_t length);
status_t AddAttribute(const char* name, type_code type,
const char* string);
status_t AddAttribute(const char* name, type_code type,
uint32 value);
status_t AddAttribute(const char* name, type_code type,
uint64 value);
status_t RemoveAttribute(const char* name,
bool checkNamespace = false);
void AddAttrCookie(attr_cookie* cookie);
void RemoveAttrCookie(attr_cookie* cookie);
void RewindAttrCookie(attr_cookie* cookie);
AttributeList::ConstIterator Attributes() const
{ return fAttributes.GetIterator(); }
const wav_header* WAVHeader() const
{ return &fWAVHeader; }
Inode* Next() const { return fNext; }
void SetNext(Inode *inode) { fNext = inode; }
private:
Inode* fNext;
ino_t fID;
int32 fType;
char* fName;
gid_t fGroupID;
uid_t fUserID;
time_t fCreationTime;
time_t fModificationTime;
uint64 fStartFrame;
uint64 fFrameCount;
AttributeList fAttributes;
AttrCookieList fAttrCookies;
wav_header fWAVHeader;
};
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 : SinglyLinkedListLinkImpl<attr_cookie> {
Attribute* current;
};
struct file_cookie {
int open_mode;
off_t buffer_offset;
void* buffer;
};
static const uint32 kMaxAttributeSize = 65536;
static const uint32 kMaxAttributes = 64;
static const char* kProtectedAttrNamespace = "CD:";
static const char* kCddbIdAttribute = "CD:cddbid";
static const char* kDoLookupAttribute = "CD:do_lookup";
static const char* kTocAttribute = "CD:toc";
extern fs_volume_ops gCDDAVolumeOps;
extern fs_vnode_ops gCDDAVnodeOps;
// #pragma mark helper functions
/*! Determines if the attribute is shared among all devices or among
all CDs in a specific device.
We use this to share certain Tracker attributes.
*/
static bool
is_special_attribute(const char* name, attr_mode attrMode)
{
if (attrMode == kDeviceAttributes) {
static const char* kAttributes[] = {
"_trk/windframe",
"_trk/pinfo",
"_trk/pinfo_le",
NULL,
};
for (int32 i = 0; kAttributes[i]; i++) {
if (!strcmp(name, kAttributes[i]))
return true;
}
} else if (attrMode == kSharedAttributes) {
static const char* kAttributes[] = {
"_trk/columns",
"_trk/columns_le",
"_trk/viewstate",
"_trk/viewstate_le",
NULL,
};
for (int32 i = 0; kAttributes[i]; i++) {
if (!strcmp(name, kAttributes[i]))
return true;
}
}
return false;
}
static void
write_line(int fd, const char* line)
{
if (line == NULL)
line = "";
size_t length = strlen(line);
write(fd, line, length);
write(fd, "\n", 1);
}
static void
write_attributes(int fd, Inode* inode, attr_mode attrMode = kDiscIDAttributes)
{
// count attributes
AttributeList::ConstIterator iterator = inode->Attributes();
uint32 count = 0;
while (iterator.HasNext()) {
Attribute* attribute = iterator.Next();
if ((attrMode == kDiscIDAttributes
|| is_special_attribute(attribute->Name(), attrMode))
&& !attribute->IsProtectedNamespace())
count++;
}
// we're artificially limiting the attribute count per inode
if (count > kMaxAttributes)
count = kMaxAttributes;
count = B_HOST_TO_BENDIAN_INT32(count);
write(fd, &count, sizeof(uint32));
// write attributes
iterator.Rewind();
while (iterator.HasNext()) {
Attribute* attribute = iterator.Next();
if ((attrMode != kDiscIDAttributes
&& !is_special_attribute(attribute->Name(), attrMode))
|| attribute->IsProtectedNamespace())
continue;
uint32 type = B_HOST_TO_BENDIAN_INT32(attribute->Type());
write(fd, &type, sizeof(uint32));
uint8 length = strlen(attribute->Name());
write(fd, &length, 1);
write(fd, attribute->Name(), length);
uint32 size = B_HOST_TO_BENDIAN_INT32(attribute->Size());
write(fd, &size, sizeof(uint32));
if (size != 0)
write(fd, attribute->Data(), attribute->Size());
if (--count == 0)
break;
}
}
static bool
read_line(int fd, char* line, size_t length)
{
bool first = true;
size_t pos = 0;
char c;
while (read(fd, &c, 1) == 1) {
first = false;
if (c == '\n')
break;
if (pos < length)
line[pos] = c;
pos++;
}
if (pos >= length - 1)
pos = length - 1;
line[pos] = '\0';
return !first;
}
static bool
read_attributes(int fd, Inode* inode)
{
uint32 count;
if (read(fd, &count, sizeof(uint32)) != (ssize_t)sizeof(uint32))
return false;
count = B_BENDIAN_TO_HOST_INT32(count);
if (count > kMaxAttributes)
return false;
for (uint32 i = 0; i < count; i++) {
char name[B_ATTR_NAME_LENGTH + 1];
uint32 type, size;
uint8 length;
if (read(fd, &type, sizeof(uint32)) != (ssize_t)sizeof(uint32)
|| read(fd, &length, 1) != 1
|| read(fd, name, length) != length
|| read(fd, &size, sizeof(uint32)) != (ssize_t)sizeof(uint32))
return false;
type = B_BENDIAN_TO_HOST_INT32(type);
size = B_BENDIAN_TO_HOST_INT32(size);
name[length] = '\0';
Attribute* attribute = new(std::nothrow) Attribute(name, type);
if (attribute == NULL)
return false;
if (attribute->IsProtectedNamespace()) {
// Attributes in the protected namespace are handled internally
// so we do not load them even if they are present in the
// attributes file.
delete attribute;
continue;
}
if (attribute->SetSize(size) != B_OK
|| inode->AddAttribute(attribute, true) != B_OK) {
delete attribute;
} else
read(fd, attribute->Data(), size);
}
return true;
}
static int
open_attributes(uint32 cddbID, int deviceFD, int mode,
enum attr_mode attrMode)
{
char* path = (char*)malloc(B_PATH_NAME_LENGTH);
if (path == NULL)
return -1;
MemoryDeleter deleter(path);
bool create = (mode & O_WRONLY) != 0;
if (find_directory(B_USER_SETTINGS_DIRECTORY, -1, create, path,
B_PATH_NAME_LENGTH) != B_OK) {
return -1;
}
strlcat(path, "/cdda", B_PATH_NAME_LENGTH);
if (create)
mkdir(path, 0755);
if (attrMode == kDiscIDAttributes) {
char id[64];
snprintf(id, sizeof(id), "/%08" B_PRIx32, cddbID);
strlcat(path, id, B_PATH_NAME_LENGTH);
} else if (attrMode == kDeviceAttributes) {
uint32 length = strlen(path);
char* deviceName = path + length;
if (ioctl(deviceFD, B_GET_PATH_FOR_DEVICE, deviceName,
B_PATH_NAME_LENGTH - length) < B_OK) {
return B_ERROR;
}
deviceName++;
// replace slashes in the device path
while (deviceName[0]) {
if (deviceName[0] == '/')
deviceName[0] = '_';
deviceName++;
}
} else
strlcat(path, "/shared", B_PATH_NAME_LENGTH);
return open(path, mode | (create ? O_CREAT | O_TRUNC : 0), 0644);
}
static void
fill_stat_buffer(Volume* volume, Inode* inode, Attribute* attribute,
struct stat& stat)
{
stat.st_dev = volume->FSVolume()->id;
stat.st_ino = inode->ID();
if (attribute != NULL) {
stat.st_size = attribute->Size();
stat.st_blocks = 0;
stat.st_mode = S_ATTR | 0666;
stat.st_type = attribute->Type();
} else {
stat.st_size = inode->Size() + sizeof(wav_header);
stat.st_blocks = inode->Size() / 512;
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_atim.tv_sec = time(NULL);
stat.st_atim.tv_nsec = 0;
stat.st_mtim.tv_sec = stat.st_ctim.tv_sec = inode->ModificationTime();
stat.st_ctim.tv_nsec = stat.st_mtim.tv_nsec = 0;
stat.st_crtim.tv_sec = inode->CreationTime();
stat.st_crtim.tv_nsec = 0;
}
bool
is_data_track(const scsi_toc_track& track)
{
return (track.control & 4) != 0;
}
uint32
count_audio_tracks(scsi_toc_toc* toc)
{
uint32 trackCount = toc->last_track + 1 - toc->first_track;
uint32 count = 0;
for (uint32 i = 0; i < trackCount; i++) {
if (!is_data_track(toc->tracks[i]))
count++;
}
return count;
}
// #pragma mark - Volume class
Volume::Volume(fs_volume* fsVolume)
:
fLock("cdda"),
fFSVolume(fsVolume),
fDevice(-1),
fRootNode(NULL),
fNextID(1),
fName(NULL),
fNumBlocks(0),
fIgnoreCDDBLookupChanges(false),
fFirstEntry(NULL)
{
}
Volume::~Volume()
{
if (fRootNode) {
_StoreAttributes();
_StoreSharedAttributes();
}
if (fDevice >= 0)
close(fDevice);
// put_vnode on the root to release the ref to it
if (fRootNode)
put_vnode(FSVolume(), fRootNode->ID());
delete fRootNode;
Inode* 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)
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;
MemoryDeleter deleter(toc);
status_t status = read_table_of_contents(fDevice, toc, 1024);
// there has to be at least one audio track
if (status == B_OK && count_audio_tracks(toc) == 0)
status = B_BAD_TYPE;
if (status != B_OK)
return status;
fDiscID = compute_cddb_disc_id(*toc);
// create the root vnode
fRootNode = _CreateNode(NULL, "", 0, 0, S_IFDIR | 0777);
if (fRootNode == NULL)
status = B_NO_MEMORY;
if (status == B_OK) {
status = publish_vnode(FSVolume(), fRootNode->ID(), fRootNode,
&gCDDAVnodeOps, fRootNode->Type(), 0);
}
if (status != B_OK)
return status;
bool doLookup = true;
cdtext text;
int fd = _OpenAttributes(O_RDONLY);
if (fd < 0) {
// We do not seem to have an attribute file so this is probably the
// first time this CD is inserted. In this case, try to read CD-Text
// data.
if (read_cdtext(fDevice, text) == B_OK)
doLookup = false;
else
TRACE(("CDDA: no CD-Text found.\n"));
} else {
doLookup = false;
}
int32 trackCount = toc->last_track + 1 - toc->first_track;
off_t totalFrames = 0;
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;
uint64 startFrame = start.minute * kFramesPerMinute
+ start.second * kFramesPerSecond + start.frame;
uint64 frames = next.minute * kFramesPerMinute
+ next.second * kFramesPerSecond + next.frame
- startFrame;
// Adjust length of the last audio track according to the Blue Book
// specification in case of an Enhanced CD
if (i + 1 < trackCount && is_data_track(toc->tracks[i + 1])
&& !is_data_track(toc->tracks[i]))
frames -= kDataTrackLeadGap;
totalFrames += frames;
if (is_data_track(toc->tracks[i]))
continue;
if (text.titles[i] != NULL) {
if (text.artists[i] != NULL) {
snprintf(title, sizeof(title), "%02" B_PRId32 ". %s - %s.wav",
track, text.artists[i], text.titles[i]);
} else {
snprintf(title, sizeof(title), "%02" B_PRId32 ". %s.wav",
track, text.titles[i]);
}
} else
snprintf(title, sizeof(title), "Track %02" B_PRId32 ".wav", track);
// remove '/' and '\n' from title
for (int32 j = 0; title[j]; j++) {
if (title[j] == '/')
title[j] = '-';
else if (title[j] == '\n')
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,
text.artists[i] != NULL ? text.artists[i] : text.artist);
inode->AddAttribute("Audio:Album", B_STRING_TYPE, text.album);
inode->AddAttribute("Audio:Title", B_STRING_TYPE, text.titles[i]);
inode->AddAttribute("Audio:Genre", B_STRING_TYPE, text.genre);
inode->AddAttribute("Audio:Track", B_INT32_TYPE, (uint32)track);
inode->AddAttribute("Audio:Bitrate", B_STRING_TYPE, "1411 kbps");
inode->AddAttribute("Media:Length", B_INT64_TYPE,
inode->FrameCount() * 1000000L / kFramesPerSecond);
inode->AddAttribute("BEOS:TYPE", B_MIME_STRING_TYPE, "audio/x-wav");
}
// Add CD:cddbid attribute.
fRootNode->AddAttribute(kCddbIdAttribute, B_UINT32_TYPE, fDiscID);
// Add CD:do_lookup attribute.
SetCDDBLookupsEnabled(doLookup);
// Add CD:toc attribute.
fRootNode->AddAttribute(kTocAttribute, B_RAW_TYPE, true,
(const uint8*)toc, B_BENDIAN_TO_HOST_INT16(toc->data_length) + 2);
_RestoreSharedAttributes();
if (fd >= 0) {
_RestoreAttributes(fd);
close(fd);
}
// determine volume title
DetermineName(fDiscID, fDevice, title, sizeof(title));
fName = strdup(title);
if (fName == NULL)
return B_NO_MEMORY;
fNumBlocks = totalFrames;
return B_OK;
}
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;
}
Semaphore&
Volume::Lock()
{
return fLock;
}
Inode*
Volume::Find(ino_t 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;
}
void
Volume::SetCDDBLookupsEnabled(bool doLookup)
{
if (!fIgnoreCDDBLookupChanges) {
fRootNode->AddAttribute(kDoLookupAttribute, B_BOOL_TYPE, true,
(const uint8*)&doLookup, sizeof(bool));
}
}
/*static*/ void
Volume::DetermineName(uint32 cddbID, int device, char* name, size_t length)
{
name[0] = '\0';
int attrFD = open_attributes(cddbID, device, O_RDONLY,
kDiscIDAttributes);
if (attrFD < 0) {
// We do not have attributes set. Read CD text.
cdtext text;
if (read_cdtext(device, text) == B_OK) {
if (text.artist != NULL && text.album != NULL)
snprintf(name, length, "%s - %s", text.artist, text.album);
else if (text.artist != NULL || text.album != NULL) {
snprintf(name, length, "%s", text.artist != NULL
? text.artist : text.album);
}
}
} else {
// We have an attribute file. Read name from it.
if (!read_line(attrFD, name, length))
name[0] = '\0';
close(attrFD);
}
if (!name[0])
strlcpy(name, "Audio CD", length);
}
Inode*
Volume::_CreateNode(Inode* parent, const char* name, uint64 start,
uint64 frames, int32 type)
{
Inode* inode = new(std::nothrow) 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)) {
// we need to order it by track for compatibility with BeOS' cdda
Inode* current = fFirstEntry;
Inode* last = NULL;
while (current != NULL) {
last = current;
current = current->Next();
}
if (last)
last->SetNext(inode);
else
fFirstEntry = inode;
}
return inode;
}
/*! Opens the file that contains the volume and inode titles as well as all
of their attributes.
The attributes are stored in files below B_USER_SETTINGS_DIRECTORY/cdda.
*/
int
Volume::_OpenAttributes(int mode, enum attr_mode attrMode)
{
return open_attributes(fDiscID, fDevice, mode, attrMode);
}
/*! Reads the attributes, if any, that belong to the CD currently being
mounted.
*/
void
Volume::_RestoreAttributes()
{
int fd = _OpenAttributes(O_RDONLY);
if (fd < 0)
return;
_RestoreAttributes(fd);
close(fd);
}
void
Volume::_RestoreAttributes(int fd)
{
char line[B_FILE_NAME_LENGTH];
if (!read_line(fd, line, B_FILE_NAME_LENGTH))
return;
SetName(line);
for (Inode* inode = fFirstEntry; inode != NULL; inode = inode->Next()) {
if (!read_line(fd, line, B_FILE_NAME_LENGTH))
break;
inode->SetName(line);
}
if (read_attributes(fd, fRootNode)) {
for (Inode* inode = fFirstEntry; inode != NULL; inode = inode->Next()) {
if (!read_attributes(fd, inode))
break;
}
}
}
void
Volume::_StoreAttributes()
{
int fd = _OpenAttributes(O_WRONLY);
if (fd < 0)
return;
write_line(fd, Name());
for (Inode* inode = fFirstEntry; inode != NULL; inode = inode->Next()) {
write_line(fd, inode->Name());
}
write_attributes(fd, fRootNode);
for (Inode* inode = fFirstEntry; inode != NULL; inode = inode->Next()) {
write_attributes(fd, inode);
}
close(fd);
}
/*! Restores the attributes, if any, that are shared between CDs; some are
stored per device, others are stored for all CDs no matter which device.
*/
void
Volume::_RestoreSharedAttributes()
{
// Don't affect CDDB lookup status while changing shared attributes
fIgnoreCDDBLookupChanges = true;
// device attributes overwrite shared attributes
int fd = _OpenAttributes(O_RDONLY, kSharedAttributes);
if (fd >= 0) {
read_attributes(fd, fRootNode);
close(fd);
}
fd = _OpenAttributes(O_RDONLY, kDeviceAttributes);
if (fd >= 0) {
read_attributes(fd, fRootNode);
close(fd);
}
fIgnoreCDDBLookupChanges = false;
}
void
Volume::_StoreSharedAttributes()
{
// write shared and device specific settings
int fd = _OpenAttributes(O_WRONLY, kSharedAttributes);
if (fd >= 0) {
write_attributes(fd, fRootNode, kSharedAttributes);
close(fd);
}
fd = _OpenAttributes(O_WRONLY, kDeviceAttributes);
if (fd >= 0) {
write_attributes(fd, fRootNode, kDeviceAttributes);
close(fd);
}
}
// #pragma mark - Attribute class
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 >= fSize) {
*_length = 0;
return B_OK;
}
if (offset + (off_t)length > fSize)
length = fSize - offset;
if (user_memcpy(buffer, fData + offset, length) < B_OK)
return B_BAD_ADDRESS;
*_length = length;
return B_OK;
}
/*! Writes to the attribute and enlarges it as needed.
An attribute has a maximum size of 65536 bytes for now.
*/
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 > kMaxAttributeSize) {
end = kMaxAttributeSize;
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;
}
//! Removes all data from the attribute.
void
Attribute::Truncate()
{
free(fData);
fData = NULL;
fSize = 0;
}
/*! Resizes the data part of an attribute to the requested amount \a size.
An attribute has a maximum size of 65536 bytes for now.
*/
status_t
Attribute::SetSize(off_t size)
{
if (size > kMaxAttributeSize)
return E2BIG;
uint8* data = (uint8*)realloc(fData, size);
if (data == NULL)
return B_NO_MEMORY;
if (fSize < size)
memset(data + fSize, 0, size - fSize);
fData = data;
fSize = size;
return B_OK;
}
bool
Attribute::IsProtectedNamespace()
{
// Check if the attribute is in the restricted namespace. Attributes in
// this namespace should not be edited by the user as they are handled
// internally by the add-on. Calls the static version.
return IsProtectedNamespace(fName);
}
bool
Attribute::IsProtectedNamespace(const char* name)
{
// Convenience static version of the above method. Usually called when we
// don't have a constructed Attribute object handy.
return strncmp(kProtectedAttrNamespace, name,
strlen(kProtectedAttrNamespace)) == 0;
}
// #pragma mark - Inode class
Inode::Inode(Volume* volume, Inode* parent, const char* name, uint64 start,
uint64 frames, int32 type)
:
fNext(NULL)
{
memset(&fWAVHeader, 0, sizeof(wav_header));
fID = volume->GetNextNodeID();
fType = type;
fStartFrame = start;
fFrameCount = frames;
fUserID = geteuid();
fGroupID = parent ? parent->GroupID() : getegid();
fCreationTime = fModificationTime = time(NULL);
fName = strdup(name);
if (fName == NULL)
return;
if (frames) {
// initialize WAV header
// RIFF header
fWAVHeader.header.magic = B_HOST_TO_BENDIAN_INT32('RIFF');
fWAVHeader.header.length = B_HOST_TO_LENDIAN_INT32(Size()
+ sizeof(wav_header) - sizeof(riff_chunk));
fWAVHeader.header.id = B_HOST_TO_BENDIAN_INT32('WAVE');
// 'fmt ' format chunk
fWAVHeader.format.fourcc = B_HOST_TO_BENDIAN_INT32('fmt ');
fWAVHeader.format.length = B_HOST_TO_LENDIAN_INT32(
sizeof(wav_format_chunk) - sizeof(riff_chunk));
fWAVHeader.format.format_tag = B_HOST_TO_LENDIAN_INT16(1);
fWAVHeader.format.channels = B_HOST_TO_LENDIAN_INT16(2);
fWAVHeader.format.samples_per_second = B_HOST_TO_LENDIAN_INT32(44100);
fWAVHeader.format.average_bytes_per_second = B_HOST_TO_LENDIAN_INT32(
44100 * sizeof(uint16) * 2);
fWAVHeader.format.block_align = B_HOST_TO_LENDIAN_INT16(4);
fWAVHeader.format.bits_per_sample = B_HOST_TO_LENDIAN_INT16(16);
// 'data' chunk
fWAVHeader.data.fourcc = B_HOST_TO_BENDIAN_INT32('data');
fWAVHeader.data.length = B_HOST_TO_LENDIAN_INT32(Size());
}
}
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]
|| strchr(name, '/') != NULL
|| strchr(name, '\n') != NULL)
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(Attribute* attribute, bool overwrite)
{
Attribute* oldAttribute = FindAttribute(attribute->Name());
if (oldAttribute != NULL) {
if (!overwrite)
return B_NAME_IN_USE;
fAttributes.Remove(oldAttribute);
delete oldAttribute;
}
fAttributes.Add(attribute);
return B_OK;
}
status_t
Inode::AddAttribute(const char* name, type_code type, bool overwrite,
const uint8* data, size_t length)
{
Attribute* attribute = new(std::nothrow) Attribute(name, type);
if (attribute == NULL)
return B_NO_MEMORY;
status_t status = attribute->InitCheck();
if (status == B_OK && data != NULL && length != 0)
status = attribute->WriteAt(0, data, &length);
if (status == B_OK)
status = AddAttribute(attribute, overwrite);
if (status != B_OK) {
delete attribute;
return status;
}
return B_OK;
}
status_t
Inode::AddAttribute(const char* name, type_code type, const char* string)
{
if (string == NULL)
return B_BAD_VALUE;
return AddAttribute(name, type, true, (const uint8*)string,
strlen(string));
}
status_t
Inode::AddAttribute(const char* name, type_code type, uint32 value)
{
uint32 data = B_HOST_TO_LENDIAN_INT32(value);
return AddAttribute(name, type, true, (const uint8*)&data, sizeof(uint32));
}
status_t
Inode::AddAttribute(const char* name, type_code type, uint64 value)
{
uint64 data = B_HOST_TO_LENDIAN_INT64(value);
return AddAttribute(name, type, true, (const uint8*)&data, sizeof(uint64));
}
status_t
Inode::RemoveAttribute(const char* name, bool checkNamespace)
{
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)) {
// check for restricted namespace if required.
if (checkNamespace && attribute->IsProtectedNamespace())
return B_NOT_ALLOWED;
// 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)
{
if (!fAttrCookies.Remove(cookie))
panic("Tried to remove %p which is not in cookie list.", cookie);
}
void
Inode::RewindAttrCookie(attr_cookie* cookie)
{
cookie->current = fAttributes.First();
}
// #pragma mark - Module API
static float
cdda_identify_partition(int fd, partition_data* partition, void** _cookie)
{
scsi_toc_toc* toc = (scsi_toc_toc*)malloc(2048);
if (toc == NULL)
return B_NO_MEMORY;
status_t status = read_table_of_contents(fd, toc, 2048);
// If we succeeded in reading the toc, check the tracks in the
// partition, which may not be the whole CD, and if any are audio,
// claim the partition.
if (status == B_OK) {
uint32 trackCount = toc->last_track + (uint32)1 - toc->first_track;
uint64 sessionStartLBA = partition->offset / partition->block_size;
uint64 sessionEndLBA = sessionStartLBA
+ (partition->size / partition->block_size);
TRACE(("cdda_identify_partition: session at %lld-%lld\n",
sessionStartLBA, sessionEndLBA));
status = B_ENTRY_NOT_FOUND;
for (uint32 i = 0; i < trackCount; i++) {
// We have to get trackLBA from track.start.time since
// track.start.lba is useless for this.
// This is how session gets it.
uint64 trackLBA
= ((toc->tracks[i].start.time.minute * kFramesPerMinute)
+ (toc->tracks[i].start.time.second * kFramesPerSecond)
+ toc->tracks[i].start.time.frame - 150);
if (trackLBA >= sessionStartLBA && trackLBA < sessionEndLBA) {
if (is_data_track(toc->tracks[i])) {
TRACE(("cdda_identify_partition: track %ld at %lld is "
"data\n", i + 1, trackLBA));
status = B_BAD_TYPE;
} else {
TRACE(("cdda_identify_partition: track %ld at %lld is "
"audio\n", i + 1, trackLBA));
status = B_OK;
break;
}
}
}
}
if (status != B_OK) {
free(toc);
return status;
}
*_cookie = toc;
return 0.8f;
}
static status_t
cdda_scan_partition(int fd, partition_data* partition, void* _cookie)
{
scsi_toc_toc* toc = (scsi_toc_toc*)_cookie;
partition->status = B_PARTITION_VALID;
partition->flags |= B_PARTITION_FILE_SYSTEM;
// compute length
uint32 lastTrack = toc->last_track + 1 - toc->first_track;
scsi_cd_msf& end = toc->tracks[lastTrack].start.time;
partition->content_size = off_t(end.minute * kFramesPerMinute
+ end.second * kFramesPerSecond + end.frame) * kFrameSize;
partition->block_size = kFrameSize;
// determine volume title
char name[256];
Volume::DetermineName(compute_cddb_disc_id(*toc), fd, name, sizeof(name));
partition->content_name = strdup(name);
if (partition->content_name == NULL)
return B_NO_MEMORY;
return B_OK;
}
static void
cdda_free_identify_partition_cookie(partition_data* partition, void* _cookie)
{
free(_cookie);
}
static status_t
cdda_mount(fs_volume* fsVolume, const char* device, uint32 flags,
const char* args, ino_t* _rootVnodeID)
{
TRACE(("cdda_mount: entry\n"));
Volume* volume = new(std::nothrow) Volume(fsVolume);
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();
fsVolume->private_volume = volume;
fsVolume->ops = &gCDDAVolumeOps;
return B_OK;
}
static status_t
cdda_unmount(fs_volume* _volume)
{
Volume* volume = (Volume*)_volume->private_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->private_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->private_volume;
Locker locker(volume->Lock());
status_t status = B_BAD_VALUE;
if ((mask & FS_WRITE_FSINFO_NAME) != 0) {
status = volume->SetName(info->volume_name);
}
return status;
}
static status_t
cdda_sync(fs_volume* _volume)
{
TRACE(("cdda_sync: entry\n"));
return B_OK;
}
static status_t
cdda_lookup(fs_volume* _volume, fs_vnode* _dir, const char* name, ino_t* _id)
{
Volume* volume = (Volume*)_volume->private_volume;
status_t status;
TRACE(("cdda_lookup: entry dir %p, name '%s'\n", _dir, name));
Inode* directory = (Inode*)_dir->private_node;
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;
status = get_vnode(volume->FSVolume(), inode->ID(), NULL);
if (status < B_OK)
return status;
*_id = inode->ID();
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->private_volume;
Inode* inode = (Inode*)_node->private_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, ino_t id, fs_vnode* _node, int* _type,
uint32* _flags, bool reenter)
{
Volume* volume = (Volume*)_volume->private_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;
_node->private_node = inode;
_node->ops = &gCDDAVnodeOps;
*_type = inode->Type();
*_flags = 0;
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, void** _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->buffer = NULL;
*_cookie = (void*)cookie;
return B_OK;
}
static status_t
cdda_close(fs_volume* _volume, fs_vnode* _node, void* _cookie)
{
return B_OK;
}
static status_t
cdda_free_cookie(fs_volume* _volume, fs_vnode* _node, void* _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* _node)
{
return B_OK;
}
static status_t
cdda_read(fs_volume* _volume, fs_vnode* _node, void* _cookie, off_t offset,
void* buffer, size_t* _length)
{
file_cookie* cookie = (file_cookie*)_cookie;
Volume* volume = (Volume*)_volume->private_volume;
Inode* inode = (Inode*)_node->private_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 (offset < 0)
return B_BAD_VALUE;
off_t maxSize = inode->Size() + sizeof(wav_header);
if (offset >= maxSize) {
*_length = 0;
return B_OK;
}
if (cookie->buffer == NULL) {
// TODO: move that to open() to make sure reading can't fail for this reason?
cookie->buffer = malloc(volume->BufferSize());
if (cookie->buffer == NULL)
return B_NO_MEMORY;
cookie->buffer_offset = -1;
}
size_t length = *_length;
if (offset + (off_t)length > maxSize)
length = maxSize - offset;
status_t status = B_OK;
size_t bytesRead = 0;
if (offset < (off_t)sizeof(wav_header)) {
// read fake WAV header
size_t size = sizeof(wav_header) - offset;
size = min_c(size, length);
if (user_memcpy(buffer, (uint8*)inode->WAVHeader() + offset, size)
< B_OK)
return B_BAD_ADDRESS;
buffer = (void*)((uint8*)buffer + size);
length -= size;
bytesRead += size;
offset = 0;
} else
offset -= sizeof(wav_header);
if (length > 0) {
// read actual CD data
offset += inode->StartFrame() * kFrameSize;
status = read_cdda_data(volume->Device(),
inode->StartFrame() + inode->FrameCount(), offset, buffer, length,
cookie->buffer_offset, cookie->buffer, volume->BufferSize());
bytesRead += length;
}
if (status == B_OK)
*_length = bytesRead;
return status;
}
static bool
cdda_can_page(fs_volume* _volume, fs_vnode* _node, void* cookie)
{
return false;
}
static status_t
cdda_read_pages(fs_volume* _volume, fs_vnode* _node, void* cookie, off_t pos,
const iovec* vecs, size_t count, size_t* _numBytes)
{
return B_NOT_ALLOWED;
}
static status_t
cdda_write_pages(fs_volume* _volume, fs_vnode* _node, void* cookie, off_t pos,
const iovec* vecs, size_t count, size_t* _numBytes)
{
return B_NOT_ALLOWED;
}
static status_t
cdda_read_stat(fs_volume* _volume, fs_vnode* _node, struct stat* stat)
{
Volume* volume = (Volume*)_volume->private_volume;
Inode* inode = (Inode*)_node->private_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, fs_vnode* _oldDir, const char* oldName,
fs_vnode* _newDir, const char* newName)
{
if (_oldDir->private_node != _newDir->private_node)
return B_BAD_VALUE;
// we only have a single directory which simplifies things a bit :-)
Volume *volume = (Volume*)_volume->private_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;
status_t status = inode->SetName(newName);
if (status == B_OK) {
// One of the tracks had its name edited from outside the filesystem
// add-on. Disable CDDB lookups. Note this will usually mean that the
// user manually renamed a track or that cddblinkd (or other program)
// did this so we do not want to do it again.
volume->SetCDDBLookupsEnabled(false);
notify_entry_moved(volume->ID(), volume->RootNode().ID(), oldName,
volume->RootNode().ID(), newName, inode->ID());
}
return status;
}
// #pragma mark - directory functions
static status_t
cdda_open_dir(fs_volume* _volume, fs_vnode* _node, void** _cookie)
{
Volume* volume = (Volume*)_volume->private_volume;
TRACE(("cdda_open_dir(): vnode = %p\n", _node));
Inode* inode = (Inode*)_node->private_node;
if (!S_ISDIR(inode->Type()))
return B_NOT_A_DIRECTORY;
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, void* _cookie,
struct dirent* buffer, size_t bufferSize, uint32* _num)
{
Volume* volume = (Volume*)_volume->private_volume;
Inode* inode = (Inode*)_node->private_node;
TRACE(("cdda_read_dir: vnode %p, cookie %p, buffer = %p, bufferSize = %ld,"
" num = %p\n", _node, _cookie, buffer, bufferSize,_num));
if ((Inode*)_node->private_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;
uint32 max = *_num;
uint32 count = 0;
while (count < max && bufferSize > sizeof(dirent)) {
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 == NULL) {
// we're at the end of the directory
break;
}
buffer->d_dev = volume->FSVolume()->id;
buffer->d_ino = childNode->ID();
buffer->d_reclen = strlen(name) + sizeof(struct dirent);
if (buffer->d_reclen > bufferSize) {
if (count == 0)
return ENOBUFS;
break;
}
strcpy(buffer->d_name, name);
bufferSize -= buffer->d_reclen;
buffer = (struct dirent*)((uint8*)buffer + buffer->d_reclen);
count++;
cookie->current = nextChildNode;
cookie->state = nextState;
}
*_num = count;
return B_OK;
}
static status_t
cdda_rewind_dir(fs_volume* _volume, fs_vnode* _node, void* _cookie)
{
Volume* volume = (Volume*)_volume->private_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, void* _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* _node, void* _cookie)
{
dir_cookie* cookie = (dir_cookie*)_cookie;
TRACE(("cdda_freecookie: entry vnode %p, cookie %p\n", _node, cookie));
free(cookie);
return 0;
}
// #pragma mark - attribute functions
static status_t
cdda_open_attr_dir(fs_volume* _volume, fs_vnode* _node, void** _cookie)
{
Volume* volume = (Volume*)_volume->private_volume;
Inode* inode = (Inode*)_node->private_node;
attr_cookie* cookie = new(std::nothrow) 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* _node, void* _cookie)
{
return B_OK;
}
static status_t
cdda_free_attr_dir_cookie(fs_volume* _volume, fs_vnode* _node, void* _cookie)
{
Volume* volume = (Volume*)_volume->private_volume;
Inode* inode = (Inode*)_node->private_node;
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* _node, void* _cookie)
{
Volume* volume = (Volume*)_volume->private_volume;
Inode* inode = (Inode*)_node->private_node;
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* _node, void* _cookie,
struct dirent* dirent, size_t bufferSize, uint32* _num)
{
Volume* volume = (Volume*)_volume->private_volume;
Inode* inode = (Inode*)_node->private_node;
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->FSVolume()->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, void** _cookie)
{
Volume *volume = (Volume*)_volume->private_volume;
Inode *inode = (Inode*)_node->private_node;
Locker _(volume->Lock());
Attribute* attribute = inode->FindAttribute(name);
if (attribute == NULL) {
if (Attribute::IsProtectedNamespace(name))
return B_NOT_ALLOWED;
status_t status = inode->AddAttribute(name, type, true, NULL, 0);
if (status != B_OK)
return status;
notify_attribute_changed(volume->ID(), -1, inode->ID(), name,
B_ATTR_CREATED);
} else if ((openMode & O_EXCL) == 0) {
if (attribute->IsProtectedNamespace())
return B_NOT_ALLOWED;
attribute->SetType(type);
if ((openMode & O_TRUNC) != 0)
attribute->Truncate();
} 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, void** _cookie)
{
Volume* volume = (Volume*)_volume->private_volume;
Inode* inode = (Inode*)_node->private_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* _volume, fs_vnode* _node, void* cookie)
{
return B_OK;
}
static status_t
cdda_free_attr_cookie(fs_volume* _volume, fs_vnode* _node, void* cookie)
{
free(cookie);
return B_OK;
}
static status_t
cdda_read_attr(fs_volume* _volume, fs_vnode* _node, void* _cookie,
off_t offset, void* buffer, size_t* _length)
{
Volume* volume = (Volume*)_volume->private_volume;
Inode* inode = (Inode*)_node->private_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* _node, void* _cookie,
off_t offset, const void* buffer, size_t* _length)
{
Volume* volume = (Volume*)_volume->private_volume;
Inode* inode = (Inode*)_node->private_node;
Locker _(volume->Lock());
Attribute* attribute = inode->FindAttribute((const char*)_cookie);
if (attribute == NULL)
return B_ENTRY_NOT_FOUND;
if (attribute->IsProtectedNamespace())
return B_NOT_ALLOWED;
status_t status = attribute->WriteAt(offset, (uint8*)buffer, _length);
if (status == B_OK) {
notify_attribute_changed(volume->ID(), -1, inode->ID(),
attribute->Name(), B_ATTR_CHANGED);
}
return status;
}
static status_t
cdda_read_attr_stat(fs_volume* _volume, fs_vnode* _node, void* _cookie,
struct stat* stat)
{
Volume* volume = (Volume*)_volume->private_volume;
Inode* inode = (Inode*)_node->private_node;
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* _node, void* 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->private_volume;
Inode* inode = (Inode*)_node->private_node;
Locker _(volume->Lock());
status_t status = inode->RemoveAttribute(name, true);
if (status == B_OK) {
notify_attribute_changed(volume->ID(), -1, inode->ID(), name,
B_ATTR_REMOVED);
}
return status;
}
fs_volume_ops gCDDAVolumeOps = {
cdda_unmount,
cdda_read_fs_stat,
cdda_write_fs_stat,
cdda_sync,
cdda_get_vnode,
// the other operations are not yet supported (indices, queries)
NULL,
};
fs_vnode_ops gCDDAVnodeOps = {
cdda_lookup,
cdda_get_vnode_name,
cdda_put_vnode,
NULL, // fs_remove_vnode()
cdda_can_page,
cdda_read_pages,
cdda_write_pages,
NULL, // io()
NULL, // cancel_io()
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()
NULL, // fs_preallocate()
// 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,
NULL, // fs_create_special_node()
};
static file_system_module_info sCDDAFileSystem = {
{
"file_systems/cdda" B_CURRENT_FS_API_VERSION,
0,
NULL,
},
"cdda", // short_name
"CDDA File System", // pretty_name
0, // DDM flags
cdda_identify_partition,
cdda_scan_partition,
cdda_free_identify_partition_cookie,
NULL, // free_partition_content_cookie()
cdda_mount,
// all other functions are not supported
NULL,
};
module_info* modules[] = {
(module_info*)&sCDDAFileSystem,
NULL,
};
↑ V1028 Possible overflow. Consider casting operands, not the result.
↑ V641 The size of the allocated memory buffer is not a multiple of the element size.
↑ V641 The size of the allocated memory buffer is not a multiple of the element size.
↑ V730 Not all members of a class are initialized inside the constructor. Consider inspecting: fDiscID.