diff --git a/src/add-ons/kernel/file_systems/cdda/Jamfile b/src/add-ons/kernel/file_systems/cdda/Jamfile index 6eb8fc843b..cb5d7d2d2e 100644 --- a/src/add-ons/kernel/file_systems/cdda/Jamfile +++ b/src/add-ons/kernel/file_systems/cdda/Jamfile @@ -7,4 +7,5 @@ UsePrivateHeaders [ FDirName storage ] ; KernelAddon cdda : kernel_interface.cpp cdda.cpp + cddb.cpp ; diff --git a/src/add-ons/kernel/file_systems/cdda/cdda.cpp b/src/add-ons/kernel/file_systems/cdda/cdda.cpp index 5d50ca55b9..24f8863208 100644 --- a/src/add-ons/kernel/file_systems/cdda/cdda.cpp +++ b/src/add-ons/kernel/file_systems/cdda/cdda.cpp @@ -56,7 +56,7 @@ is_garbage(char c) static void -sanitize_string(char *string) +sanitize_string(char *&string) { if (string == NULL) return; @@ -78,6 +78,12 @@ sanitize_string(char *string) while (length > 1 && isspace(string[length - 1])) { string[--length] = '\0'; } + + if (!string[0]) { + // free string if it's empty + free(string); + string = NULL; + } } @@ -127,7 +133,13 @@ sanitize_album(cdtext &text) cut_string(text.album, text.artist); sanitize_string(text.album); - if ((text.artist == NULL || !text.artist[0]) && text.album != NULL) { + if (text.album != NULL && !strcasecmp(text.album, "My CD")) { + // don't laugh, people really do that! + free(text.album); + text.album = NULL; + } + + if ((text.artist == NULL || text.artist[0] == NULL) && text.album != NULL) { // try to extract artist from album char *space = strstr(text.album, " "); if (space != NULL) { diff --git a/src/add-ons/kernel/file_systems/cdda/cddb.cpp b/src/add-ons/kernel/file_systems/cdda/cddb.cpp new file mode 100644 index 0000000000..76cf40b6c6 --- /dev/null +++ b/src/add-ons/kernel/file_systems/cdda/cddb.cpp @@ -0,0 +1,47 @@ +/* + * Copyright 2007, Axel Dörfler, axeld@pinc-software.de. + * Distributed under the terms of the MIT License. + */ + + +#include "cddb.h" + + +static int32 +cddb_sum(int32 n) +{ + int32 result = 0; + + while (n > 0) { + result = result + (n % 10); + n = n / 10; + } + + return result; +} + + +// #pragma mark - exported functions + + +/*! + Computes the CD Disc ID as specified in the FreeDB how-to (code taken from there). +*/ +uint32 +compute_cddb_disc_id(scsi_toc_toc &toc) +{ + int32 numTracks = toc.last_track + 1 - toc.first_track; + uint32 n = 0; + + for (int32 i = 0; i < numTracks; i++) { + n = n + cddb_sum((toc.tracks[i].start.time.minute * 60) + + toc.tracks[i].start.time.second); + } + + int32 t = ((toc.tracks[numTracks].start.time.minute * 60) + + toc.tracks[numTracks].start.time.second) + - ((toc.tracks[0].start.time.minute * 60) + + toc.tracks[0].start.time.second); + + return (n % 0xff) << 24 | t << 8 | numTracks; +} diff --git a/src/add-ons/kernel/file_systems/cdda/cddb.h b/src/add-ons/kernel/file_systems/cdda/cddb.h new file mode 100644 index 0000000000..49a4b5a274 --- /dev/null +++ b/src/add-ons/kernel/file_systems/cdda/cddb.h @@ -0,0 +1,14 @@ +/* + * Copyright 2007, Axel Dörfler, axeld@pinc-software.de. + * Distributed under the terms of the MIT License. + */ +#ifndef CDDB_H +#define CDDB_H + + +#include + + +uint32 compute_cddb_disc_id(scsi_toc_toc &toc); + +#endif // CDDB_H diff --git a/src/add-ons/kernel/file_systems/cdda/kernel_interface.cpp b/src/add-ons/kernel/file_systems/cdda/kernel_interface.cpp index 9d70fc5531..4521d3255c 100644 --- a/src/add-ons/kernel/file_systems/cdda/kernel_interface.cpp +++ b/src/add-ons/kernel/file_systems/cdda/kernel_interface.cpp @@ -10,8 +10,11 @@ #include #include #include +#include +#include #include +#include #include #include @@ -29,10 +32,14 @@ #endif -class Volume; +class Attribute; class Inode; +struct attr_cookie; struct dir_cookie; +typedef DoublyLinkedList AttributeList; +typedef DoublyLinkedList AttrCookieList; + class Volume { public: Volume(mount_id id); @@ -73,6 +80,29 @@ class Volume { Inode *fFirstEntry; }; +class Attribute : public DoublyLinkedListLinkImpl { + 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: @@ -103,6 +133,18 @@ class Inode { 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; } @@ -117,12 +159,13 @@ class Inode { time_t fModificationTime; off_t fStartFrame; off_t fFrameCount; + AttributeList fAttributes; + AttrCookieList fAttrCookies; }; - struct dir_cookie { - Inode *current; - int state; // iteration state + Inode *current; + int state; // iteration state }; // directory iteration states @@ -133,8 +176,12 @@ enum { ITERATION_STATE_BEGIN = ITERATION_STATE_DOT, }; +struct attr_cookie : DoublyLinkedListLinkImpl { + Attribute *current; +}; + struct file_cookie { - int open_mode; + int open_mode; }; @@ -212,6 +259,7 @@ Volume::Mount(const char* device) 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; @@ -219,18 +267,43 @@ Volume::Mount(const char* device) + next.second * kFramesPerSecond + next.frame - startFrame; - if (text.titles[i] != NULL && text.titles[i]) { - if (text.artists[i] != NULL && text.artists[i]) { - snprintf(title, sizeof(title), "%02ld. %s - %s.wav", i + 1, + 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", i + 1, + snprintf(title, sizeof(title), "%02ld. %s.wav", track, text.titles[i]); } } else - snprintf(title, sizeof(title), "%02ld.wav", i + 1); + snprintf(title, sizeof(title), "%02ld.wav", track); - _CreateNode(fRootNode, title, startFrame, frames, S_IFREG | 0444); + // 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); @@ -330,6 +403,116 @@ Volume::SetName(const char *name) // #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) : @@ -383,6 +566,150 @@ Inode::SetName(const char* name) } +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 @@ -636,21 +963,7 @@ cdda_read_stat(fs_volume _volume, fs_vnode _node, struct stat *stat) 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(); + fill_stat_buffer(volume, inode, NULL, *stat); return B_OK; } @@ -813,6 +1126,228 @@ cdda_free_dir_cookie(fs_volume _volume, fs_vnode _vnode, fs_cookie _cookie) } +// #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, ...) { @@ -873,7 +1408,7 @@ static file_system_module_info sCDDAFileSystem = { NULL, // fs_symlink() NULL, // fs_link() NULL, // fs_unlink() - NULL, // fs_rename() + &cdda_rename, NULL, // fs_access() &cdda_read_stat, @@ -896,7 +1431,6 @@ static file_system_module_info sCDDAFileSystem = { &cdda_read_dir, &cdda_rewind_dir, -#if 0 // attribute directory operations &cdda_open_attr_dir, &cdda_close_attr_dir, @@ -914,9 +1448,8 @@ static file_system_module_info sCDDAFileSystem = { &cdda_read_attr_stat, &cdda_write_attr_stat, - &cdda_rename_attr, + NULL, // fs_rename_attr() &cdda_remove_attr, -#endif // the other operations are not yet supported (indices, queries) NULL, diff --git a/src/tests/add-ons/kernel/file_systems/cdda/Jamfile b/src/tests/add-ons/kernel/file_systems/cdda/Jamfile index 1c9b0fb865..75ee89ac8d 100644 --- a/src/tests/add-ons/kernel/file_systems/cdda/Jamfile +++ b/src/tests/add-ons/kernel/file_systems/cdda/Jamfile @@ -6,4 +6,5 @@ SimpleTest cdda_text : cdda_text.cpp cdda.cpp + cddb.cpp ; diff --git a/src/tests/add-ons/kernel/file_systems/cdda/cdda_text.cpp b/src/tests/add-ons/kernel/file_systems/cdda/cdda_text.cpp index cd0aa7a8bc..02e82f741f 100644 --- a/src/tests/add-ons/kernel/file_systems/cdda/cdda_text.cpp +++ b/src/tests/add-ons/kernel/file_systems/cdda/cdda_text.cpp @@ -5,6 +5,7 @@ #include "cdda.h" +#include "cddb.h" #include #include @@ -36,7 +37,8 @@ main(int argc, char** argv) return -1; uint8 buffer[1024]; - if (read_table_of_contents(fd, (scsi_toc_toc*)buffer, sizeof(buffer)) < 0) { + scsi_toc_toc *toc = (scsi_toc_toc *)buffer; + if (read_table_of_contents(fd, toc, sizeof(buffer)) < 0) { fprintf(stderr, "%s: Retrieving TOC failed", __progname); return -1; } @@ -44,5 +46,8 @@ main(int argc, char** argv) cdtext text; read_cdtext(fd, text); + uint32 id = compute_cddb_disc_id(*toc); + printf("CDDB disc ID: %lx\n", id); + close(fd); }