diff --git a/src/add-ons/kernel/file_systems/Jamfile b/src/add-ons/kernel/file_systems/Jamfile index c119742cb2..c41218629b 100644 --- a/src/add-ons/kernel/file_systems/Jamfile +++ b/src/add-ons/kernel/file_systems/Jamfile @@ -2,6 +2,7 @@ SubDir HAIKU_TOP src add-ons kernel file_systems ; SubInclude HAIKU_TOP src add-ons kernel file_systems dos ; SubInclude HAIKU_TOP src add-ons kernel file_systems bfs ; +SubInclude HAIKU_TOP src add-ons kernel file_systems cdda ; SubInclude HAIKU_TOP src add-ons kernel file_systems googlefs ; SubInclude HAIKU_TOP src add-ons kernel file_systems iso9660 ; SubInclude HAIKU_TOP src add-ons kernel file_systems nfs ; diff --git a/src/add-ons/kernel/file_systems/cdda/Jamfile b/src/add-ons/kernel/file_systems/cdda/Jamfile new file mode 100644 index 0000000000..a938338c90 --- /dev/null +++ b/src/add-ons/kernel/file_systems/cdda/Jamfile @@ -0,0 +1,13 @@ +SubDir HAIKU_TOP src add-ons kernel file_systems cdda ; + +UsePrivateHeaders [ FDirName kernel ] ; +UsePrivateHeaders [ FDirName kernel disk_device_manager ] ; +UsePrivateHeaders [ FDirName storage ] ; + +KernelAddon cdda : + kernel_interface.cpp + ; + +SimpleTest cdda_text : + cdda.cpp + : be ; diff --git a/src/add-ons/kernel/file_systems/cdda/Lock.h b/src/add-ons/kernel/file_systems/cdda/Lock.h new file mode 100644 index 0000000000..e6db2727c5 --- /dev/null +++ b/src/add-ons/kernel/file_systems/cdda/Lock.h @@ -0,0 +1,96 @@ +/* + * Copyright 2001-2007, Axel Dörfler, axeld@pinc-software.de. + * This file may be used under the terms of the MIT License. + */ +#ifndef LOCK_H +#define LOCK_H + + +#include + + +#define USE_BENAPHORE + // if defined, benaphores are used for the Semaphore class + +class Semaphore { + public: + Semaphore(const char *name) + : +#ifdef USE_BENAPHORE + fSemaphore(create_sem(0, name)), + fCount(1) +#else + fSemaphore(create_sem(1, name)) +#endif + { + } + + ~Semaphore() + { + delete_sem(fSemaphore); + } + + status_t InitCheck() + { + if (fSemaphore < B_OK) + return fSemaphore; + + return B_OK; + } + + status_t Lock() + { +#ifdef USE_BENAPHORE + if (atomic_add(&fCount, -1) <= 0) +#endif + return acquire_sem(fSemaphore); +#ifdef USE_BENAPHORE + return B_OK; +#endif + } + + status_t Unlock() + { +#ifdef USE_BENAPHORE + if (atomic_add(&fCount, 1) < 0) +#endif + return release_sem(fSemaphore); +#ifdef USE_BENAPHORE + return B_OK; +#endif + } + + private: + sem_id fSemaphore; +#ifdef USE_BENAPHORE + vint32 fCount; +#endif +}; + +// a convenience class to lock a Semaphore object + +class Locker { + public: + Locker(Semaphore &lock) + : fLock(lock) + { + fStatus = lock.Lock(); + } + + ~Locker() + { + if (fStatus == B_OK) + fLock.Unlock(); + } + + status_t Status() const + { + return fStatus; + } + + private: + Semaphore &fLock; + status_t fStatus; +}; + +#endif /* LOCK_H */ diff --git a/src/add-ons/kernel/file_systems/cdda/cdda.cpp b/src/add-ons/kernel/file_systems/cdda/cdda.cpp new file mode 100644 index 0000000000..0ca8776fc6 --- /dev/null +++ b/src/add-ons/kernel/file_systems/cdda/cdda.cpp @@ -0,0 +1,532 @@ +/* + * Copyright 2007, Axel Dörfler, axeld@pinc-software.de. + * Distributed under the terms of the MIT License. + */ + + +#include +#include + +#include +#include +#include +#include +#include + +#define kFramesPerSecond 75 +#define kFramesPerMinute (kFramesPerSecond * 60) + +struct cdtext_pack_data { + uint8 id; + uint8 track; + uint8 number; + uint8 character_position : 4; + uint8 block_number : 3; + uint8 double_byte : 1; + char text[12]; + uint8 crc[2]; +} _PACKED; + + +static const uint8 kMaxTracks = 0x63; +enum { + kTrackID = 0x80, + kArtistID = 0x81, + kMessageID = 0x85, +}; + +struct cdtext { + char *artist; + char *album; + char *titles[kMaxTracks]; + char *artists[kMaxTracks]; + uint8 track_count; + char *genre; +}; + + +// #pragma mark - string functions + + +bool +is_garbage(char c) +{ + return isspace(c) || c == '-' || c == '/' || c == '\\'; +} + + +void +sanitize_string(char *string) +{ + if (string == NULL) + return; + + // strip garbage at the start + + uint32 length = strlen(string); + uint32 garbage = 0; + while (is_garbage(string[garbage])) { + garbage++; + } + + length -= garbage; + if (garbage) + memmove(string, string + garbage, length + 1); + + // strip garbage from the end + + while (length > 1 && isspace(string[length - 1])) { + string[--length] = '\0'; + } +} + + +//! Finds the first occurrence of \a find in \a string, ignores case. +static char* +find_string(const char *string, const char *find) +{ + if (string == NULL || find == NULL) + return NULL; + + char first = tolower(find[0]); + if (first == '\0') + return (char *)string; + + int32 findLength = strlen(find) - 1; + find++; + + for (; string[0]; string++) { + if (tolower(string[0]) != first) + continue; + if (strncasecmp(string + 1, find, findLength) == 0) + return (char *)string; + } + + return NULL; +} + + +void +cut_string(char *string, char *cut) +{ + if (string == NULL || cut == NULL) + return; + + char *found = find_string(string, cut); + if (found != NULL) { + uint32 foundLength = strlen(found); + uint32 cutLength = strlen(cut); + memmove(found, found + cutLength, foundLength + 1 - cutLength); + } +} + + +void +sanitize_album(cdtext &text) +{ + cut_string(text.album, text.artist); + sanitize_string(text.album); + + if ((text.artist == NULL || !text.artist[0]) && text.album != NULL) { + // try to extract artist from album + char *space = strstr(text.album, " "); + if (space != NULL) { + space[0] = '\0'; + text.artist = text.album; + text.album = strdup(space + 2); + + sanitize_string(text.artist); + sanitize_string(text.album); + } + } +} + + +void +sanitize_titles(cdtext &text) +{ + for (uint8 i = 0; i < text.track_count; i++) { + cut_string(text.titles[i], "(Album Version)"); + sanitize_string(text.titles[i]); + sanitize_string(text.artists[i]); + if (!strcasecmp(text.artists[i], text.artist)) { + // if the title artist is the same as the main artist, remove it + free(text.artists[i]); + text.artists[i] = NULL; + } + + if (text.titles[i] != NULL && text.titles[i][0] == '\t' && i > 0) + text.titles[i] = strdup(text.titles[i - 1]); + } +} + + +bool +single_case(const char *string, bool &upper, bool &first) +{ + if (string == NULL) + return true; + + while (string[0]) { + while (!isalpha(string[0])) { + string++; + } + + if (first) { + upper = isupper(string[0]) != 0; + first = false; + } else if ((isupper(string[0]) != 0) ^ upper) + return false; + + string++; + } + + return true; +} + + +void +capitalize_string(char *string) +{ + if (string == NULL) + return; + + bool newWord = isalpha(string[0]) || isspace(string[0]); + while (string[0]) { + if (isalpha(string[0])) { + if (newWord) { + string[0] = toupper(string[0]); + newWord = false; + } else + string[0] = tolower(string[0]); + } else if (string[0] != '\'') + newWord = true; + + string++; + } +} + + +void +correct_case(cdtext &text) +{ + // check if all titles share a single case + bool first = true; + bool upper; + if (!single_case(text.album, upper, first) + || !single_case(text.artist, upper, first)) + return; + + for (int32 i = 0; i < text.track_count; i++) { + if (!single_case(text.titles[i], upper, first) + || !single_case(text.artists[i], upper, first)) + return; + } + + // If we get here, everything has a single case; we fix that + // and capitalize each word + + capitalize_string(text.album); + capitalize_string(text.artist); + for (int32 i = 0; i < text.track_count; i++) { + capitalize_string(text.titles[i]); + capitalize_string(text.artists[i]); + } +} + + +// #pragma mark - CD-Text + + +bool +is_string_id(uint8 id) +{ + return id >= kTrackID && id <= kMessageID; +} + + +bool +parse_pack_data(cdtext_pack_data *&pack, uint32 &packLeft, + cdtext_pack_data *&lastPack, uint8 &id, uint8 &track, uint8 &state, + char *buffer, size_t &length) +{ + if (packLeft < sizeof(cdtext_pack_data)) + return false; + + uint8 number = pack->number; + size_t size = length; + + if (state != 0) { + // we had a terminated string and a missing track + track++; + memcpy(buffer, lastPack->text + state, 12 - state); + if (pack->track - track == 1) + state = 0; + else + state += strnlen(buffer, 12 - state); + return true; + } + + id = pack->id; + track = pack->track; + buffer[0] = '\0'; + length = 0; + + size_t position = pack->character_position; + if (position > 0 && lastPack != NULL) { + memcpy(buffer, &lastPack->text[12 - position], position); + length = position; + } + + while (id == pack->id && track == pack->track) { +#if 1 + printf("%u.%u.%u, %u.%u.%u, ", pack->id, pack->track, pack->number, + pack->double_byte, pack->block_number, pack->character_position); + for (int32 i = 0; i < 12; i++) { + if (isprint(pack->text[i])) + putchar(pack->text[i]); + } + putchar('\n'); +#endif + if (is_string_id(id)) { + // TODO: support double byte characters + if (length + 12 < size) { + memcpy(buffer + length, pack->text, 12); + length += 12; + } + } + + packLeft -= sizeof(cdtext_pack_data); + if (packLeft < sizeof(cdtext_pack_data)) + return false; + + lastPack = pack; + number++; + pack++; + + if (pack->number != number) + return false; + } + + if (id == pack->id) { + length -= pack->character_position; + if (length >= size) + length = size - 1; + buffer[length] = '\0'; + + if (pack->track > lastPack->track + 1) { + // there is a missing track + for (int32 i = 0; i < 12; i++) { + if (lastPack->text[i] == '\0') { + state = i + (lastPack->double_byte ? 2 : 1); + break; + } + } + } + } + + // TODO: convert text to UTF-8 + return true; +} + + +void +dump_cdtext(cdtext_pack_data *pack, size_t packLength) +{ + cdtext_pack_data *lastPack = NULL; + char buffer[256]; + uint8 state = 0; + cdtext text; + + memset(&text, 0, sizeof(cdtext)); + + while (true) { + size_t length = sizeof(buffer); + uint8 id, track; + + if (!parse_pack_data(pack, packLength, lastPack, id, track, + state, buffer, length)) + break; + + switch (id) { + case kTrackID: + if (track == 0) { + if (text.album == NULL) + text.album = strdup(buffer); + } else if (track <= kMaxTracks) { + if (text.titles[track - 1] == NULL) + text.titles[track - 1] = strdup(buffer); + if (track > text.track_count) + text.track_count = track; + } + break; + + case kArtistID: + if (track == 0) { + if (text.artist == NULL) + text.artist = strdup(buffer); + } else if (track <= kMaxTracks) { + if (text.artists[track - 1] == NULL) + text.artists[track - 1] = strdup(buffer); + } + break; + + default: + if (is_string_id(id)) + printf("UNKNOWN %u: \"%s\"\n", id, buffer); + break; + } + } + + sanitize_string(text.artist); + sanitize_album(text); + sanitize_titles(text); + correct_case(text); + + if (text.album) + printf("Album: \"%s\"\n", text.album); + if (text.artist) + printf("Artist: \"%s\"\n", text.artist); + for (uint8 i = 0; i < text.track_count; i++) { + printf("Track %02u: \"%s\"%s%s%s\n", i + 1, text.titles[i], + text.artists[i] ? " (" : "", text.artists[i] ? text.artists[i] : "", + text.artists[i] ? ")" : ""); + } +} + + +void +dump_toc(scsi_toc_toc *toc) +{ + int32 numTracks = toc->last_track + 1 - toc->first_track; + + for (int32 i = 0; i < numTracks; i++) { + scsi_toc_track& track = toc->tracks[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; + scsi_cd_msf length; + + uint64 diff = next.minute * kFramesPerMinute + + next.second * kFramesPerSecond + next.frame + - start.minute * kFramesPerMinute + - start.second * kFramesPerSecond - start.frame; + length.minute = diff / kFramesPerMinute; + length.second = (diff % kFramesPerMinute) / kFramesPerSecond; + length.frame = diff % kFramesPerSecond; + + printf("%02u. %02u:%02u.%02u (length %02u:%02u.%02u)\n", + track.track_number, start.minute, start.second, start.frame, + length.minute, length.second, length.frame); + } +} + + +status_t +read_table_of_contents(int fd, uint32 track, uint8 format, uint8 *buffer, + size_t bufferSize) +{ + raw_device_command raw; + uint8 senseData[1024]; + + memset(&raw, 0, sizeof(raw_device_command)); + memset(senseData, 0, sizeof(senseData)); + memset(buffer, 0, bufferSize); + + scsi_cmd_read_toc &toc = *(scsi_cmd_read_toc*)&raw.command; + toc.opcode = SCSI_OP_READ_TOC; + toc.time = 1; + toc.format = format; + toc.track = track; + toc.allocation_length = B_HOST_TO_BENDIAN_INT16(bufferSize); + + raw.command_length = 10; + raw.flags = B_RAW_DEVICE_DATA_IN | B_RAW_DEVICE_REPORT_RESIDUAL + | B_RAW_DEVICE_SHORT_READ_VALID; + raw.scsi_status = 0; + raw.cam_status = 0; + raw.data = buffer; + raw.data_length = bufferSize; + raw.timeout = 10000000LL; // 10 secs + raw.sense_data = senseData; + raw.sense_data_length = sizeof(senseData); + + if (ioctl(fd, B_RAW_DEVICE_COMMAND, &raw) == 0) { + if (raw.scsi_status == 0 && raw.cam_status == 1) { + puts("success!\n"); + } else { + puts("failure!\n"); + } + } + + return 0; +} + + +void +dump_block(const uint8 *buffer, int size, const char *prefix) +{ + int i; + + for (i = 0; i < size;) + { + int start = i; + + printf(prefix); + for (; i < start+16; i++) + { + if (!(i % 4)) + printf(" "); + + if (i >= size) + printf(" "); + else + printf("%02x", *(unsigned char *)(buffer + i)); + } + printf(" "); + + for (i = start; i < start + 16; i++) + { + if (i < size) + { + char c = buffer[i]; + + if (c < 30) + printf("."); + else + printf("%c", c); + } + else + break; + } + printf("\n"); + } +} + + +int +main(int argc, char** argv) +{ + int fd = open(argv[1], O_RDONLY); + if (fd < 0) + return -1; + + uint8 buffer[1024]; + scsi_toc_general *header = (scsi_toc_general *)buffer; + + read_table_of_contents(fd, 1, SCSI_TOC_FORMAT_TOC, buffer, sizeof(buffer)); + header->data_length = B_BENDIAN_TO_HOST_INT16(header->data_length); + printf("TOC header %u, %d, %d\n", header->data_length, header->first, header->last); + //dump_block(buffer, header->data_length + 2, "TOC"); + dump_toc((scsi_toc_toc *)buffer); + + read_table_of_contents(fd, 1, SCSI_TOC_FORMAT_CD_TEXT, buffer, sizeof(buffer)); + read_table_of_contents(fd, 1, SCSI_TOC_FORMAT_CD_TEXT, buffer, sizeof(buffer)); + header->data_length = B_BENDIAN_TO_HOST_INT16(header->data_length); + printf("TEXT header %u, %d, %d\n", header->data_length, header->first, header->last); + //dump_block(buffer, header->data_length + 2, "TEXT"); + + dump_cdtext((cdtext_pack_data *)(buffer + 4), header->data_length - 2); + + close(fd); +} diff --git a/src/add-ons/kernel/file_systems/cdda/kernel_interface.cpp b/src/add-ons/kernel/file_systems/cdda/kernel_interface.cpp new file mode 100644 index 0000000000..198b69152f --- /dev/null +++ b/src/add-ons/kernel/file_systems/cdda/kernel_interface.cpp @@ -0,0 +1,826 @@ +/* + * Copyright 2007, Axel Dörfler, axeld@pinc-software.de. + * Distributed under the terms of the MIT License. + */ + + +#include "Lock.h" + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + + +//#define TRACE_CDDA +#ifdef TRACE_CDDA +# define TRACE(x) dprintf x +#else +# define TRACE(x) +#endif + + +class Volume; +class Inode; +struct dir_cookie; + +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, 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 Inode { + public: + Inode(Volume *volume, Inode *parent, const char *name, 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 Size() const + { return fSize; } + + Inode *Next() const { return fNext; } + void SetNext(Inode *inode) { fNext = inode; } + + private: + Inode *fNext; + vnode_id fID; + int32 fType; + const char *fName; + gid_t fGroupID; + uid_t fUserID; + time_t fCreationTime; + time_t fModificationTime; + off_t fSize; +}; + + +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 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, "", 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; + } +} + + +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; + + return B_OK; +} + + +Semaphore& +Volume::Lock() +{ + return fLock; +} + + +Inode * +Volume::_CreateNode(Inode *parent, const char *name, int32 type) +{ + Inode *inode = new Inode(this, parent, name, 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) +{ + return B_ERROR; +} + + +// #pragma mark - + + +Inode::Inode(Volume *volume, Inode *parent, const char *name, int32 type) + : + fNext(NULL) +{ + fName = strdup(name); + if (fName == NULL) + return; + + fID = volume->GetNextNodeID(); + fType = type; + + fUserID = geteuid(); + fGroupID = parent ? parent->GroupID() : getegid(); + + fCreationTime = fModificationTime = time(NULL); +} + + +Inode::~Inode() +{ + free(const_cast(fName)); +} + + +status_t +Inode::InitCheck() +{ + if (fName == NULL) + return B_NO_MEMORY; + + return B_OK; +} + + +status_t +Inode::SetName(const char* name) +{ + return B_ERROR; +} + + +// #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)); + + stat->st_dev = volume->ID(); + stat->st_ino = inode->ID(); + + stat->st_size = inode->Size(); + stat->st_mode = inode->Type(); + + 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(); + + 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; + + return B_ERROR; +} + + +// #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; +} + + +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() + NULL, // fs_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, + +#if 0 + // 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, + &cdda_rename_attr, + &cdda_remove_attr, +#endif + + // the other operations are not yet supported (indices, queries) + NULL, +}; + +module_info *modules[] = { + (module_info *)&sCDDAFileSystem, + NULL, +};