xref: /haiku/src/add-ons/kernel/file_systems/cdda/cdda.cpp (revision 24fb5ed32c27af1ec32afc42867c2681fef45b77)
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 
9 #include <KernelExport.h>
10 #include <device/scsi.h>
11 
12 #include <ctype.h>
13 #include <errno.h>
14 #include <stdlib.h>
15 #include <string.h>
16 
17 
18 struct cdtext_pack_data {
19 	uint8	id;
20 	uint8	track;
21 	uint8	number;
22 	uint8	character_position : 4;
23 	uint8	block_number : 3;
24 	uint8	double_byte : 1;
25 	char	text[12];
26 	uint8	crc[2];
27 } _PACKED;
28 
29 enum {
30 	kTrackID	= 0x80,
31 	kArtistID	= 0x81,
32 	kMessageID	= 0x85,
33 };
34 
35 static const uint32 kBufferSize = 16384;
36 static const uint32 kSenseSize = 1024;
37 
38 
39 //	#pragma mark - string functions
40 
41 
42 static char *
43 copy_string(const char *string)
44 {
45 	if (string == NULL || !string[0])
46 		return NULL;
47 
48 	return strdup(string);
49 }
50 
51 
52 static bool
53 is_garbage(char c)
54 {
55 	return isspace(c) || c == '-' || c == '/' || c == '\\';
56 }
57 
58 
59 static void
60 sanitize_string(char *&string)
61 {
62 	if (string == NULL)
63 		return;
64 
65 	// strip garbage at the start
66 
67 	uint32 length = strlen(string);
68 	uint32 garbage = 0;
69 	while (is_garbage(string[garbage])) {
70 		garbage++;
71 	}
72 
73 	length -= garbage;
74 	if (garbage)
75 		memmove(string, string + garbage, length + 1);
76 
77 	// strip garbage from the end
78 
79 	while (length > 1 && isspace(string[length - 1])) {
80 		string[--length] = '\0';
81 	}
82 
83 	if (!string[0]) {
84 		// free string if it's empty
85 		free(string);
86 		string = NULL;
87 	}
88 }
89 
90 
91 //! Finds the first occurrence of \a find in \a string, ignores case.
92 static char*
93 find_string(const char *string, const char *find)
94 {
95 	if (string == NULL || find == NULL)
96 		return NULL;
97 
98 	char first = tolower(find[0]);
99 	if (first == '\0')
100 		return (char *)string;
101 
102 	int32 findLength = strlen(find) - 1;
103 	find++;
104 
105 	for (; string[0]; string++) {
106 		if (tolower(string[0]) != first)
107 			continue;
108 		if (strncasecmp(string + 1, find, findLength) == 0)
109 			return (char *)string;
110 	}
111 
112 	return NULL;
113 }
114 
115 
116 static void
117 cut_string(char *string, const char *cut)
118 {
119 	if (string == NULL || cut == NULL)
120 		return;
121 
122 	char *found = find_string(string, cut);
123 	if (found != NULL) {
124 		uint32 foundLength = strlen(found);
125 		uint32 cutLength = strlen(cut);
126 		memmove(found, found + cutLength, foundLength + 1 - cutLength);
127 	}
128 }
129 
130 
131 static void
132 sanitize_album(cdtext &text)
133 {
134 	cut_string(text.album, text.artist);
135 	sanitize_string(text.album);
136 
137 	if (text.album != NULL && !strcasecmp(text.album, "My CD")) {
138 		// don't laugh, people really do that!
139 		free(text.album);
140 		text.album = NULL;
141 	}
142 
143 	if ((text.artist == NULL || text.artist[0] == '\0') && text.album != NULL) {
144 		// try to extract artist from album
145 		char *space = strstr(text.album, "  ");
146 		if (space != NULL) {
147 			space[0] = '\0';
148 			text.artist = text.album;
149 			text.album = copy_string(space + 2);
150 
151 			sanitize_string(text.artist);
152 			sanitize_string(text.album);
153 		}
154 	}
155 }
156 
157 
158 static void
159 sanitize_titles(cdtext &text)
160 {
161 	for (uint8 i = 0; i < text.track_count; i++) {
162 		cut_string(text.titles[i], "(Album Version)");
163 		sanitize_string(text.titles[i]);
164 		sanitize_string(text.artists[i]);
165 
166 		if (text.artists[i] != NULL && text.artist != NULL
167 			&& !strcasecmp(text.artists[i], text.artist)) {
168 			// if the title artist is the same as the main artist, remove it
169 			free(text.artists[i]);
170 			text.artists[i] = NULL;
171 		}
172 
173 		if (text.titles[i] != NULL && text.titles[i][0] == '\t' && i > 0)
174 			text.titles[i] = copy_string(text.titles[i - 1]);
175 	}
176 }
177 
178 
179 static bool
180 single_case(const char *string, bool &upper, bool &first)
181 {
182 	if (string == NULL)
183 		return true;
184 
185 	while (string[0]) {
186 		while (!isalpha(string[0])) {
187 			string++;
188 		}
189 
190 		if (first) {
191 			upper = isupper(string[0]) != 0;
192 			first = false;
193 		} else if ((isupper(string[0]) != 0) ^ upper)
194 			return false;
195 
196 		string++;
197 	}
198 
199 	return true;
200 }
201 
202 
203 static void
204 capitalize_string(char *string)
205 {
206 	if (string == NULL)
207 		return;
208 
209 	bool newWord = isalpha(string[0]) || isspace(string[0]);
210 	while (string[0]) {
211 		if (isalpha(string[0])) {
212 			if (newWord) {
213 				string[0] = toupper(string[0]);
214 				newWord = false;
215 			} else
216 				string[0] = tolower(string[0]);
217 		} else if (string[0] != '\'')
218 			newWord = true;
219 
220 		string++;
221 	}
222 }
223 
224 
225 static void
226 correct_case(cdtext &text)
227 {
228 	// check if all titles share a single case
229 	bool first = true;
230 	bool upper;
231 	if (!single_case(text.album, upper, first)
232 		|| !single_case(text.artist, upper, first))
233 		return;
234 
235 	for (int32 i = 0; i < text.track_count; i++) {
236 		if (!single_case(text.titles[i], upper, first)
237 			|| !single_case(text.artists[i], upper, first))
238 			return;
239 	}
240 
241 	// If we get here, everything has a single case; we fix that
242 	// and capitalize each word
243 
244 	capitalize_string(text.album);
245 	capitalize_string(text.artist);
246 	for (int32 i = 0; i < text.track_count; i++) {
247 		capitalize_string(text.titles[i]);
248 		capitalize_string(text.artists[i]);
249 	}
250 }
251 
252 
253 //	#pragma mark - CD-Text
254 
255 
256 cdtext::cdtext()
257 	:
258 	artist(NULL),
259 	album(NULL),
260 	genre(NULL),
261 	track_count(0)
262 {
263 	memset(titles, 0, sizeof(titles));
264 	memset(artists, 0, sizeof(artists));
265 }
266 
267 
268 cdtext::~cdtext()
269 {
270 	free(album);
271 	free(artist);
272 	free(genre);
273 
274 	for (uint8 i = 0; i < track_count; i++) {
275 		free(titles[i]);
276 		free(artists[i]);
277 	}
278 }
279 
280 
281 static bool
282 is_string_id(uint8 id)
283 {
284 	return id >= kTrackID && id <= kMessageID;
285 }
286 
287 
288 /*!	Parses a \a pack data into the provided text buffer; the corresponding
289 	track number will be left in \a track, and the type of the data in \a id.
290 	The pack data is explained in SCSI MMC-3.
291 
292 	\a id, \a track, and \a state must stay constant between calls to this
293 	function. \a state must be initialized to zero for the first call.
294 */
295 static bool
296 parse_pack_data(cdtext_pack_data *&pack, uint32 &packLeft,
297 	cdtext_pack_data *&lastPack, uint8 &id, uint8 &track, uint8 &state,
298 	char *buffer, size_t &length)
299 {
300 	if (packLeft < sizeof(cdtext_pack_data))
301 		return false;
302 
303 	uint8 number = pack->number;
304 	size_t size = length;
305 
306 	if (state != 0) {
307 		// we had a terminated string and a missing track
308 		track++;
309 
310 		memcpy(buffer, lastPack->text + state, 12 - state);
311 		if (pack->track - track == 1)
312 			state = 0;
313 		else
314 			state += strnlen(buffer, 12 - state);
315 		return true;
316 	}
317 
318 	id = pack->id;
319 	track = pack->track;
320 	buffer[0] = '\0';
321 	length = 0;
322 
323 	size_t position = pack->character_position;
324 	if (position > 0 && lastPack != NULL) {
325 		memcpy(buffer, &lastPack->text[12 - position], position);
326 		length = position;
327 	}
328 
329 	while (id == pack->id && track == pack->track) {
330 #if 0
331 		dprintf("%u.%u.%u, %u.%u.%u, ", pack->id, pack->track, pack->number,
332 			pack->double_byte, pack->block_number, pack->character_position);
333 		for (int32 i = 0; i < 12; i++) {
334 			if (isprint(pack->text[i]))
335 				dprintf("%c", pack->text[i]);
336 			else
337 				dprintf("-");
338 		}
339 		dprintf("\n");
340 #endif
341 		if (is_string_id(id)) {
342 			// TODO: support double byte characters
343 			if (length + 12 < size) {
344 				memcpy(buffer + length, pack->text, 12);
345 				length += 12;
346 			}
347 		}
348 
349 		packLeft -= sizeof(cdtext_pack_data);
350 		if (packLeft < sizeof(cdtext_pack_data))
351 			return false;
352 
353 		lastPack = pack;
354 		number++;
355 		pack++;
356 
357 		if (pack->number != number)
358 			return false;
359 	}
360 
361 	if (id == pack->id) {
362 		length -= pack->character_position;
363 		if (length >= size)
364 			length = size - 1;
365 		buffer[length] = '\0';
366 
367 		if (pack->track > lastPack->track + 1) {
368 			// there is a missing track
369 			for (int32 i = 0; i < 12; i++) {
370 				if (lastPack->text[i] == '\0') {
371 					state = i + (lastPack->double_byte ? 2 : 1);
372 					break;
373 				}
374 			}
375 		}
376 	}
377 
378 	// TODO: convert text to UTF-8
379 	return true;
380 }
381 
382 
383 static void
384 dump_cdtext(cdtext &text)
385 {
386 	if (text.album)
387 		dprintf("Album:    \"%s\"\n", text.album);
388 	if (text.artist)
389 		dprintf("Artist:   \"%s\"\n", text.artist);
390 	for (uint8 i = 0; i < text.track_count; i++) {
391 		dprintf("Track %02u: \"%s\"%s%s%s\n", i + 1, text.titles[i],
392 			text.artists[i] ? " (" : "", text.artists[i] ? text.artists[i] : "",
393 			text.artists[i] ? ")" : "");
394 	}
395 }
396 
397 
398 static void
399 dump_toc(scsi_toc_toc *toc)
400 {
401 	int32 numTracks = toc->last_track + 1 - toc->first_track;
402 
403 	for (int32 i = 0; i < numTracks; i++) {
404 		scsi_toc_track& track = toc->tracks[i];
405 		scsi_cd_msf& next = toc->tracks[i + 1].start.time;
406 			// the last track is always lead-out
407 		scsi_cd_msf& start = toc->tracks[i].start.time;
408 		scsi_cd_msf length;
409 
410 		uint64 diff = next.minute * kFramesPerMinute
411 			+ next.second * kFramesPerSecond + next.frame
412 			- start.minute * kFramesPerMinute
413 			- start.second * kFramesPerSecond - start.frame;
414 		length.minute = diff / kFramesPerMinute;
415 		length.second = (diff % kFramesPerMinute) / kFramesPerSecond;
416 		length.frame = diff % kFramesPerSecond;
417 
418 		dprintf("%02u. %02u:%02u.%02u (length %02u:%02u.%02u)\n",
419 			track.track_number, start.minute, start.second, start.frame,
420 			length.minute, length.second, length.frame);
421 	}
422 }
423 
424 
425 static status_t
426 read_frames(int fd, off_t firstFrame, uint8 *buffer, size_t count)
427 {
428 	size_t framesLeft = count;
429 
430 	while (framesLeft > 0) {
431 		scsi_read_cd read;
432 		read.start_m = firstFrame / kFramesPerMinute;
433 		read.start_s = (firstFrame / kFramesPerSecond) % 60;
434 		read.start_f = firstFrame % kFramesPerSecond;
435 
436 		read.length_m = count / kFramesPerMinute;
437 		read.length_s = (count / kFramesPerSecond) % 60;
438 		read.length_f = count % kFramesPerSecond;
439 
440 		read.buffer_length = count * kFrameSize;
441 		read.buffer = (char *)buffer;
442 		read.play = false;
443 
444 		if (ioctl(fd, B_SCSI_READ_CD, &read) < 0) {
445 			// drive couldn't read data - try again to read with a smaller block size
446 			if (count == 1)
447 				return errno;
448 
449 			if (count >= 32)
450 				count = 8;
451 			else
452 				count = 1;
453 			continue;
454 		}
455 
456 		buffer += count * kFrameSize;
457 		framesLeft -= count;
458 		firstFrame += count;
459 	}
460 
461 	return B_OK;
462 }
463 
464 
465 static status_t
466 read_table_of_contents(int fd, uint32 track, uint8 format, uint8 *buffer,
467 	size_t bufferSize)
468 {
469 	raw_device_command raw;
470 	uint8 *senseData = (uint8 *)malloc(kSenseSize);
471 	if (senseData == NULL)
472 		return B_NO_MEMORY;
473 
474 	memset(&raw, 0, sizeof(raw_device_command));
475 	memset(senseData, 0, kSenseSize);
476 	memset(buffer, 0, bufferSize);
477 
478 	scsi_cmd_read_toc &toc = *(scsi_cmd_read_toc*)&raw.command;
479 	toc.opcode = SCSI_OP_READ_TOC;
480 	toc.time = 1;
481 	toc.format = format;
482 	toc.track = track;
483 	toc.allocation_length = B_HOST_TO_BENDIAN_INT16(bufferSize);
484 
485 	raw.command_length = 10;
486 	raw.flags = B_RAW_DEVICE_DATA_IN | B_RAW_DEVICE_REPORT_RESIDUAL
487 		| B_RAW_DEVICE_SHORT_READ_VALID;
488 	raw.scsi_status = 0;
489 	raw.cam_status = 0;
490 	raw.data = buffer;
491 	raw.data_length = bufferSize;
492 	raw.timeout = 10000000LL;	// 10 secs
493 	raw.sense_data = senseData;
494 	raw.sense_data_length = sizeof(kSenseSize);
495 
496 	if (ioctl(fd, B_RAW_DEVICE_COMMAND, &raw) == 0
497 		&& raw.scsi_status == 0 && raw.cam_status == 1) {
498 		free(senseData);
499 		return B_OK;
500 	}
501 
502 	free(senseData);
503 	return B_ERROR;
504 }
505 
506 
507 //	#pragma mark - exported functions
508 
509 
510 status_t
511 read_cdtext(int fd, struct cdtext &cdtext)
512 {
513 	uint8 *buffer = (uint8 *)malloc(kBufferSize);
514 	if (buffer == NULL)
515 		return B_NO_MEMORY;
516 
517 	// do it twice, just in case...
518 	// (at least my CD-ROM sometimes returned broken data on first try)
519 	read_table_of_contents(fd, 1, SCSI_TOC_FORMAT_CD_TEXT, buffer,
520 		kBufferSize);
521 	if (read_table_of_contents(fd, 1, SCSI_TOC_FORMAT_CD_TEXT, buffer,
522 			kBufferSize) != B_OK) {
523 		free(buffer);
524 		return B_ERROR;
525 	}
526 
527 	scsi_toc_general *header = (scsi_toc_general *)buffer;
528 
529 	size_t packLength = B_BENDIAN_TO_HOST_INT16(header->data_length) - 2;
530 	cdtext_pack_data *pack = (cdtext_pack_data *)(header + 1);
531 	cdtext_pack_data *lastPack = NULL;
532 	uint8 state = 0;
533 	uint8 track = 0;
534 	uint8 id = 0;
535 	char text[256];
536 
537 	while (true) {
538 		size_t length = sizeof(text);
539 
540 		if (!parse_pack_data(pack, packLength, lastPack, id, track,
541 				state, text, length))
542 			break;
543 
544 		switch (id) {
545 			case kTrackID:
546 				if (track == 0) {
547 					if (cdtext.album == NULL)
548 						cdtext.album = copy_string(text);
549 				} else if (track <= kMaxTracks) {
550 					if (cdtext.titles[track - 1] == NULL)
551 						cdtext.titles[track - 1] = copy_string(text);
552 					if (track > cdtext.track_count)
553 						cdtext.track_count = track;
554 				}
555 				break;
556 
557 			case kArtistID:
558 				if (track == 0) {
559 					if (cdtext.artist == NULL)
560 						cdtext.artist = copy_string(text);
561 				} else if (track <= kMaxTracks) {
562 					if (cdtext.artists[track - 1] == NULL)
563 						cdtext.artists[track - 1] = copy_string(text);
564 				}
565 				break;
566 
567 			default:
568 				if (is_string_id(id))
569 					dprintf("UNKNOWN %u: \"%s\"\n", id, text);
570 				break;
571 		}
572 	}
573 
574 	free(buffer);
575 
576 	if (cdtext.artist == NULL || cdtext.album == NULL)
577 		return B_ERROR;
578 
579 	for (int i = 0; i < cdtext.track_count; i++) {
580 		if (cdtext.titles[i] == NULL)
581 			return B_ERROR;
582 	}
583 
584 	sanitize_string(cdtext.artist);
585 	sanitize_album(cdtext);
586 	sanitize_titles(cdtext);
587 	correct_case(cdtext);
588 
589 	dump_cdtext(cdtext);
590 	return B_OK;
591 }
592 
593 
594 status_t
595 read_table_of_contents(int fd, scsi_toc_toc *toc, size_t length)
596 {
597 	status_t status = read_table_of_contents(fd, 1, SCSI_TOC_FORMAT_TOC,
598 		(uint8*)toc, length);
599 	if (status < B_OK)
600 		return status;
601 
602 	// make sure the values in the TOC make sense
603 
604 	int32 lastTrack = toc->last_track + 1 - toc->first_track;
605 	size_t dataLength = B_BENDIAN_TO_HOST_INT16(toc->data_length) + 2;
606 	if (dataLength < sizeof(scsi_toc_toc) || lastTrack <= 0)
607 		return B_BAD_DATA;
608 
609 	if (length > dataLength)
610 		length = dataLength;
611 
612 	length -= sizeof(scsi_toc_general);
613 
614 	if (lastTrack * sizeof(scsi_toc_track) > length)
615 		toc->last_track = length / sizeof(scsi_toc_track) + toc->first_track;
616 
617 	dump_toc(toc);
618 	return B_OK;
619 }
620 
621 
622 status_t
623 read_cdda_data(int fd, off_t endFrame, off_t offset, void *data, size_t length,
624 	off_t bufferOffset, void *buffer, size_t bufferSize)
625 {
626 	if (bufferOffset >= 0 && bufferOffset <= offset + length
627 		&& bufferOffset + bufferSize > offset) {
628 		if (offset >= bufferOffset) {
629 			// buffer reaches into the beginning of the request
630 			off_t dataOffset = offset - bufferOffset;
631 			size_t bytes = min_c(bufferSize - dataOffset, length);
632 			if (user_memcpy(data, (uint8 *)buffer + dataOffset, bytes) < B_OK)
633 				return B_BAD_ADDRESS;
634 
635 			data = (void *)((uint8 *)data + bytes);
636 			length -= bytes;
637 			offset += bytes;
638 		} else if (offset < bufferOffset
639 			&& offset + length < bufferOffset + bufferSize) {
640 			// buffer overlaps at the end of the request
641 			off_t dataOffset = bufferOffset - offset;
642 			size_t bytes = length - dataOffset;
643 			if (user_memcpy((uint8 *)data + dataOffset, buffer, bytes) < B_OK)
644 				return B_BAD_ADDRESS;
645 
646 			length -= bytes;
647 		}
648 		// we don't handle the case where we would need to split the request
649 	}
650 
651 	while (length > 0) {
652 		off_t frame = offset / kFrameSize;
653 		uint32 count = bufferSize / kFrameSize;
654 		if (frame + count > endFrame)
655 			count = endFrame - frame;
656 
657 		status_t status = read_frames(fd, frame, (uint8 *)buffer, count);
658 		if (status < B_OK)
659 			return status;
660 
661 		off_t dataOffset = offset % kFrameSize;
662 		size_t bytes = bufferSize - dataOffset;
663 		if (bytes > length)
664 			bytes = length;
665 
666 		if (user_memcpy(data, (uint8 *)buffer + dataOffset, bytes) < B_OK)
667 			return B_BAD_ADDRESS;
668 
669 		data = (void *)((uint8 *)data + bytes);
670 		length -= bytes;
671 		offset += bytes;
672 	}
673 
674 	return B_OK;
675 }
676