xref: /haiku/src/add-ons/kernel/file_systems/cdda/kernel_interface.cpp (revision 1294543de9ac0eff000eaea1b18368c36435d08e)
1 /*
2  * Copyright 2007-2010, Axel Dörfler, axeld@pinc-software.de.
3  * Distributed under the terms of the MIT License.
4  */
5 
6 
7 #include "cdda.h"
8 #include "cddb.h"
9 #include "Lock.h"
10 
11 #include <FindDirectory.h>
12 #include <fs_info.h>
13 #include <fs_interface.h>
14 #include <KernelExport.h>
15 #include <Mime.h>
16 #include <NodeMonitor.h>
17 #include <TypeConstants.h>
18 
19 #include <util/kernel_cpp.h>
20 #include <util/DoublyLinkedList.h>
21 
22 #include <dirent.h>
23 #include <errno.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <sys/stat.h>
28 
29 
30 //#define TRACE_CDDA
31 #ifdef TRACE_CDDA
32 #	define TRACE(x) dprintf x
33 #else
34 #	define TRACE(x)
35 #endif
36 
37 
38 class Attribute;
39 class Inode;
40 struct attr_cookie;
41 struct dir_cookie;
42 
43 typedef DoublyLinkedList<Attribute> AttributeList;
44 typedef DoublyLinkedList<attr_cookie> AttrCookieList;
45 
46 struct riff_header {
47 	uint32		magic;
48 	uint32		length;
49 	uint32		id;
50 } _PACKED;
51 
52 struct riff_chunk {
53 	uint32		fourcc;
54 	uint32		length;
55 } _PACEKD;
56 
57 struct wav_format_chunk : riff_chunk {
58 	uint16		format_tag;
59 	uint16		channels;
60 	uint32		samples_per_second;
61 	uint32		average_bytes_per_second;
62 	uint16		block_align;
63 	uint16		bits_per_sample;
64 } _PACKED;
65 
66 struct wav_header {
67 	riff_header			header;
68 	wav_format_chunk	format;
69 	riff_chunk			data;
70 } _PACKED;
71 
72 enum attr_mode {
73 	kDiscIDAttributes,
74 	kSharedAttributes,
75 	kDeviceAttributes
76 };
77 
78 class Volume {
79 public:
80 							Volume(fs_volume* fsVolume);
81 							~Volume();
82 
83 			status_t		InitCheck();
84 
85 			fs_volume*		FSVolume() const { return fFSVolume; }
86 			dev_t			ID() const { return fFSVolume->id; }
87 			uint32			DiscID() const { return fDiscID; }
88 			Inode&			RootNode() const { return *fRootNode; }
89 
90 			status_t		Mount(const char* device);
91 			int				Device() const { return fDevice; }
92 			ino_t			GetNextNodeID() { return fNextID++; }
93 
94 			const char*		Name() const { return fName; }
95 			status_t		SetName(const char* name);
96 
97 			Semaphore&		Lock();
98 
99 			Inode*			Find(ino_t id);
100 			Inode*			Find(const char* name);
101 
102 			Inode*			FirstEntry() const { return fFirstEntry; }
103 
104 			off_t			NumBlocks() const { return fNumBlocks; }
105 			size_t			BufferSize() const { return 32 * kFrameSize; }
106 								// TODO: for now
107 
108 			void			DisableCDDBLookUps();
109 
110 	static	void			DetermineName(uint32 cddbId, int device, char* name,
111 								size_t length);
112 
113 private:
114 			Inode*			_CreateNode(Inode* parent, const char* name,
115 								off_t start, off_t frames, int32 type);
116 			int				_OpenAttributes(int mode,
117 								enum attr_mode attrMode = kDiscIDAttributes);
118 			void			_RestoreAttributes();
119 			void			_RestoreAttributes(int fd);
120 			void			_StoreAttributes();
121 			void			_RestoreSharedAttributes();
122 			void			_StoreSharedAttributes();
123 
124 			Semaphore		fLock;
125 			fs_volume*		fFSVolume;
126 			int				fDevice;
127 			uint32			fDiscID;
128 			Inode*			fRootNode;
129 			ino_t			fNextID;
130 			char*			fName;
131 			off_t			fNumBlocks;
132 
133 			// root directory contents - we don't support other directories
134 			Inode*			fFirstEntry;
135 };
136 
137 class Attribute : public DoublyLinkedListLinkImpl<Attribute> {
138 public:
139 							Attribute(const char* name, type_code type);
140 							~Attribute();
141 
142 			status_t		InitCheck() const
143 								{ return fName != NULL ? B_OK : B_NO_MEMORY; }
144 			status_t		SetTo(const char* name, type_code type);
145 			void			SetType(type_code type)
146 								{ fType = type; }
147 
148 			status_t		ReadAt(off_t offset, uint8* buffer,
149 								size_t* _length);
150 			status_t		WriteAt(off_t offset, const uint8* buffer,
151 								size_t* _length);
152 			void			Truncate();
153 			status_t		SetSize(off_t size);
154 
155 			const char*		Name() const { return fName; }
156 			size_t			Size() const { return fSize; }
157 			type_code		Type() const { return fType; }
158 			uint8*			Data() const { return fData; }
159 
160 			bool			IsProtectedNamespace();
161 	static	bool			IsProtectedNamespace(const char* name);
162 
163 private:
164 			char*			fName;
165 			type_code		fType;
166 			uint8*			fData;
167 			size_t			fSize;
168 };
169 
170 class Inode {
171 public:
172 							Inode(Volume* volume, Inode* parent,
173 								const char* name, off_t start, off_t frames,
174 								int32 type);
175 							~Inode();
176 
177 			status_t		InitCheck();
178 			ino_t			ID() const { return fID; }
179 
180 			const char*		Name() const { return fName; }
181 			status_t		SetName(const char* name);
182 
183 			int32			Type() const
184 								{ return fType; }
185 			gid_t			GroupID() const
186 								{ return fGroupID; }
187 			uid_t			UserID() const
188 								{ return fUserID; }
189 			time_t			CreationTime() const
190 								{ return fCreationTime; }
191 			time_t			ModificationTime() const
192 								{ return fModificationTime; }
193 			off_t			StartFrame() const
194 								{ return fStartFrame; }
195 			off_t			FrameCount() const
196 								{ return fFrameCount; }
197 			off_t			Size() const
198 								{ return fFrameCount * kFrameSize; }
199 									// does not include the WAV header
200 
201 			Attribute*		FindAttribute(const char* name) const;
202 			status_t		AddAttribute(Attribute* attribute, bool overwrite);
203 			status_t		AddAttribute(const char* name, type_code type,
204 								bool overwrite, const uint8* data,
205 								size_t length);
206 			status_t		AddAttribute(const char* name, type_code type,
207 								const char* string);
208 			status_t		AddAttribute(const char* name, type_code type,
209 								uint32 value);
210 			status_t		RemoveAttribute(const char* name,
211 								bool checkNamespace = false);
212 
213 			void			AddAttrCookie(attr_cookie* cookie);
214 			void			RemoveAttrCookie(attr_cookie* cookie);
215 			void			RewindAttrCookie(attr_cookie* cookie);
216 
217 			AttributeList::ConstIterator Attributes() const
218 								{ return fAttributes.GetIterator(); }
219 
220 			const wav_header* WAVHeader() const
221 								{ return &fWAVHeader; }
222 
223 			Inode*			Next() const { return fNext; }
224 			void			SetNext(Inode *inode) { fNext = inode; }
225 
226 private:
227 			Inode*			fNext;
228 			ino_t			fID;
229 			int32			fType;
230 			char*			fName;
231 			gid_t			fGroupID;
232 			uid_t			fUserID;
233 			time_t			fCreationTime;
234 			time_t			fModificationTime;
235 			off_t			fStartFrame;
236 			off_t			fFrameCount;
237 			AttributeList	fAttributes;
238 			AttrCookieList	fAttrCookies;
239 			wav_header		fWAVHeader;
240 };
241 
242 struct dir_cookie {
243 	Inode*		current;
244 	int			state;	// iteration state
245 };
246 
247 // directory iteration states
248 enum {
249 	ITERATION_STATE_DOT		= 0,
250 	ITERATION_STATE_DOT_DOT	= 1,
251 	ITERATION_STATE_OTHERS	= 2,
252 	ITERATION_STATE_BEGIN	= ITERATION_STATE_DOT,
253 };
254 
255 struct attr_cookie : DoublyLinkedListLinkImpl<attr_cookie> {
256 	Attribute*	current;
257 };
258 
259 struct file_cookie {
260 	int			open_mode;
261 	off_t		buffer_offset;
262 	void*		buffer;
263 };
264 
265 static const uint32 kMaxAttributeSize = 65536;
266 static const uint32 kMaxAttributes = 64;
267 
268 static const char* kProtectedAttrNamespace = "CD:";
269 
270 static const char* kCddbIdAttribute = "CD:cddbid";
271 static const char* kDoLookupAttribute = "CD:do_lookup";
272 static const char* kTocAttribute = "CD:toc";
273 
274 extern fs_volume_ops gCDDAVolumeOps;
275 extern fs_vnode_ops gCDDAVnodeOps;
276 
277 
278 //	#pragma mark helper functions
279 
280 
281 /*!	Determines if the attribute is shared among all devices or among
282 	all CDs in a specific device.
283 	We use this to share certain Tracker attributes.
284 */
285 static bool
286 is_special_attribute(const char* name, attr_mode attrMode)
287 {
288 	if (attrMode == kDeviceAttributes) {
289 		static const char* kAttributes[] = {
290 			"_trk/windframe",
291 			"_trk/pinfo",
292 			"_trk/pinfo_le",
293 			NULL,
294 		};
295 
296 		for (int32 i = 0; kAttributes[i]; i++) {
297 			if (!strcmp(name, kAttributes[i]))
298 				return true;
299 		}
300 	} else if (attrMode == kSharedAttributes) {
301 		static const char* kAttributes[] = {
302 			"_trk/columns",
303 			"_trk/columns_le",
304 			"_trk/viewstate",
305 			"_trk/viewstate_le",
306 			NULL,
307 		};
308 
309 		for (int32 i = 0; kAttributes[i]; i++) {
310 			if (!strcmp(name, kAttributes[i]))
311 				return true;
312 		}
313 	}
314 
315 	return false;
316 }
317 
318 
319 static void
320 write_line(int fd, const char* line)
321 {
322 	if (line == NULL)
323 		line = "";
324 
325 	size_t length = strlen(line);
326 	write(fd, line, length);
327 	write(fd, "\n", 1);
328 }
329 
330 
331 static void
332 write_attributes(int fd, Inode* inode, attr_mode attrMode = kDiscIDAttributes)
333 {
334 	// count attributes
335 
336 	AttributeList::ConstIterator iterator = inode->Attributes();
337 	uint32 count = 0;
338 	while (iterator.HasNext()) {
339 		Attribute* attribute = iterator.Next();
340 		if ((attrMode == kDiscIDAttributes
341 			|| is_special_attribute(attribute->Name(), attrMode))
342 				&& !attribute->IsProtectedNamespace())
343 			count++;
344 	}
345 
346 	// we're artificially limiting the attribute count per inode
347 	if (count > kMaxAttributes)
348 		count = kMaxAttributes;
349 
350 	count = B_HOST_TO_BENDIAN_INT32(count);
351 	write(fd, &count, sizeof(uint32));
352 
353 	// write attributes
354 
355 	iterator.Rewind();
356 
357 	while (iterator.HasNext()) {
358 		Attribute* attribute = iterator.Next();
359 		if ((attrMode != kDiscIDAttributes
360 			&& !is_special_attribute(attribute->Name(), attrMode))
361 				|| attribute->IsProtectedNamespace())
362 			continue;
363 
364 		uint32 type = B_HOST_TO_BENDIAN_INT32(attribute->Type());
365 		write(fd, &type, sizeof(uint32));
366 
367 		uint8 length = strlen(attribute->Name());
368 		write(fd, &length, 1);
369 		write(fd, attribute->Name(), length);
370 
371 		uint32 size = B_HOST_TO_BENDIAN_INT32(attribute->Size());
372 		write(fd, &size, sizeof(uint32));
373 		if (size != 0)
374 			write(fd, attribute->Data(), attribute->Size());
375 
376 		if (--count == 0)
377 			break;
378 	}
379 }
380 
381 
382 static bool
383 read_line(int fd, char* line, size_t length)
384 {
385 	bool first = true;
386 	size_t pos = 0;
387 	char c;
388 
389 	while (read(fd, &c, 1) == 1) {
390 		first = false;
391 
392 		if (c == '\n')
393 			break;
394 		if (pos < length)
395 			line[pos] = c;
396 
397 		pos++;
398 	}
399 
400 	if (pos >= length - 1)
401 		pos = length - 1;
402 	line[pos] = '\0';
403 
404 	return !first;
405 }
406 
407 
408 static bool
409 read_attributes(int fd, Inode* inode)
410 {
411 	uint32 count;
412 	if (read(fd, &count, sizeof(uint32)) != (ssize_t)sizeof(uint32))
413 		return false;
414 
415 	count = B_BENDIAN_TO_HOST_INT32(count);
416 	if (count > kMaxAttributes)
417 		return false;
418 
419 	for (uint32 i = 0; i < count; i++) {
420 		char name[B_ATTR_NAME_LENGTH + 1];
421 		uint32 type, size;
422 		uint8 length;
423 		if (read(fd, &type, sizeof(uint32)) != (ssize_t)sizeof(uint32)
424 			|| read(fd, &length, 1) != 1
425 			|| read(fd, name, length) != length
426 			|| read(fd, &size, sizeof(uint32)) != (ssize_t)sizeof(uint32))
427 			return false;
428 
429 		type = B_BENDIAN_TO_HOST_INT32(type);
430 		size = B_BENDIAN_TO_HOST_INT32(size);
431 		name[length] = '\0';
432 
433 		Attribute* attribute = new Attribute(name, type);
434 		if (attribute->IsProtectedNamespace()) {
435 			// Attributes in the protected namespace are handled internally
436 			// so we do not load them even if they are present in the
437 			// attributes file.
438 			delete attribute;
439 			continue;
440 		}
441 		if (attribute->SetSize(size) != B_OK
442 			|| inode->AddAttribute(attribute, true) != B_OK) {
443 			delete attribute;
444 		} else
445 			read(fd, attribute->Data(), size);
446 	}
447 
448 	return true;
449 }
450 
451 
452 static int
453 open_attributes(uint32 cddbID, int deviceFD, int mode,
454 	enum attr_mode attrMode)
455 {
456 	char* path = (char*)malloc(B_PATH_NAME_LENGTH);
457 	if (path == NULL)
458 		return -1;
459 
460 	bool create = (mode & O_WRONLY) != 0;
461 
462 	if (find_directory(B_USER_SETTINGS_DIRECTORY, -1, create, path,
463 			B_PATH_NAME_LENGTH) != B_OK) {
464 		free(path);
465 		return -1;
466 	}
467 
468 	strlcat(path, "/cdda", B_PATH_NAME_LENGTH);
469 	if (create)
470 		mkdir(path, 0755);
471 
472 	if (attrMode == kDiscIDAttributes) {
473 		char id[64];
474 		snprintf(id, sizeof(id), "/%08lx", cddbID);
475 		strlcat(path, id, B_PATH_NAME_LENGTH);
476 	} else if (attrMode == kDeviceAttributes) {
477 		uint32 length = strlen(path);
478 		char* deviceName = path + length;
479 		if (ioctl(deviceFD, B_GET_PATH_FOR_DEVICE, deviceName,
480 				B_PATH_NAME_LENGTH - length) < B_OK) {
481 			free(path);
482 			return B_ERROR;
483 		}
484 
485 		deviceName++;
486 
487 		// replace slashes in the device path
488 		while (deviceName[0]) {
489 			if (deviceName[0] == '/')
490 				deviceName[0] = '_';
491 
492 			deviceName++;
493 		}
494 	} else
495 		strlcat(path, "/shared", B_PATH_NAME_LENGTH);
496 
497 	int fd = open(path, mode | (create ? O_CREAT | O_TRUNC : 0), 0644);
498 
499 	free(path);
500 	return fd;
501 }
502 
503 
504 static void
505 fill_stat_buffer(Volume* volume, Inode* inode, Attribute* attribute,
506 	struct stat& stat)
507 {
508 	stat.st_dev = volume->FSVolume()->id;
509 	stat.st_ino = inode->ID();
510 
511 	if (attribute != NULL) {
512 		stat.st_size = attribute->Size();
513 		stat.st_blocks = 0;
514 		stat.st_mode = S_ATTR | 0666;
515 		stat.st_type = attribute->Type();
516 	} else {
517 		stat.st_size = inode->Size() + sizeof(wav_header);
518 		stat.st_blocks = inode->Size() / 512;
519 		stat.st_mode = inode->Type();
520 		stat.st_type = 0;
521 	}
522 
523 	stat.st_nlink = 1;
524 	stat.st_blksize = 2048;
525 
526 	stat.st_uid = inode->UserID();
527 	stat.st_gid = inode->GroupID();
528 
529 	stat.st_atime = time(NULL);
530 	stat.st_mtime = stat.st_ctime = inode->ModificationTime();
531 	stat.st_crtime = inode->CreationTime();
532 }
533 
534 
535 bool
536 is_data_track(const scsi_toc_track& track)
537 {
538 	return (track.control & 4) != 0;
539 }
540 
541 
542 uint32
543 count_audio_tracks(scsi_toc_toc* toc)
544 {
545 	uint32 lastTrack = toc->last_track + 1 - toc->first_track;
546 	uint32 count = 0;
547 	for (uint32 i = 0; i < lastTrack; i++) {
548 		if (!is_data_track(toc->tracks[i]))
549 			count++;
550 	}
551 
552 	return count;
553 }
554 
555 
556 //	#pragma mark - Volume class
557 
558 
559 Volume::Volume(fs_volume* fsVolume)
560 	:
561 	fLock("cdda"),
562 	fFSVolume(fsVolume),
563 	fDevice(-1),
564 	fRootNode(NULL),
565 	fNextID(1),
566 	fName(NULL),
567 	fNumBlocks(0),
568 	fFirstEntry(NULL)
569 {
570 }
571 
572 
573 Volume::~Volume()
574 {
575 	if (fRootNode) {
576 		_StoreAttributes();
577 		_StoreSharedAttributes();
578 	}
579 
580 	if (fDevice >= 0)
581 		close(fDevice);
582 
583 	// put_vnode on the root to release the ref to it
584 	if (fRootNode)
585 		put_vnode(FSVolume(), fRootNode->ID());
586 
587 	delete fRootNode;
588 
589 	Inode* inode;
590 	Inode* next;
591 
592 	for (inode = fFirstEntry; inode != NULL; inode = next) {
593 		next = inode->Next();
594 		delete inode;
595 	}
596 
597 	free(fName);
598 }
599 
600 
601 status_t
602 Volume::InitCheck()
603 {
604 	if (fLock.InitCheck() < B_OK)
605 		return B_ERROR;
606 
607 	return B_OK;
608 }
609 
610 
611 /*static*/ void
612 Volume::DetermineName(uint32 cddbID, int device, char* name, size_t length)
613 {
614 	name[0] = '\0';
615 
616 	int attrFD = open_attributes(cddbID, device, O_RDONLY,
617 		kDiscIDAttributes);
618 	if (attrFD < 0) {
619 		// We do not have attributes set. Read CD text.
620 		cdtext text;
621 		if (read_cdtext(device, text) == B_OK) {
622 			if (text.artist != NULL && text.album != NULL)
623 				snprintf(name, length, "%s - %s", text.artist, text.album);
624 			else if (text.artist != NULL || text.album != NULL) {
625 				snprintf(name, length, "%s", text.artist != NULL
626 					? text.artist : text.album);
627 			}
628 		}
629 	} else {
630 		// We have an attribute file. Read name from it.
631 		if (!read_line(attrFD, name, length))
632 			name[0] = '\0';
633 
634 		close(attrFD);
635 	}
636 
637 	if (!name[0])
638 		strlcpy(name, "Audio CD", length);
639 }
640 
641 
642 status_t
643 Volume::Mount(const char* device)
644 {
645 	fDevice = open(device, O_RDONLY);
646 	if (fDevice < 0)
647 		return errno;
648 
649 	scsi_toc_toc* toc = (scsi_toc_toc*)malloc(1024);
650 	if (toc == NULL)
651 		return B_NO_MEMORY;
652 
653 	status_t status = read_table_of_contents(fDevice, toc, 1024);
654 	// there has to be at least one audio track
655 	if (status == B_OK && count_audio_tracks(toc) == 0)
656 		status = B_BAD_TYPE;
657 
658 	if (status != B_OK) {
659 		free(toc);
660 		return status;
661 	}
662 
663 	fDiscID = compute_cddb_disc_id(*toc);
664 
665 	// create the root vnode
666 	fRootNode = _CreateNode(NULL, "", 0, 0, S_IFDIR | 0777);
667 	if (fRootNode == NULL)
668 		status = B_NO_MEMORY;
669 	if (status == B_OK) {
670 		status = publish_vnode(FSVolume(), fRootNode->ID(), fRootNode,
671 			&gCDDAVnodeOps, fRootNode->Type(), 0);
672 	}
673 	if (status != B_OK) {
674 		free(toc);
675 		return status;
676 	}
677 
678 	bool doLookup = true;
679 	cdtext text;
680 	int fd = _OpenAttributes(O_RDONLY);
681 	if (fd < 0) {
682 		// We do not seem to have an attribute file so this is probably the
683 		// first time this CD is inserted. In this case, try to read CD-Text
684 		// data.
685 		if (read_cdtext(fDevice, text) != B_OK)
686 			dprintf("CDDA: no CD-Text found.\n");
687 		else
688 			doLookup = false;
689 	} else {
690 		doLookup = false;
691 	}
692 
693 	int32 trackCount = toc->last_track + 1 - toc->first_track;
694 	off_t totalFrames = 0;
695 	char title[256];
696 
697 	for (int32 i = 0; i < trackCount; i++) {
698 		scsi_cd_msf& next = toc->tracks[i + 1].start.time;
699 			// the last track is always lead-out
700 		scsi_cd_msf& start = toc->tracks[i].start.time;
701 		int32 track = i + 1;
702 
703 		off_t startFrame = start.minute * kFramesPerMinute
704 			+ start.second * kFramesPerSecond + start.frame;
705 		off_t frames = next.minute * kFramesPerMinute
706 			+ next.second * kFramesPerSecond + next.frame
707 			- startFrame;
708 
709 		totalFrames += frames;
710 
711 		if (is_data_track(toc->tracks[i]))
712 			continue;
713 
714 		if (text.titles[i] != NULL) {
715 			if (text.artists[i] != NULL) {
716 				snprintf(title, sizeof(title), "%02ld. %s - %s.wav", track,
717 					text.artists[i], text.titles[i]);
718 			} else {
719 				snprintf(title, sizeof(title), "%02ld. %s.wav", track,
720 					text.titles[i]);
721 			}
722 		} else
723 			snprintf(title, sizeof(title), "Track %02ld.wav", track);
724 
725 		// remove '/' and '\n' from title
726 		for (int32 j = 0; title[j]; j++) {
727 			if (title[j] == '/')
728 				title[j] = '-';
729 			else if (title[j] == '\n')
730 				title[j] = ' ';
731 		}
732 
733 		Inode* inode = _CreateNode(fRootNode, title, startFrame, frames,
734 			S_IFREG | 0444);
735 		if (inode == NULL)
736 			continue;
737 
738 		// add attributes
739 
740 		inode->AddAttribute("Audio:Artist", B_STRING_TYPE,
741 			text.artists[i] != NULL ? text.artists[i] : text.artist);
742 		inode->AddAttribute("Audio:Album", B_STRING_TYPE, text.album);
743 		inode->AddAttribute("Audio:Title", B_STRING_TYPE, text.titles[i]);
744 		inode->AddAttribute("Audio:Genre", B_STRING_TYPE, text.genre);
745 		inode->AddAttribute("Audio:Track", B_INT32_TYPE, track);
746 		inode->AddAttribute("Audio:Bitrate", B_STRING_TYPE, "1411 kbps");
747 
748 		snprintf(title, sizeof(title), "%02lu:%02lu",
749 			uint32(inode->FrameCount() / kFramesPerMinute),
750 			uint32((inode->FrameCount() % kFramesPerMinute) / kFramesPerSecond));
751 		inode->AddAttribute("Audio:Length", B_STRING_TYPE, title);
752 		inode->AddAttribute("BEOS:TYPE", B_MIME_STRING_TYPE, "audio/x-wav");
753 	}
754 
755 	// Add CD:cddbid attribute.
756 	fRootNode->AddAttribute(kCddbIdAttribute, B_UINT32_TYPE, fDiscID);
757 
758 	// Add CD:do_lookup attribute.
759 	fRootNode->AddAttribute(kDoLookupAttribute, B_BOOL_TYPE, true,
760 		(const uint8*)&doLookup, sizeof(bool));
761 
762 	// Add CD:toc attribute.
763 	fRootNode->AddAttribute(kTocAttribute, B_RAW_TYPE, true,
764 		(const uint8*)toc, B_BENDIAN_TO_HOST_INT16(toc->data_length) + 2);
765 
766 	_RestoreSharedAttributes();
767 	if (fd >= 0)
768 		_RestoreAttributes(fd);
769 
770 	free(toc);
771 
772 	// determine volume title
773 	DetermineName(fDiscID, fDevice, title, sizeof(title));
774 
775 	fName = strdup(title);
776 	if (fName == NULL)
777 		return B_NO_MEMORY;
778 
779 	fNumBlocks = totalFrames;
780 	return B_OK;
781 }
782 
783 
784 Semaphore&
785 Volume::Lock()
786 {
787 	return fLock;
788 }
789 
790 
791 Inode*
792 Volume::_CreateNode(Inode* parent, const char* name, off_t start, off_t frames,
793 	int32 type)
794 {
795 	Inode* inode = new Inode(this, parent, name, start, frames, type);
796 	if (inode == NULL)
797 		return NULL;
798 
799 	if (inode->InitCheck() != B_OK) {
800 		delete inode;
801 		return NULL;
802 	}
803 
804 	if (S_ISREG(type)) {
805 		// we need to order it by track for compatibility with BeOS' cdda
806 		Inode* current = fFirstEntry;
807 		Inode* last = NULL;
808 		while (current != NULL) {
809 			last = current;
810 			current = current->Next();
811 		}
812 
813 		if (last)
814 			last->SetNext(inode);
815 		else
816 			fFirstEntry = inode;
817 	}
818 
819 	return inode;
820 }
821 
822 
823 Inode*
824 Volume::Find(ino_t id)
825 {
826 	for (Inode* inode = fFirstEntry; inode != NULL; inode = inode->Next()) {
827 		if (inode->ID() == id)
828 			return inode;
829 	}
830 
831 	return NULL;
832 }
833 
834 
835 Inode*
836 Volume::Find(const char* name)
837 {
838 	if (!strcmp(name, ".")
839 		|| !strcmp(name, ".."))
840 		return fRootNode;
841 
842 	for (Inode* inode = fFirstEntry; inode != NULL; inode = inode->Next()) {
843 		if (!strcmp(inode->Name(), name))
844 			return inode;
845 	}
846 
847 	return NULL;
848 }
849 
850 
851 status_t
852 Volume::SetName(const char* name)
853 {
854 	if (name == NULL || !name[0])
855 		return B_BAD_VALUE;
856 
857 	name = strdup(name);
858 	if (name == NULL)
859 		return B_NO_MEMORY;
860 
861 	free(fName);
862 	fName = (char*)name;
863 	return B_OK;
864 }
865 
866 
867 void
868 Volume::DisableCDDBLookUps()
869 {
870 	bool doLookup = false;
871 	RootNode().AddAttribute(kDoLookupAttribute, B_BOOL_TYPE, true,
872 		(const uint8*)&doLookup, sizeof(bool));
873 }
874 
875 
876 /*!	Opens the file that contains the volume and inode titles as well as all
877 	of their attributes.
878 	The attributes are stored in files below B_USER_SETTINGS_DIRECTORY/cdda.
879 */
880 int
881 Volume::_OpenAttributes(int mode, enum attr_mode attrMode)
882 {
883 	return open_attributes(fDiscID, fDevice, mode, attrMode);
884 }
885 
886 
887 /*!	Reads the attributes, if any, that belong to the CD currently being
888 	mounted.
889 */
890 void
891 Volume::_RestoreAttributes()
892 {
893 	int fd = _OpenAttributes(O_RDONLY);
894 	if (fd < 0)
895 		return;
896 
897 	_RestoreAttributes(fd);
898 
899 	close(fd);
900 }
901 
902 
903 void
904 Volume::_RestoreAttributes(int fd)
905 {
906 	char line[B_FILE_NAME_LENGTH];
907 	if (!read_line(fd, line, B_FILE_NAME_LENGTH))
908 		return;
909 
910 	SetName(line);
911 
912 	for (Inode* inode = fFirstEntry; inode != NULL; inode = inode->Next()) {
913 		if (!read_line(fd, line, B_FILE_NAME_LENGTH))
914 			break;
915 
916 		inode->SetName(line);
917 	}
918 
919 	if (read_attributes(fd, fRootNode)) {
920 		for (Inode* inode = fFirstEntry; inode != NULL; inode = inode->Next()) {
921 			if (!read_attributes(fd, inode))
922 				break;
923 		}
924 	}
925 }
926 
927 
928 void
929 Volume::_StoreAttributes()
930 {
931 	int fd = _OpenAttributes(O_WRONLY);
932 	if (fd < 0)
933 		return;
934 
935 	write_line(fd, Name());
936 
937 	for (Inode* inode = fFirstEntry; inode != NULL; inode = inode->Next()) {
938 		write_line(fd, inode->Name());
939 	}
940 
941 	write_attributes(fd, fRootNode);
942 
943 	for (Inode* inode = fFirstEntry; inode != NULL; inode = inode->Next()) {
944 		write_attributes(fd, inode);
945 	}
946 
947 	close(fd);
948 }
949 
950 
951 /*!	Restores the attributes, if any, that are shared between CDs; some are
952 	stored per device, others are stored for all CDs no matter which device.
953 */
954 void
955 Volume::_RestoreSharedAttributes()
956 {
957 	// device attributes overwrite shared attributes
958 
959 	int fd = _OpenAttributes(O_RDONLY, kSharedAttributes);
960 	if (fd >= 0) {
961 		read_attributes(fd, fRootNode);
962 		close(fd);
963 	}
964 
965 	fd = _OpenAttributes(O_RDONLY, kDeviceAttributes);
966 	if (fd >= 0) {
967 		read_attributes(fd, fRootNode);
968 		close(fd);
969 	}
970 }
971 
972 
973 void
974 Volume::_StoreSharedAttributes()
975 {
976 	// write shared and device specific settings
977 
978 	int fd = _OpenAttributes(O_WRONLY, kSharedAttributes);
979 	if (fd >= 0) {
980 		write_attributes(fd, fRootNode, kSharedAttributes);
981 		close(fd);
982 	}
983 
984 	fd = _OpenAttributes(O_WRONLY, kDeviceAttributes);
985 	if (fd >= 0) {
986 		write_attributes(fd, fRootNode, kDeviceAttributes);
987 		close(fd);
988 	}
989 }
990 
991 
992 //	#pragma mark - Attribute class
993 
994 
995 Attribute::Attribute(const char* name, type_code type)
996 	:
997 	fName(NULL),
998 	fType(0),
999 	fData(NULL),
1000 	fSize(0)
1001 {
1002 	SetTo(name, type);
1003 }
1004 
1005 
1006 Attribute::~Attribute()
1007 {
1008 	free(fName);
1009 	free(fData);
1010 }
1011 
1012 
1013 status_t
1014 Attribute::SetTo(const char* name, type_code type)
1015 {
1016 	if (name == NULL || !name[0])
1017 		return B_BAD_VALUE;
1018 
1019 	name = strdup(name);
1020 	if (name == NULL)
1021 		return B_NO_MEMORY;
1022 
1023 	free(fName);
1024 
1025 	fName = (char*)name;
1026 	fType = type;
1027 	return B_OK;
1028 }
1029 
1030 
1031 status_t
1032 Attribute::ReadAt(off_t offset, uint8* buffer, size_t* _length)
1033 {
1034 	size_t length = *_length;
1035 
1036 	if (offset < 0)
1037 		return B_BAD_VALUE;
1038 	if (offset >= fSize) {
1039 		*_length = 0;
1040 		return B_OK;
1041 	}
1042 	if (offset + length > fSize)
1043 		length = fSize - offset;
1044 
1045 	if (user_memcpy(buffer, fData + offset, length) < B_OK)
1046 		return B_BAD_ADDRESS;
1047 
1048 	*_length = length;
1049 	return B_OK;
1050 }
1051 
1052 
1053 /*!	Writes to the attribute and enlarges it as needed.
1054 	An attribute has a maximum size of 65536 bytes for now.
1055 */
1056 status_t
1057 Attribute::WriteAt(off_t offset, const uint8* buffer, size_t* _length)
1058 {
1059 	size_t length = *_length;
1060 
1061 	if (offset < 0)
1062 		return B_BAD_VALUE;
1063 
1064 	// we limit the attribute size to something reasonable
1065 	off_t end = offset + length;
1066 	if (end > kMaxAttributeSize) {
1067 		end = kMaxAttributeSize;
1068 		length = end - offset;
1069 	}
1070 	if (offset > end) {
1071 		*_length = 0;
1072 		return E2BIG;
1073 	}
1074 
1075 	if (end > fSize) {
1076 		// make room in the data stream
1077 		uint8* data = (uint8*)realloc(fData, end);
1078 		if (data == NULL)
1079 			return B_NO_MEMORY;
1080 
1081 		if (fSize < offset)
1082 			memset(data + fSize, 0, offset - fSize);
1083 
1084 		fData = data;
1085 		fSize = end;
1086 	}
1087 
1088 	if (user_memcpy(fData + offset, buffer, length) < B_OK)
1089 		return B_BAD_ADDRESS;
1090 
1091 	*_length = length;
1092 	return B_OK;
1093 }
1094 
1095 
1096 //!	Removes all data from the attribute.
1097 void
1098 Attribute::Truncate()
1099 {
1100 	free(fData);
1101 	fData = NULL;
1102 	fSize = 0;
1103 }
1104 
1105 
1106 /*!	Resizes the data part of an attribute to the requested amount \a size.
1107 	An attribute has a maximum size of 65536 bytes for now.
1108 */
1109 status_t
1110 Attribute::SetSize(off_t size)
1111 {
1112 	if (size > kMaxAttributeSize)
1113 		return E2BIG;
1114 
1115 	uint8* data = (uint8*)realloc(fData, size);
1116 	if (data == NULL)
1117 		return B_NO_MEMORY;
1118 
1119 	if (fSize < size)
1120 		memset(data + fSize, 0, size - fSize);
1121 
1122 	fData = data;
1123 	fSize = size;
1124 	return B_OK;
1125 }
1126 
1127 
1128 bool
1129 Attribute::IsProtectedNamespace()
1130 {
1131 	// Check if the attribute is in the restricted namespace. Attributes in
1132 	// this namespace should not be edited by the user as they are handled
1133 	// internally by the add-on. Calls the static version.
1134 	return IsProtectedNamespace(fName);
1135 }
1136 
1137 
1138 bool
1139 Attribute::IsProtectedNamespace(const char* name)
1140 {
1141 	// Convenience static version of the above method. Usually called when we
1142 	// don't have a constructed Attribute object handy.
1143 	return strncmp(kProtectedAttrNamespace, name,
1144 		strlen(kProtectedAttrNamespace)) == 0;
1145 }
1146 
1147 
1148 //	#pragma mark - Inode class
1149 
1150 
1151 Inode::Inode(Volume* volume, Inode* parent, const char* name, off_t start,
1152 		off_t frames, int32 type)
1153 	:
1154 	fNext(NULL)
1155 {
1156 	fName = strdup(name);
1157 	if (fName == NULL)
1158 		return;
1159 
1160 	fID = volume->GetNextNodeID();
1161 	fType = type;
1162 	fStartFrame = start;
1163 	fFrameCount = frames;
1164 
1165 	fUserID = geteuid();
1166 	fGroupID = parent ? parent->GroupID() : getegid();
1167 
1168 	fCreationTime = fModificationTime = time(NULL);
1169 
1170 	if (frames) {
1171 		// initialize WAV header
1172 
1173 		// RIFF header
1174 		fWAVHeader.header.magic = B_HOST_TO_BENDIAN_INT32('RIFF');
1175 		fWAVHeader.header.length = B_HOST_TO_LENDIAN_INT32(Size()
1176 			+ sizeof(wav_header) - sizeof(riff_chunk));
1177 		fWAVHeader.header.id = B_HOST_TO_BENDIAN_INT32('WAVE');
1178 
1179 		// 'fmt ' format chunk
1180 		fWAVHeader.format.fourcc = B_HOST_TO_BENDIAN_INT32('fmt ');
1181 		fWAVHeader.format.length = B_HOST_TO_LENDIAN_INT32(
1182 			sizeof(wav_format_chunk) - sizeof(riff_chunk));
1183 		fWAVHeader.format.format_tag = B_HOST_TO_LENDIAN_INT16(1);
1184 		fWAVHeader.format.channels = B_HOST_TO_LENDIAN_INT16(2);
1185 		fWAVHeader.format.samples_per_second = B_HOST_TO_LENDIAN_INT32(44100);
1186 		fWAVHeader.format.average_bytes_per_second = B_HOST_TO_LENDIAN_INT32(
1187 			44100 * sizeof(uint16) * 2);
1188 		fWAVHeader.format.block_align = B_HOST_TO_LENDIAN_INT16(4);
1189 		fWAVHeader.format.bits_per_sample = B_HOST_TO_LENDIAN_INT16(16);
1190 
1191 		// 'data' chunk
1192 		fWAVHeader.data.fourcc = B_HOST_TO_BENDIAN_INT32('data');
1193 		fWAVHeader.data.length = B_HOST_TO_LENDIAN_INT32(Size());
1194 	}
1195 }
1196 
1197 
1198 Inode::~Inode()
1199 {
1200 	free(const_cast<char*>(fName));
1201 }
1202 
1203 
1204 status_t
1205 Inode::InitCheck()
1206 {
1207 	if (fName == NULL)
1208 		return B_NO_MEMORY;
1209 
1210 	return B_OK;
1211 }
1212 
1213 
1214 status_t
1215 Inode::SetName(const char* name)
1216 {
1217 	if (name == NULL || !name[0]
1218 		|| strchr(name, '/') != NULL
1219 		|| strchr(name, '\n') != NULL)
1220 		return B_BAD_VALUE;
1221 
1222 	name = strdup(name);
1223 	if (name == NULL)
1224 		return B_NO_MEMORY;
1225 
1226 	free(fName);
1227 	fName = (char*)name;
1228 	return B_OK;
1229 }
1230 
1231 
1232 Attribute*
1233 Inode::FindAttribute(const char* name) const
1234 {
1235 	if (name == NULL || !name[0])
1236 		return NULL;
1237 
1238 	AttributeList::ConstIterator iterator = fAttributes.GetIterator();
1239 
1240 	while (iterator.HasNext()) {
1241 		Attribute* attribute = iterator.Next();
1242 		if (!strcmp(attribute->Name(), name))
1243 			return attribute;
1244 	}
1245 
1246 	return NULL;
1247 }
1248 
1249 
1250 status_t
1251 Inode::AddAttribute(Attribute* attribute, bool overwrite)
1252 {
1253 	Attribute* oldAttribute = FindAttribute(attribute->Name());
1254 	if (oldAttribute != NULL) {
1255 		if (!overwrite)
1256 			return B_NAME_IN_USE;
1257 
1258 		fAttributes.Remove(oldAttribute);
1259 		delete oldAttribute;
1260 	}
1261 
1262 	fAttributes.Add(attribute);
1263 	return B_OK;
1264 }
1265 
1266 
1267 status_t
1268 Inode::AddAttribute(const char* name, type_code type, bool overwrite,
1269 	const uint8* data, size_t length)
1270 {
1271 	Attribute* attribute = new Attribute(name, type);
1272 	if (attribute == NULL)
1273 		return B_NO_MEMORY;
1274 
1275 	status_t status = attribute->InitCheck();
1276 	if (status == B_OK && data != NULL && length != 0)
1277 		status = attribute->WriteAt(0, data, &length);
1278 	if (status == B_OK)
1279 		status = AddAttribute(attribute, overwrite);
1280 	if (status != B_OK) {
1281 		delete attribute;
1282 		return status;
1283 	}
1284 
1285 	return B_OK;
1286 }
1287 
1288 
1289 status_t
1290 Inode::AddAttribute(const char* name, type_code type, const char* string)
1291 {
1292 	if (string == NULL)
1293 		return B_BAD_VALUE;
1294 
1295 	return AddAttribute(name, type, true, (const uint8*)string,
1296 		strlen(string));
1297 }
1298 
1299 
1300 status_t
1301 Inode::AddAttribute(const char* name, type_code type, uint32 value)
1302 {
1303 	return AddAttribute(name, type, true, (const uint8*)&value, sizeof(uint32));
1304 }
1305 
1306 
1307 status_t
1308 Inode::RemoveAttribute(const char* name, bool checkNamespace)
1309 {
1310 	if (name == NULL || !name[0])
1311 		return B_ENTRY_NOT_FOUND;
1312 
1313 	AttributeList::Iterator iterator = fAttributes.GetIterator();
1314 
1315 	while (iterator.HasNext()) {
1316 		Attribute* attribute = iterator.Next();
1317 		if (!strcmp(attribute->Name(), name)) {
1318 			// check for restricted namespace if required.
1319 			if (checkNamespace && attribute->IsProtectedNamespace())
1320 				return B_NOT_ALLOWED;
1321 			// look for attribute in cookies
1322 			AttrCookieList::Iterator i = fAttrCookies.GetIterator();
1323 			while (i.HasNext()) {
1324 				attr_cookie* cookie = i.Next();
1325 				if (cookie->current == attribute)
1326 					cookie->current = attribute->GetDoublyLinkedListLink()->next;
1327 			}
1328 
1329 			iterator.Remove();
1330 			delete attribute;
1331 			return B_OK;
1332 		}
1333 	}
1334 
1335 	return B_ENTRY_NOT_FOUND;
1336 }
1337 
1338 
1339 void
1340 Inode::AddAttrCookie(attr_cookie* cookie)
1341 {
1342 	fAttrCookies.Add(cookie);
1343 	RewindAttrCookie(cookie);
1344 }
1345 
1346 
1347 void
1348 Inode::RemoveAttrCookie(attr_cookie* cookie)
1349 {
1350 	fAttrCookies.Remove(cookie);
1351 }
1352 
1353 
1354 void
1355 Inode::RewindAttrCookie(attr_cookie* cookie)
1356 {
1357 	cookie->current = fAttributes.First();
1358 }
1359 
1360 
1361 //	#pragma mark - Module API
1362 
1363 
1364 static float
1365 cdda_identify_partition(int fd, partition_data* partition, void** _cookie)
1366 {
1367 	scsi_toc_toc* toc = (scsi_toc_toc*)malloc(2048);
1368 	if (toc == NULL)
1369 		return B_NO_MEMORY;
1370 
1371 	status_t status = read_table_of_contents(fd, toc, 2048);
1372 
1373 	// there has to be at least a single audio track
1374 	if (status == B_OK && count_audio_tracks(toc) == 0)
1375 		status = B_BAD_TYPE;
1376 
1377 	if (status < B_OK) {
1378 		free(toc);
1379 		return status;
1380 	}
1381 
1382 	*_cookie = toc;
1383 	return 0.8f;
1384 }
1385 
1386 
1387 static status_t
1388 cdda_scan_partition(int fd, partition_data* partition, void* _cookie)
1389 {
1390 	scsi_toc_toc* toc = (scsi_toc_toc*)_cookie;
1391 
1392 	partition->status = B_PARTITION_VALID;
1393 	partition->flags |= B_PARTITION_FILE_SYSTEM;
1394 
1395 	// compute length
1396 
1397 	uint32 lastTrack = toc->last_track + 1 - toc->first_track;
1398 	scsi_cd_msf& end = toc->tracks[lastTrack].start.time;
1399 
1400 	partition->content_size = off_t(end.minute * kFramesPerMinute
1401 		+ end.second * kFramesPerSecond + end.frame) * kFrameSize;
1402 	partition->block_size = kFrameSize;
1403 
1404 	// determine volume title
1405 
1406 	char name[256];
1407 	Volume::DetermineName(compute_cddb_disc_id(*toc), fd, name, sizeof(name));
1408 
1409 	partition->content_name = strdup(name);
1410 	if (partition->content_name == NULL)
1411 		return B_NO_MEMORY;
1412 
1413 	return B_OK;
1414 }
1415 
1416 
1417 static void
1418 cdda_free_identify_partition_cookie(partition_data* partition, void* _cookie)
1419 {
1420 	free(_cookie);
1421 }
1422 
1423 
1424 static status_t
1425 cdda_mount(fs_volume* fsVolume, const char* device, uint32 flags,
1426 	const char* args, ino_t* _rootVnodeID)
1427 {
1428 	TRACE(("cdda_mount: entry\n"));
1429 
1430 	Volume* volume = new Volume(fsVolume);
1431 	if (volume == NULL)
1432 		return B_NO_MEMORY;
1433 
1434 	status_t status = volume->InitCheck();
1435 	if (status == B_OK)
1436 		status = volume->Mount(device);
1437 
1438 	if (status < B_OK) {
1439 		delete volume;
1440 		return status;
1441 	}
1442 
1443 	*_rootVnodeID = volume->RootNode().ID();
1444 	fsVolume->private_volume = volume;
1445 	fsVolume->ops = &gCDDAVolumeOps;
1446 
1447 	return B_OK;
1448 }
1449 
1450 
1451 static status_t
1452 cdda_unmount(fs_volume* _volume)
1453 {
1454 	struct Volume* volume = (struct Volume*)_volume->private_volume;
1455 
1456 	TRACE(("cdda_unmount: entry fs = %p\n", _volume));
1457 	delete volume;
1458 
1459 	return 0;
1460 }
1461 
1462 
1463 static status_t
1464 cdda_read_fs_stat(fs_volume* _volume, struct fs_info* info)
1465 {
1466 	Volume* volume = (Volume*)_volume->private_volume;
1467 	Locker locker(volume->Lock());
1468 
1469 	// File system flags.
1470 	info->flags = B_FS_IS_PERSISTENT | B_FS_HAS_ATTR | B_FS_HAS_MIME
1471 		| B_FS_IS_REMOVABLE;
1472 	info->io_size = 65536;
1473 
1474 	info->block_size = 2048;
1475 	info->total_blocks = volume->NumBlocks();
1476 	info->free_blocks = 0;
1477 
1478 	// Volume name
1479 	strlcpy(info->volume_name, volume->Name(), sizeof(info->volume_name));
1480 
1481 	// File system name
1482 	strlcpy(info->fsh_name, "cdda", sizeof(info->fsh_name));
1483 
1484 	return B_OK;
1485 }
1486 
1487 
1488 static status_t
1489 cdda_write_fs_stat(fs_volume* _volume, const struct fs_info* info, uint32 mask)
1490 {
1491 	Volume* volume = (Volume*)_volume->private_volume;
1492 	Locker locker(volume->Lock());
1493 
1494 	status_t status = B_BAD_VALUE;
1495 
1496 	if ((mask & FS_WRITE_FSINFO_NAME) != 0) {
1497 		status = volume->SetName(info->volume_name);
1498 		if (status == B_OK) {
1499 			// The volume had its name changed from outside the filesystem
1500 			// add-on. Disable CDDB lookups. Note this will usually mean that
1501 			// the user manually renamed the volume or that cddblinkd (or other
1502 			// program) did this so we do not want to do it again.
1503 			volume->DisableCDDBLookUps();
1504 		}
1505 	}
1506 
1507 	return status;
1508 }
1509 
1510 
1511 static status_t
1512 cdda_sync(fs_volume* _volume)
1513 {
1514 	TRACE(("cdda_sync: entry\n"));
1515 
1516 	return B_OK;
1517 }
1518 
1519 
1520 static status_t
1521 cdda_lookup(fs_volume* _volume, fs_vnode* _dir, const char* name, ino_t* _id)
1522 {
1523 	Volume* volume = (Volume*)_volume->private_volume;
1524 	status_t status;
1525 
1526 	TRACE(("cdda_lookup: entry dir %p, name '%s'\n", _dir, name));
1527 
1528 	Inode* directory = (Inode*)_dir->private_node;
1529 	if (!S_ISDIR(directory->Type()))
1530 		return B_NOT_A_DIRECTORY;
1531 
1532 	Locker _(volume->Lock());
1533 
1534 	Inode* inode = volume->Find(name);
1535 	if (inode == NULL)
1536 		return B_ENTRY_NOT_FOUND;
1537 
1538 	status = get_vnode(volume->FSVolume(), inode->ID(), NULL);
1539 	if (status < B_OK)
1540 		return status;
1541 
1542 	*_id = inode->ID();
1543 	return B_OK;
1544 }
1545 
1546 
1547 static status_t
1548 cdda_get_vnode_name(fs_volume* _volume, fs_vnode* _node, char* buffer,
1549 	size_t bufferSize)
1550 {
1551 	Volume* volume = (Volume*)_volume->private_volume;
1552 	Inode* inode = (Inode*)_node->private_node;
1553 
1554 	TRACE(("cdda_get_vnode_name(): inode = %p\n", inode));
1555 
1556 	Locker _(volume->Lock());
1557 	strlcpy(buffer, inode->Name(), bufferSize);
1558 	return B_OK;
1559 }
1560 
1561 
1562 static status_t
1563 cdda_get_vnode(fs_volume* _volume, ino_t id, fs_vnode* _node, int* _type,
1564 	uint32* _flags, bool reenter)
1565 {
1566 	Volume* volume = (Volume*)_volume->private_volume;
1567 	Inode* inode;
1568 
1569 	TRACE(("cdda_getvnode: asking for vnode 0x%Lx, r %d\n", id, reenter));
1570 
1571 	inode = volume->Find(id);
1572 	if (inode == NULL)
1573 		return B_ENTRY_NOT_FOUND;
1574 
1575 	_node->private_node = inode;
1576 	_node->ops = &gCDDAVnodeOps;
1577 	*_type = inode->Type();
1578 	*_flags = 0;
1579 	return B_OK;
1580 }
1581 
1582 
1583 static status_t
1584 cdda_put_vnode(fs_volume* _volume, fs_vnode* _node, bool reenter)
1585 {
1586 	return B_OK;
1587 }
1588 
1589 
1590 static status_t
1591 cdda_open(fs_volume* _volume, fs_vnode* _node, int openMode, void** _cookie)
1592 {
1593 	TRACE(("cdda_open(): node = %p, openMode = %d\n", _node, openMode));
1594 
1595 	file_cookie* cookie = (file_cookie*)malloc(sizeof(file_cookie));
1596 	if (cookie == NULL)
1597 		return B_NO_MEMORY;
1598 
1599 	TRACE(("  open cookie = %p\n", cookie));
1600 	cookie->open_mode = openMode;
1601 	cookie->buffer = NULL;
1602 
1603 	*_cookie = (void*)cookie;
1604 
1605 	return B_OK;
1606 }
1607 
1608 
1609 static status_t
1610 cdda_close(fs_volume* _volume, fs_vnode* _node, void* _cookie)
1611 {
1612 	return B_OK;
1613 }
1614 
1615 
1616 static status_t
1617 cdda_free_cookie(fs_volume* _volume, fs_vnode* _node, void* _cookie)
1618 {
1619 	file_cookie* cookie = (file_cookie*)_cookie;
1620 
1621 	TRACE(("cdda_freecookie: entry vnode %p, cookie %p\n", _node, _cookie));
1622 
1623 	free(cookie);
1624 	return B_OK;
1625 }
1626 
1627 
1628 static status_t
1629 cdda_fsync(fs_volume* _volume, fs_vnode* _node)
1630 {
1631 	return B_OK;
1632 }
1633 
1634 
1635 static status_t
1636 cdda_read(fs_volume* _volume, fs_vnode* _node, void* _cookie, off_t offset,
1637 	void* buffer, size_t* _length)
1638 {
1639 	file_cookie* cookie = (file_cookie*)_cookie;
1640 	Volume* volume = (Volume*)_volume->private_volume;
1641 	Inode* inode = (Inode*)_node->private_node;
1642 
1643 	TRACE(("cdda_read(vnode = %p, offset %Ld, length = %lu, mode = %d)\n",
1644 		_node, offset, *_length, cookie->open_mode));
1645 
1646 	if (S_ISDIR(inode->Type()))
1647 		return B_IS_A_DIRECTORY;
1648 	if (offset < 0)
1649 		return B_BAD_VALUE;
1650 
1651 	off_t maxSize = inode->Size() + sizeof(wav_header);
1652 	if (offset >= maxSize) {
1653 		*_length = 0;
1654 		return B_OK;
1655 	}
1656 
1657 	if (cookie->buffer == NULL) {
1658 		// TODO: move that to open() to make sure reading can't fail for this reason?
1659 		cookie->buffer = malloc(volume->BufferSize());
1660 		if (cookie->buffer == NULL)
1661 			return B_NO_MEMORY;
1662 
1663 		cookie->buffer_offset = -1;
1664 	}
1665 
1666 	size_t length = *_length;
1667 	if (offset + length > maxSize)
1668 		length = maxSize - offset;
1669 
1670 	status_t status = B_OK;
1671 	size_t bytesRead = 0;
1672 
1673 	if (offset < sizeof(wav_header)) {
1674 		// read fake WAV header
1675 		size_t size = sizeof(wav_header) - offset;
1676 		size = min_c(size, length);
1677 
1678 		if (user_memcpy(buffer, (uint8*)inode->WAVHeader() + offset, size)
1679 				< B_OK)
1680 			return B_BAD_ADDRESS;
1681 
1682 		buffer = (void*)((uint8*)buffer + size);
1683 		length -= size;
1684 		bytesRead += size;
1685 		offset = 0;
1686 	} else
1687 		offset -= sizeof(wav_header);
1688 
1689 	if (length > 0) {
1690 		// read actual CD data
1691 		offset += inode->StartFrame() * kFrameSize;
1692 
1693 		status = read_cdda_data(volume->Device(),
1694 			inode->StartFrame() + inode->FrameCount(), offset, buffer, length,
1695 			cookie->buffer_offset, cookie->buffer, volume->BufferSize());
1696 
1697 		bytesRead += length;
1698 	}
1699 	if (status == B_OK)
1700 		*_length = bytesRead;
1701 
1702 	return status;
1703 }
1704 
1705 
1706 static bool
1707 cdda_can_page(fs_volume* _volume, fs_vnode* _node, void* cookie)
1708 {
1709 	return false;
1710 }
1711 
1712 
1713 static status_t
1714 cdda_read_pages(fs_volume* _volume, fs_vnode* _node, void* cookie, off_t pos,
1715 	const iovec* vecs, size_t count, size_t* _numBytes)
1716 {
1717 	return B_NOT_ALLOWED;
1718 }
1719 
1720 
1721 static status_t
1722 cdda_write_pages(fs_volume* _volume, fs_vnode* _node, void* cookie, off_t pos,
1723 	const iovec* vecs, size_t count, size_t* _numBytes)
1724 {
1725 	return B_NOT_ALLOWED;
1726 }
1727 
1728 
1729 static status_t
1730 cdda_read_stat(fs_volume* _volume, fs_vnode* _node, struct stat* stat)
1731 {
1732 	Volume* volume = (Volume*)_volume->private_volume;
1733 	Inode* inode = (Inode*)_node->private_node;
1734 
1735 	TRACE(("cdda_read_stat: vnode %p (0x%Lx), stat %p\n", inode, inode->ID(), stat));
1736 
1737 	fill_stat_buffer(volume, inode, NULL, *stat);
1738 
1739 	return B_OK;
1740 }
1741 
1742 
1743 status_t
1744 cdda_rename(fs_volume* _volume, fs_vnode* _oldDir, const char* oldName,
1745 	fs_vnode* _newDir, const char* newName)
1746 {
1747 	if (_oldDir->private_node != _newDir->private_node)
1748 		return B_BAD_VALUE;
1749 
1750 	// we only have a single directory which simplifies things a bit :-)
1751 
1752 	Volume *volume = (Volume*)_volume->private_volume;
1753 	Locker _(volume->Lock());
1754 
1755 	Inode* inode = volume->Find(oldName);
1756 	if (inode == NULL)
1757 		return B_ENTRY_NOT_FOUND;
1758 
1759 	if (volume->Find(newName) != NULL)
1760 		return B_NAME_IN_USE;
1761 
1762 	status_t status = inode->SetName(newName);
1763 	if (status == B_OK) {
1764 		// One of the tracks had its name edited from outside the filesystem
1765 		// add-on. Disable CDDB lookups. Note this will usually mean that the
1766 		// user manually renamed a track or that cddblinkd (or other program)
1767 		// did this so we do not want to do it again.
1768 		volume->DisableCDDBLookUps();
1769 
1770 		notify_entry_moved(volume->ID(), volume->RootNode().ID(), oldName,
1771 			volume->RootNode().ID(), newName, inode->ID());
1772 	}
1773 
1774 	return status;
1775 }
1776 
1777 
1778 //	#pragma mark - directory functions
1779 
1780 
1781 static status_t
1782 cdda_open_dir(fs_volume* _volume, fs_vnode* _node, void** _cookie)
1783 {
1784 	Volume* volume = (Volume*)_volume->private_volume;
1785 
1786 	TRACE(("cdda_open_dir(): vnode = %p\n", _node));
1787 
1788 	Inode* inode = (Inode*)_node->private_node;
1789 	if (!S_ISDIR(inode->Type()))
1790 		return B_NOT_A_DIRECTORY;
1791 
1792 	if (inode != &volume->RootNode())
1793 		panic("pipefs: found directory that's not the root!");
1794 
1795 	dir_cookie* cookie = (dir_cookie*)malloc(sizeof(dir_cookie));
1796 	if (cookie == NULL)
1797 		return B_NO_MEMORY;
1798 
1799 	cookie->current = volume->FirstEntry();
1800 	cookie->state = ITERATION_STATE_BEGIN;
1801 
1802 	*_cookie = (void*)cookie;
1803 	return B_OK;
1804 }
1805 
1806 
1807 static status_t
1808 cdda_read_dir(fs_volume* _volume, fs_vnode* _node, void* _cookie,
1809 	struct dirent* buffer, size_t bufferSize, uint32* _num)
1810 {
1811 	Volume* volume = (Volume*)_volume->private_volume;
1812 	Inode* inode = (Inode*)_node->private_node;
1813 
1814 	TRACE(("cdda_read_dir: vnode %p, cookie %p, buffer = %p, bufferSize = %ld, num = %p\n", _node, _cookie, dirent, bufferSize,_num));
1815 
1816 	if ((Inode*)_node->private_node != &volume->RootNode())
1817 		return B_BAD_VALUE;
1818 
1819 	Locker _(volume->Lock());
1820 
1821 	dir_cookie* cookie = (dir_cookie*)_cookie;
1822 	Inode* childNode = NULL;
1823 	const char* name = NULL;
1824 	Inode* nextChildNode = NULL;
1825 	int nextState = cookie->state;
1826 	uint32 max = *_num;
1827 	uint32 count = 0;
1828 
1829 	while (count < max && bufferSize > sizeof(dirent)) {
1830 		switch (cookie->state) {
1831 			case ITERATION_STATE_DOT:
1832 				childNode = inode;
1833 				name = ".";
1834 				nextChildNode = volume->FirstEntry();
1835 				nextState = cookie->state + 1;
1836 				break;
1837 			case ITERATION_STATE_DOT_DOT:
1838 				childNode = inode; // parent of the root node is the root node
1839 				name = "..";
1840 				nextChildNode = volume->FirstEntry();
1841 				nextState = cookie->state + 1;
1842 				break;
1843 			default:
1844 				childNode = cookie->current;
1845 				if (childNode) {
1846 					name = childNode->Name();
1847 					nextChildNode = childNode->Next();
1848 				}
1849 				break;
1850 		}
1851 
1852 		if (childNode == NULL) {
1853 			// we're at the end of the directory
1854 			break;
1855 		}
1856 
1857 		buffer->d_dev = volume->FSVolume()->id;
1858 		buffer->d_ino = childNode->ID();
1859 		buffer->d_reclen = strlen(name) + sizeof(struct dirent);
1860 
1861 		if (buffer->d_reclen > bufferSize) {
1862 			if (count == 0)
1863 				return ENOBUFS;
1864 
1865 			break;
1866 		}
1867 
1868 		strcpy(buffer->d_name, name);
1869 
1870 		bufferSize -= buffer->d_reclen;
1871 		buffer = (struct dirent*)((uint8*)buffer + buffer->d_reclen);
1872 		count++;
1873 
1874 		cookie->current = nextChildNode;
1875 		cookie->state = nextState;
1876 	}
1877 
1878 	*_num = count;
1879 	return B_OK;
1880 }
1881 
1882 
1883 static status_t
1884 cdda_rewind_dir(fs_volume* _volume, fs_vnode* _node, void* _cookie)
1885 {
1886 	Volume* volume = (Volume*)_volume->private_volume;
1887 
1888 	dir_cookie* cookie = (dir_cookie*)_cookie;
1889 	cookie->current = volume->FirstEntry();
1890 	cookie->state = ITERATION_STATE_BEGIN;
1891 
1892 	return B_OK;
1893 }
1894 
1895 
1896 static status_t
1897 cdda_close_dir(fs_volume* _volume, fs_vnode* _node, void* _cookie)
1898 {
1899 	TRACE(("cdda_close: entry vnode %p, cookie %p\n", _node, _cookie));
1900 
1901 	return 0;
1902 }
1903 
1904 
1905 static status_t
1906 cdda_free_dir_cookie(fs_volume* _volume, fs_vnode* _node, void* _cookie)
1907 {
1908 	dir_cookie* cookie = (dir_cookie*)_cookie;
1909 
1910 	TRACE(("cdda_freecookie: entry vnode %p, cookie %p\n", _vnode, cookie));
1911 
1912 	free(cookie);
1913 	return 0;
1914 }
1915 
1916 
1917 //	#pragma mark - attribute functions
1918 
1919 
1920 static status_t
1921 cdda_open_attr_dir(fs_volume* _volume, fs_vnode* _node, void** _cookie)
1922 {
1923 	Volume* volume = (Volume*)_volume->private_volume;
1924 	Inode* inode = (Inode*)_node->private_node;
1925 
1926 	attr_cookie* cookie = new(std::nothrow) attr_cookie;
1927 	if (cookie == NULL)
1928 		return B_NO_MEMORY;
1929 
1930 	Locker _(volume->Lock());
1931 
1932 	inode->AddAttrCookie(cookie);
1933 	*_cookie = cookie;
1934 	return B_OK;
1935 }
1936 
1937 
1938 static status_t
1939 cdda_close_attr_dir(fs_volume* _volume, fs_vnode* _node, void* _cookie)
1940 {
1941 	return B_OK;
1942 }
1943 
1944 
1945 static status_t
1946 cdda_free_attr_dir_cookie(fs_volume* _volume, fs_vnode* _node, void* _cookie)
1947 {
1948 	Volume* volume = (Volume*)_volume->private_volume;
1949 	Inode* inode = (Inode*)_node->private_node;
1950 	attr_cookie* cookie = (attr_cookie*)_cookie;
1951 
1952 	Locker _(volume->Lock());
1953 
1954 	inode->RemoveAttrCookie(cookie);
1955 	delete cookie;
1956 	return B_OK;
1957 }
1958 
1959 
1960 static status_t
1961 cdda_rewind_attr_dir(fs_volume* _volume, fs_vnode* _node, void* _cookie)
1962 {
1963 	Volume* volume = (Volume*)_volume->private_volume;
1964 	Inode* inode = (Inode*)_node->private_node;
1965 	attr_cookie* cookie = (attr_cookie*)_cookie;
1966 
1967 	Locker _(volume->Lock());
1968 
1969 	inode->RewindAttrCookie(cookie);
1970 	return B_OK;
1971 }
1972 
1973 
1974 static status_t
1975 cdda_read_attr_dir(fs_volume* _volume, fs_vnode* _node, void* _cookie,
1976 	struct dirent* dirent, size_t bufferSize, uint32* _num)
1977 {
1978 	Volume* volume = (Volume*)_volume->private_volume;
1979 	Inode* inode = (Inode*)_node->private_node;
1980 	attr_cookie* cookie = (attr_cookie*)_cookie;
1981 
1982 	Locker _(volume->Lock());
1983 	Attribute* attribute = cookie->current;
1984 
1985 	if (attribute == NULL) {
1986 		*_num = 0;
1987 		return B_OK;
1988 	}
1989 
1990 	size_t length = strlcpy(dirent->d_name, attribute->Name(), bufferSize);
1991 	dirent->d_dev = volume->FSVolume()->id;
1992 	dirent->d_ino = inode->ID();
1993 	dirent->d_reclen = sizeof(struct dirent) + length;
1994 
1995 	cookie->current = attribute->GetDoublyLinkedListLink()->next;
1996 	*_num = 1;
1997 	return B_OK;
1998 }
1999 
2000 
2001 static status_t
2002 cdda_create_attr(fs_volume* _volume, fs_vnode* _node, const char* name,
2003 	uint32 type, int openMode, void** _cookie)
2004 {
2005 	Volume *volume = (Volume*)_volume->private_volume;
2006 	Inode *inode = (Inode*)_node->private_node;
2007 
2008 	Locker _(volume->Lock());
2009 
2010 	Attribute* attribute = inode->FindAttribute(name);
2011 	if (attribute == NULL) {
2012 		if (Attribute::IsProtectedNamespace(name))
2013 			return B_NOT_ALLOWED;
2014 		status_t status = inode->AddAttribute(name, type, true, NULL, 0);
2015 		if (status != B_OK)
2016 			return status;
2017 
2018 		notify_attribute_changed(volume->ID(), inode->ID(), name,
2019 			B_ATTR_CREATED);
2020 	} else if ((openMode & O_EXCL) == 0) {
2021 		if (attribute->IsProtectedNamespace())
2022 			return B_NOT_ALLOWED;
2023 		attribute->SetType(type);
2024 		if ((openMode & O_TRUNC) != 0)
2025 			attribute->Truncate();
2026 	} else
2027 		return B_FILE_EXISTS;
2028 
2029 	*_cookie = strdup(name);
2030 	if (*_cookie == NULL)
2031 		return B_NO_MEMORY;
2032 
2033 	return B_OK;
2034 }
2035 
2036 
2037 static status_t
2038 cdda_open_attr(fs_volume* _volume, fs_vnode* _node, const char* name,
2039 	int openMode, void** _cookie)
2040 {
2041 	Volume* volume = (Volume*)_volume->private_volume;
2042 	Inode* inode = (Inode*)_node->private_node;
2043 
2044 	Locker _(volume->Lock());
2045 
2046 	Attribute* attribute = inode->FindAttribute(name);
2047 	if (attribute == NULL)
2048 		return B_ENTRY_NOT_FOUND;
2049 
2050 	*_cookie = strdup(name);
2051 	if (*_cookie == NULL)
2052 		return B_NO_MEMORY;
2053 
2054 	return B_OK;
2055 }
2056 
2057 
2058 static status_t
2059 cdda_close_attr(fs_volume* _volume, fs_vnode* _node, void* cookie)
2060 {
2061 	return B_OK;
2062 }
2063 
2064 
2065 static status_t
2066 cdda_free_attr_cookie(fs_volume* _volume, fs_vnode* _node, void* cookie)
2067 {
2068 	free(cookie);
2069 	return B_OK;
2070 }
2071 
2072 
2073 static status_t
2074 cdda_read_attr(fs_volume* _volume, fs_vnode* _node, void* _cookie,
2075 	off_t offset, void* buffer, size_t* _length)
2076 {
2077 	Volume* volume = (Volume*)_volume->private_volume;
2078 	Inode* inode = (Inode*)_node->private_node;
2079 
2080 	Locker _(volume->Lock());
2081 
2082 	Attribute* attribute = inode->FindAttribute((const char*)_cookie);
2083 	if (attribute == NULL)
2084 		return B_ENTRY_NOT_FOUND;
2085 
2086 	return attribute->ReadAt(offset, (uint8*)buffer, _length);
2087 }
2088 
2089 
2090 static status_t
2091 cdda_write_attr(fs_volume* _volume, fs_vnode* _node, void* _cookie,
2092 	off_t offset, const void* buffer, size_t* _length)
2093 {
2094 	Volume* volume = (Volume*)_volume->private_volume;
2095 	Inode* inode = (Inode*)_node->private_node;
2096 
2097 	Locker _(volume->Lock());
2098 
2099 	Attribute* attribute = inode->FindAttribute((const char*)_cookie);
2100 	if (attribute == NULL)
2101 		return B_ENTRY_NOT_FOUND;
2102 
2103 	if (attribute->IsProtectedNamespace())
2104 		return B_NOT_ALLOWED;
2105 
2106 	status_t status = attribute->WriteAt(offset, (uint8*)buffer, _length);
2107 	if (status == B_OK) {
2108 		notify_attribute_changed(volume->ID(), inode->ID(), attribute->Name(),
2109 			B_ATTR_CHANGED);
2110 	}
2111 	return status;
2112 }
2113 
2114 
2115 static status_t
2116 cdda_read_attr_stat(fs_volume* _volume, fs_vnode* _node, void* _cookie,
2117 	struct stat* stat)
2118 {
2119 	Volume* volume = (Volume*)_volume->private_volume;
2120 	Inode* inode = (Inode*)_node->private_node;
2121 
2122 	Locker _(volume->Lock());
2123 
2124 	Attribute* attribute = inode->FindAttribute((const char*)_cookie);
2125 	if (attribute == NULL)
2126 		return B_ENTRY_NOT_FOUND;
2127 
2128 	fill_stat_buffer(volume, inode, attribute, *stat);
2129 	return B_OK;
2130 }
2131 
2132 
2133 static status_t
2134 cdda_write_attr_stat(fs_volume* _volume, fs_vnode* _node, void* cookie,
2135 	const struct stat* stat, int statMask)
2136 {
2137 	return EOPNOTSUPP;
2138 }
2139 
2140 
2141 static status_t
2142 cdda_remove_attr(fs_volume* _volume, fs_vnode* _node, const char* name)
2143 {
2144 	if (name == NULL)
2145 		return B_BAD_VALUE;
2146 
2147 	Volume* volume = (Volume*)_volume->private_volume;
2148 	Inode* inode = (Inode*)_node->private_node;
2149 
2150 	Locker _(volume->Lock());
2151 
2152 	status_t status = inode->RemoveAttribute(name, true);
2153 	if (status == B_OK) {
2154 		notify_attribute_changed(volume->ID(), inode->ID(), name,
2155 			B_ATTR_REMOVED);
2156 	}
2157 
2158 	return status;
2159 }
2160 
2161 
2162 fs_volume_ops gCDDAVolumeOps = {
2163 	cdda_unmount,
2164 	cdda_read_fs_stat,
2165 	cdda_write_fs_stat,
2166 	cdda_sync,
2167 	cdda_get_vnode,
2168 
2169 	// the other operations are not yet supported (indices, queries)
2170 	NULL,
2171 };
2172 
2173 fs_vnode_ops gCDDAVnodeOps = {
2174 	cdda_lookup,
2175 	cdda_get_vnode_name,
2176 	cdda_put_vnode,
2177 	NULL,	// fs_remove_vnode()
2178 
2179 	cdda_can_page,
2180 	cdda_read_pages,
2181 	cdda_write_pages,
2182 
2183 	NULL,	// io()
2184 	NULL,	// cancel_io()
2185 
2186 	NULL,	// get_file_map()
2187 
2188 	// common
2189 	NULL,	// fs_ioctl()
2190 	NULL,	// fs_set_flags()
2191 	NULL,	// fs_select()
2192 	NULL,	// fs_deselect()
2193 	cdda_fsync,
2194 
2195 	NULL,	// fs_read_link()
2196 	NULL,	// fs_symlink()
2197 	NULL,	// fs_link()
2198 	NULL,	// fs_unlink()
2199 	cdda_rename,
2200 
2201 	NULL,	// fs_access()
2202 	cdda_read_stat,
2203 	NULL,	// fs_write_stat()
2204 	NULL,	// fs_preallocate()
2205 
2206 	// file
2207 	NULL,	// fs_create()
2208 	cdda_open,
2209 	cdda_close,
2210 	cdda_free_cookie,
2211 	cdda_read,
2212 	NULL,	// fs_write()
2213 
2214 	// directory
2215 	NULL,	// fs_create_dir()
2216 	NULL,	// fs_remove_dir()
2217 	cdda_open_dir,
2218 	cdda_close_dir,
2219 	cdda_free_dir_cookie,
2220 	cdda_read_dir,
2221 	cdda_rewind_dir,
2222 
2223 	// attribute directory operations
2224 	cdda_open_attr_dir,
2225 	cdda_close_attr_dir,
2226 	cdda_free_attr_dir_cookie,
2227 	cdda_read_attr_dir,
2228 	cdda_rewind_attr_dir,
2229 
2230 	// attribute operations
2231 	cdda_create_attr,
2232 	cdda_open_attr,
2233 	cdda_close_attr,
2234 	cdda_free_attr_cookie,
2235 	cdda_read_attr,
2236 	cdda_write_attr,
2237 
2238 	cdda_read_attr_stat,
2239 	cdda_write_attr_stat,
2240 	NULL,	// fs_rename_attr()
2241 	cdda_remove_attr,
2242 
2243 	NULL,	// fs_create_special_node()
2244 };
2245 
2246 static file_system_module_info sCDDAFileSystem = {
2247 	{
2248 		"file_systems/cdda" B_CURRENT_FS_API_VERSION,
2249 		0,
2250 		NULL,
2251 	},
2252 
2253 	"cdda",					// short_name
2254 	"CDDA File System",		// pretty_name
2255 	0,	// DDM flags
2256 
2257 	cdda_identify_partition,
2258 	cdda_scan_partition,
2259 	cdda_free_identify_partition_cookie,
2260 	NULL,	// free_partition_content_cookie()
2261 
2262 	cdda_mount,
2263 
2264 	// all other functions are not supported
2265 	NULL,
2266 };
2267 
2268 module_info* modules[] = {
2269 	(module_info*)&sCDDAFileSystem,
2270 	NULL,
2271 };
2272