xref: /haiku/src/add-ons/kernel/file_systems/cdda/cdda.cpp (revision 1daacc6b45d3b52a7cdc65752409aa4339ca4839)
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 static bool
289 parse_pack_data(cdtext_pack_data *&pack, uint32 &packLeft,
290 	cdtext_pack_data *&lastPack, uint8 &id, uint8 &track, uint8 &state,
291 	char *buffer, size_t &length)
292 {
293 	if (packLeft < sizeof(cdtext_pack_data))
294 		return false;
295 
296 	uint8 number = pack->number;
297 	size_t size = length;
298 
299 	if (state != 0) {
300 		// we had a terminated string and a missing track
301 		track++;
302 		memcpy(buffer, lastPack->text + state, 12 - state);
303 		if (pack->track - track == 1)
304 			state = 0;
305 		else
306 			state += strnlen(buffer, 12 - state);
307 		return true;
308 	}
309 
310 	id = pack->id;
311 	track = pack->track;
312 	buffer[0] = '\0';
313 	length = 0;
314 
315 	size_t position = pack->character_position;
316 	if (position > 0 && lastPack != NULL) {
317 		memcpy(buffer, &lastPack->text[12 - position], position);
318 		length = position;
319 	}
320 
321 	while (id == pack->id && track == pack->track) {
322 #if 1
323 		dprintf("%u.%u.%u, %u.%u.%u, ", pack->id, pack->track, pack->number,
324 			pack->double_byte, pack->block_number, pack->character_position);
325 		for (int32 i = 0; i < 12; i++) {
326 			if (isprint(pack->text[i]))
327 				dprintf("%c", pack->text[i]);
328 		}
329 		dprintf("\n");
330 #endif
331 		if (is_string_id(id)) {
332 			// TODO: support double byte characters
333 			if (length + 12 < size) {
334 				memcpy(buffer + length, pack->text, 12);
335 				length += 12;
336 			}
337 		}
338 
339 		packLeft -= sizeof(cdtext_pack_data);
340 		if (packLeft < sizeof(cdtext_pack_data))
341 			return false;
342 
343 		lastPack = pack;
344 		number++;
345 		pack++;
346 
347 		if (pack->number != number)
348 			return false;
349 	}
350 
351 	if (id == pack->id) {
352 		length -= pack->character_position;
353 		if (length >= size)
354 			length = size - 1;
355 		buffer[length] = '\0';
356 
357 		if (pack->track > lastPack->track + 1) {
358 			// there is a missing track
359 			for (int32 i = 0; i < 12; i++) {
360 				if (lastPack->text[i] == '\0') {
361 					state = i + (lastPack->double_byte ? 2 : 1);
362 					break;
363 				}
364 			}
365 		}
366 	}
367 
368 	// TODO: convert text to UTF-8
369 	return true;
370 }
371 
372 
373 static void
374 dump_cdtext(cdtext &text)
375 {
376 	if (text.album)
377 		dprintf("Album:    \"%s\"\n", text.album);
378 	if (text.artist)
379 		dprintf("Artist:   \"%s\"\n", text.artist);
380 	for (uint8 i = 0; i < text.track_count; i++) {
381 		dprintf("Track %02u: \"%s\"%s%s%s\n", i + 1, text.titles[i],
382 			text.artists[i] ? " (" : "", text.artists[i] ? text.artists[i] : "",
383 			text.artists[i] ? ")" : "");
384 	}
385 }
386 
387 
388 static void
389 dump_toc(scsi_toc_toc *toc)
390 {
391 	int32 numTracks = toc->last_track + 1 - toc->first_track;
392 
393 	for (int32 i = 0; i < numTracks; i++) {
394 		scsi_toc_track& track = toc->tracks[i];
395 		scsi_cd_msf& next = toc->tracks[i + 1].start.time;
396 			// the last track is always lead-out
397 		scsi_cd_msf& start = toc->tracks[i].start.time;
398 		scsi_cd_msf length;
399 
400 		uint64 diff = next.minute * kFramesPerMinute
401 			+ next.second * kFramesPerSecond + next.frame
402 			- start.minute * kFramesPerMinute
403 			- start.second * kFramesPerSecond - start.frame;
404 		length.minute = diff / kFramesPerMinute;
405 		length.second = (diff % kFramesPerMinute) / kFramesPerSecond;
406 		length.frame = diff % kFramesPerSecond;
407 
408 		dprintf("%02u. %02u:%02u.%02u (length %02u:%02u.%02u)\n",
409 			track.track_number, start.minute, start.second, start.frame,
410 			length.minute, length.second, length.frame);
411 	}
412 }
413 
414 
415 static status_t
416 read_frames(int fd, off_t firstFrame, uint8 *buffer, size_t count)
417 {
418 	size_t framesLeft = count;
419 
420 	while (framesLeft > 0) {
421 		scsi_read_cd read;
422 		read.start_m = firstFrame / kFramesPerMinute;
423 		read.start_s = (firstFrame / kFramesPerSecond) % 60;
424 		read.start_f = firstFrame % kFramesPerSecond;
425 
426 		read.length_m = count / kFramesPerMinute;
427 		read.length_s = (count / kFramesPerSecond) % 60;
428 		read.length_f = count % kFramesPerSecond;
429 
430 		read.buffer_length = count * kFrameSize;
431 		read.buffer = (char *)buffer;
432 		read.play = false;
433 
434 		if (ioctl(fd, B_SCSI_READ_CD, &read) < 0) {
435 			// drive couldn't read data - try again to read with a smaller block size
436 			if (count == 1)
437 				return errno;
438 
439 			if (count >= 32)
440 				count = 8;
441 			else
442 				count = 1;
443 			continue;
444 		}
445 
446 		buffer += count * kFrameSize;
447 		framesLeft -= count;
448 		firstFrame += count;
449 	}
450 
451 	return B_OK;
452 }
453 
454 
455 static status_t
456 read_table_of_contents(int fd, uint32 track, uint8 format, uint8 *buffer,
457 	size_t bufferSize)
458 {
459 	raw_device_command raw;
460 	uint8 *senseData = (uint8 *)malloc(kSenseSize);
461 	if (senseData == NULL)
462 		return B_NO_MEMORY;
463 
464 	memset(&raw, 0, sizeof(raw_device_command));
465 	memset(senseData, 0, kSenseSize);
466 	memset(buffer, 0, bufferSize);
467 
468 	scsi_cmd_read_toc &toc = *(scsi_cmd_read_toc*)&raw.command;
469 	toc.opcode = SCSI_OP_READ_TOC;
470 	toc.time = 1;
471 	toc.format = format;
472 	toc.track = track;
473 	toc.allocation_length = B_HOST_TO_BENDIAN_INT16(bufferSize);
474 
475 	raw.command_length = 10;
476 	raw.flags = B_RAW_DEVICE_DATA_IN | B_RAW_DEVICE_REPORT_RESIDUAL
477 		| B_RAW_DEVICE_SHORT_READ_VALID;
478 	raw.scsi_status = 0;
479 	raw.cam_status = 0;
480 	raw.data = buffer;
481 	raw.data_length = bufferSize;
482 	raw.timeout = 10000000LL;	// 10 secs
483 	raw.sense_data = senseData;
484 	raw.sense_data_length = sizeof(kSenseSize);
485 
486 	if (ioctl(fd, B_RAW_DEVICE_COMMAND, &raw) == 0
487 		&& raw.scsi_status == 0 && raw.cam_status == 1) {
488 		free(senseData);
489 		return B_OK;
490 	}
491 
492 	free(senseData);
493 	return B_ERROR;
494 }
495 
496 
497 //	#pragma mark - exported functions
498 
499 
500 status_t
501 read_cdtext(int fd, struct cdtext &cdtext)
502 {
503 	uint8 *buffer = (uint8 *)malloc(kBufferSize);
504 	if (buffer == NULL)
505 		return B_NO_MEMORY;
506 
507 	// do it twice, just in case...
508 	// (at least my CD-ROM sometimes returned broken data on first try)
509 	read_table_of_contents(fd, 1, SCSI_TOC_FORMAT_CD_TEXT, buffer,
510 		kBufferSize);
511 	if (read_table_of_contents(fd, 1, SCSI_TOC_FORMAT_CD_TEXT, buffer,
512 			kBufferSize) != B_OK) {
513 		free(buffer);
514 		return B_ERROR;
515 	}
516 
517 	scsi_toc_general *header = (scsi_toc_general *)buffer;
518 
519 	size_t packLength = B_BENDIAN_TO_HOST_INT16(header->data_length) - 2;
520 	cdtext_pack_data *pack = (cdtext_pack_data *)(header + 1);
521 	cdtext_pack_data *lastPack = NULL;
522 	uint8 state = 0;
523 	char text[256];
524 
525 	while (true) {
526 		size_t length = sizeof(text);
527 		uint8 id = 0, track = 0;
528 
529 		if (!parse_pack_data(pack, packLength, lastPack, id, track,
530 				state, text, length))
531 			break;
532 
533 		switch (id) {
534 			case kTrackID:
535 				if (track == 0) {
536 					if (cdtext.album == NULL)
537 						cdtext.album = copy_string(text);
538 				} else if (track <= kMaxTracks) {
539 					if (cdtext.titles[track - 1] == NULL)
540 						cdtext.titles[track - 1] = copy_string(text);
541 					if (track > cdtext.track_count)
542 						cdtext.track_count = track;
543 				}
544 				break;
545 
546 			case kArtistID:
547 				if (track == 0) {
548 					if (cdtext.artist == NULL)
549 						cdtext.artist = copy_string(text);
550 				} else if (track <= kMaxTracks) {
551 					if (cdtext.artists[track - 1] == NULL)
552 						cdtext.artists[track - 1] = copy_string(text);
553 				}
554 				break;
555 
556 			default:
557 				if (is_string_id(id))
558 					dprintf("UNKNOWN %u: \"%s\"\n", id, text);
559 				break;
560 		}
561 	}
562 
563 	free(buffer);
564 
565 	if (cdtext.artist == NULL || cdtext.album == NULL)
566 		return B_ERROR;
567 
568 	for (int i = 0; i < cdtext.track_count; i++) {
569 		if (cdtext.titles[i] == NULL)
570 			return B_ERROR;
571 	}
572 
573 	sanitize_string(cdtext.artist);
574 	sanitize_album(cdtext);
575 	sanitize_titles(cdtext);
576 	correct_case(cdtext);
577 
578 	dump_cdtext(cdtext);
579 	return B_OK;
580 }
581 
582 
583 status_t
584 read_table_of_contents(int fd, scsi_toc_toc *toc, size_t length)
585 {
586 	status_t status = read_table_of_contents(fd, 1, SCSI_TOC_FORMAT_TOC,
587 		(uint8*)toc, length);
588 	if (status < B_OK)
589 		return status;
590 
591 	// make sure the values in the TOC make sense
592 
593 	int32 lastTrack = toc->last_track + 1 - toc->first_track;
594 	size_t dataLength = B_BENDIAN_TO_HOST_INT16(toc->data_length) + 2;
595 	if (dataLength < sizeof(scsi_toc_toc) || lastTrack <= 0)
596 		return B_BAD_DATA;
597 
598 	if (length > dataLength)
599 		length = dataLength;
600 
601 	length -= sizeof(scsi_toc_general);
602 
603 	if (lastTrack * sizeof(scsi_toc_track) > length)
604 		toc->last_track = length / sizeof(scsi_toc_track) + toc->first_track;
605 
606 	dump_toc(toc);
607 	return B_OK;
608 }
609 
610 
611 status_t
612 read_cdda_data(int fd, off_t endFrame, off_t offset, void *data, size_t length,
613 	off_t bufferOffset, void *buffer, size_t bufferSize)
614 {
615 	if (bufferOffset >= 0 && bufferOffset <= offset + length
616 		&& bufferOffset + bufferSize > offset) {
617 		if (offset >= bufferOffset) {
618 			// buffer reaches into the beginning of the request
619 			off_t dataOffset = offset - bufferOffset;
620 			size_t bytes = min_c(bufferSize - dataOffset, length);
621 			if (user_memcpy(data, (uint8 *)buffer + dataOffset, bytes) < B_OK)
622 				return B_BAD_ADDRESS;
623 
624 			data = (void *)((uint8 *)data + bytes);
625 			length -= bytes;
626 			offset += bytes;
627 		} else if (offset < bufferOffset
628 			&& offset + length < bufferOffset + bufferSize) {
629 			// buffer overlaps at the end of the request
630 			off_t dataOffset = bufferOffset - offset;
631 			size_t bytes = length - dataOffset;
632 			if (user_memcpy((uint8 *)data + dataOffset, buffer, bytes) < B_OK)
633 				return B_BAD_ADDRESS;
634 
635 			length -= bytes;
636 		}
637 		// we don't handle the case where we would need to split the request
638 	}
639 
640 	while (length > 0) {
641 		off_t frame = offset / kFrameSize;
642 		uint32 count = bufferSize / kFrameSize;
643 		if (frame + count > endFrame)
644 			count = endFrame - frame;
645 
646 		status_t status = read_frames(fd, frame, (uint8 *)buffer, count);
647 		if (status < B_OK)
648 			return status;
649 
650 		off_t dataOffset = offset % kFrameSize;
651 		size_t bytes = bufferSize - dataOffset;
652 		if (bytes > length)
653 			bytes = length;
654 
655 		if (user_memcpy(data, (uint8 *)buffer + dataOffset, bytes) < B_OK)
656 			return B_BAD_ADDRESS;
657 
658 		data = (void *)((uint8 *)data + bytes);
659 		length -= bytes;
660 		offset += bytes;
661 	}
662 
663 	return B_OK;
664 }
665