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