/* * Copyright 2007-2016, Axel Dörfler, axeld@pinc-software.de. * Distributed under the terms of the MIT License. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cdda.h" #include "cddb.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 AttributeList; typedef SinglyLinkedList 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); mutex& 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(); mutex 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 { 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 { 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) : fFSVolume(fsVolume), fDevice(-1), fRootNode(NULL), fNextID(1), fName(NULL), fNumBlocks(0), fIgnoreCDDBLookupChanges(false), fFirstEntry(NULL) { mutex_init(&fLock, "cdda"); } 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); mutex_destroy(&fLock); } status_t Volume::InitCheck() { 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; } mutex& 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(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 -1; 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 -1; } *_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; MutexLocker 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; MutexLocker 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; MutexLocker _(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)); MutexLocker _(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 %lld, 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; MutexLocker _(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; MutexLocker _(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 = offsetof(struct dirent, d_name) + strlen(name) + 1; 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; MutexLocker _(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; MutexLocker _(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; MutexLocker _(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; MutexLocker _(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 = offsetof(struct dirent, d_name) + length + 1; 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; MutexLocker _(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; MutexLocker _(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; MutexLocker _(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; MutexLocker _(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; MutexLocker _(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; MutexLocker _(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, };