1 /* 2 * Copyright 2007, 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, 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] == NULL) && 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, track; 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 sanitize_string(cdtext.artist); 566 sanitize_album(cdtext); 567 sanitize_titles(cdtext); 568 correct_case(cdtext); 569 570 dump_cdtext(cdtext); 571 return B_OK; 572 } 573 574 575 status_t 576 read_table_of_contents(int fd, scsi_toc_toc *toc, size_t length) 577 { 578 status_t status = read_table_of_contents(fd, 1, SCSI_TOC_FORMAT_TOC, 579 (uint8*)toc, length); 580 if (status < B_OK) 581 return status; 582 583 int32 lastTrack = toc->last_track + 1 - toc->first_track; 584 size_t dataLength = B_BENDIAN_TO_HOST_INT16(toc->data_length) + 2; 585 if (dataLength < sizeof(scsi_toc_toc) 586 || lastTrack <= 0) 587 return B_BAD_DATA; 588 589 // make sure the values in the TOC make sense 590 591 if (length > dataLength) 592 length = dataLength; 593 594 length -= sizeof(scsi_toc_general); 595 596 if (lastTrack * sizeof(scsi_toc_track) > length) 597 toc->last_track = length / sizeof(scsi_toc_track) + toc->first_track; 598 599 dump_toc(toc); 600 return B_OK; 601 } 602 603 604 status_t 605 read_cdda_data(int fd, off_t offset, void *data, size_t length, 606 off_t bufferOffset, void *buffer, size_t bufferSize) 607 { 608 if (bufferOffset >= 0 && bufferOffset <= offset + length 609 && bufferOffset + bufferSize > offset) { 610 if (offset >= bufferOffset) { 611 // buffer reaches into the beginning of the request 612 off_t dataOffset = offset - bufferOffset; 613 size_t bytes = min_c(bufferSize - dataOffset, length); 614 if (user_memcpy(data, (uint8 *)buffer + dataOffset, bytes) < B_OK) 615 return B_BAD_ADDRESS; 616 617 data = (void *)((uint8 *)data + bytes); 618 length -= bytes; 619 offset += bytes; 620 } else if (offset < bufferOffset 621 && offset + length < bufferOffset + bufferSize) { 622 // buffer overlaps at the end of the request 623 off_t dataOffset = bufferOffset - offset; 624 size_t bytes = length - dataOffset; 625 if (user_memcpy((uint8 *)data + dataOffset, buffer, bytes) < B_OK) 626 return B_BAD_ADDRESS; 627 628 length -= bytes; 629 } 630 // we don't handle the case we would need to split the request 631 } 632 633 while (length > 0) { 634 off_t frame = offset / kFrameSize; 635 uint32 count = bufferSize / kFrameSize; 636 637 status_t status = read_frames(fd, frame, (uint8 *)buffer, count); 638 if (status < B_OK) 639 return status; 640 641 off_t dataOffset = offset % kFrameSize; 642 size_t bytes = bufferSize - dataOffset; 643 if (bytes > length) 644 bytes = length; 645 646 if (user_memcpy(data, (uint8 *)buffer + dataOffset, bytes) < B_OK) 647 return B_BAD_ADDRESS; 648 649 data = (void *)((uint8 *)data + bytes); 650 length -= bytes; 651 offset += bytes; 652 } 653 654 return B_OK; 655 } 656