1 /* $OpenBSD: rip.c,v 1.8 2008/06/30 23:35:39 av Exp $ */
2
3 /*
4 * Copyright (c) 2007 Alexey Vatchenko <av@bsdua.org>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18 #include <sys/types.h>
19 #include <sys/signal.h>
20 #include <sys/device.h>
21
22 #include <sys/audioio.h>
23 #include <sys/cdio.h>
24 #include <sys/ioctl.h>
25 #include <sys/scsiio.h>
26 #include <sys/stat.h>
27
28 #include <scsi/scsi_all.h>
29 #include <scsi/scsi_disk.h>
30 #include <scsi/scsiconf.h>
31 #include <scsi/cd.h>
32
33 #include <ctype.h>
34 #include <err.h>
35 #include <errno.h>
36 #include <fcntl.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <unistd.h>
41
42 extern int fd;
43 extern int msf;
44 extern struct cd_toc_entry *toc_buffer;
45
46 extern u_int msf2lba(u_char m, u_char s, u_char f);
47 extern int read_toc_entrys(int size);
48
49 /*
50 * Arguments parser
51 */
52 TAILQ_HEAD(track_pair_head, track_pair);
53
54 static int _parse_val(char *start, char *nxt, int *val);
55 static int _parse_pair(char *start, char *nxt, int *val1, int *val2);
56 static int _add_pair(struct track_pair_head *head, int val1, int val2,
57 int issorted);
58
59 struct track_pair {
60 u_char start;
61 u_char end;
62 TAILQ_ENTRY(track_pair) list;
63 };
64
65 void parse_tracks_init(struct track_pair_head *head);
66 void parse_tracks_final(struct track_pair_head *head);
67 int parse_tracks(struct track_pair_head *head, u_char first, u_char last,
68 const char *arg, int issorted);
69 int parse_tracks_add(struct track_pair_head *head, u_char first,
70 u_char last, int issorted);
71
72 /*
73 * Tracks ripping
74 */
75 /* Header of the canonical WAVE file */
76 static u_char wavehdr[44] = {
77 'R', 'I', 'F', 'F', 0x0, 0x0, 0x0, 0x0, 'W', 'A', 'V', 'E',
78 'f', 'm', 't', ' ', 0x10, 0x0, 0x0, 0x0, 0x1, 0x0, 0x2, 0x0,
79 0x44, 0xac, 0x0, 0x0, 0x10, 0xb1, 0x2, 0x0, 0x4, 0x0, 0x10, 0x0,
80 'd', 'a', 't', 'a', 0x0, 0x0, 0x0, 0x0
81 };
82
83 static int write_sector(int, u_char *, u_int32_t);
84
85 int read_data_sector(u_int32_t, u_char *, u_int32_t);
86
87 struct track_info {
88 int fd; /* descriptor of output file */
89 u_int track; /* track number */
90 char name[12]; /* output file name, i.e. trackXX.wav/trackXX.dat */
91 u_char isaudio; /* true if audio track, otherwise it's data track */
92 u_int32_t start_lba; /* starting address of this track */
93 u_int32_t end_lba; /* starting address of the next track */
94 };
95
96 int read_track(int, struct track_info *);
97
98 int rip_next_track(struct track_info *);
99 int play_next_track(struct track_info *);
100
101 static int rip_tracks_loop(struct track_pair *tp, u_int n_tracks,
102 int (*next_track)(struct track_info *));
103
104 int rip_tracks(char *arg, int (*next_track)(struct track_info *),
105 int issorted);
106
107 /* Next-Track function exit codes */
108 #define NXTRACK_OK 0
109 #define NXTRACK_FAIL 1
110 #define NXTRACK_SKIP 2
111
112 static int
_parse_val(char * start,char * nxt,int * val)113 _parse_val(char *start, char *nxt, int *val)
114 {
115 char *p;
116 int i, base, n;
117
118 n = nxt - start;
119
120 if (n > 3 || n < 1)
121 return (-1);
122 for (p = start; p < nxt; p++) {
123 if (!isdigit(*p))
124 return (-1);
125 }
126
127 *val = 0;
128 base = 1;
129 for (i = 0; i < n; i++) {
130 *val += base * (start[n - i - 1] - '0');
131 base *= 10;
132 }
133 return (0);
134 }
135
136 static int
_parse_pair(char * start,char * nxt,int * val1,int * val2)137 _parse_pair(char *start, char *nxt, int *val1, int *val2)
138 {
139 char *p, *delim;
140 int error;
141
142 delim = NULL;
143 p = start;
144 while (p < nxt) {
145 if (*p == '-')
146 delim = p;
147 p++;
148 }
149
150 if (delim != NULL) {
151 error = 0;
152 if (delim - start < 1)
153 *val1 = -1;
154 else
155 error = _parse_val(start, delim, val1);
156
157 if (error == 0) {
158 if ((nxt - delim - 1) < 1)
159 *val2 = -1;
160 else
161 error = _parse_val(delim + 1, nxt, val2);
162 }
163 } else {
164 error = _parse_val(start, nxt, val1);
165 *val2 = *val1;
166 }
167
168 if (error == 0) {
169 if (*val1 > 99 || *val2 > 99)
170 error = -1;
171 }
172
173 return (error);
174 }
175
176 static int
_add_pair(struct track_pair_head * head,int val1,int val2,int issorted)177 _add_pair(struct track_pair_head *head, int val1, int val2, int issorted)
178 {
179 u_char v1, v2, v3;
180 struct track_pair *tp, *entry;
181 int fix;
182
183 v1 = (u_char)val1;
184 v2 = (u_char)val2;
185
186 if (issorted) {
187 /* 1. Fix order */
188 if (v1 > v2) {
189 v3 = v1;
190 v1 = v2;
191 v2 = v3;
192 }
193
194 /* 2. Find closest range and fix it */
195 fix = 0;
196 TAILQ_FOREACH(entry, head, list) {
197 if (v1 + 1 == entry->start || v1 == entry->start)
198 fix = 1;
199 else if (v1 > entry->start && v1 <= entry->end + 1)
200 fix = 1;
201 else if (v2 + 1 == entry->start || v2 == entry->start)
202 fix = 1;
203 else if (v2 > entry->start && v2 <= entry->end + 1)
204 fix = 1;
205 if (fix)
206 break;
207 }
208
209 if (fix) {
210 if (v1 < entry->start)
211 entry->start = v1;
212 if (v2 > entry->end)
213 entry->end = v2;
214
215 return (0);
216 }
217 }
218
219 tp = (struct track_pair *)malloc(sizeof(*tp));
220 if (tp == NULL)
221 return (-1);
222
223 tp->start = v1;
224 tp->end = v2;
225 TAILQ_INSERT_TAIL(head, tp, list);
226
227 return (0);
228 }
229
230 void
parse_tracks_init(struct track_pair_head * head)231 parse_tracks_init(struct track_pair_head *head)
232 {
233
234 memset(head, 0, sizeof(*head));
235 TAILQ_INIT(head);
236 }
237
238 void
parse_tracks_final(struct track_pair_head * head)239 parse_tracks_final(struct track_pair_head *head)
240 {
241 struct track_pair *tp;
242
243 while ((tp = TAILQ_FIRST(head)) != TAILQ_END(head)) {
244 TAILQ_REMOVE(head, tp, list);
245 free(tp);
246 }
247 }
248
249 int
parse_tracks(struct track_pair_head * head,u_char first,u_char last,const char * arg,int issorted)250 parse_tracks(struct track_pair_head *head, u_char first, u_char last,
251 const char *arg, int issorted)
252 {
253 char *p, *nxt;
254 int error, val1, val2;
255
256 p = (char *)arg;
257 for (;;) {
258 /* Skip trailing spaces */
259 while (*p != '\0' && isspace(*p))
260 ++p;
261 if (*p == '\0')
262 break;
263
264 /* Search for the next space symbol */
265 nxt = p;
266 while (*nxt != '\0' && !isspace(*nxt))
267 ++nxt;
268 /* ``nxt'' can't be equal to ``p'' here */
269 error = _parse_pair(p, nxt, &val1, &val2);
270 if (error != 0)
271 break; /* parse error */
272
273 if (val1 == -1)
274 val1 = first;
275 if (val2 == -1)
276 val2 = last;
277
278 error = _add_pair(head, val1, val2, issorted);
279 if (error != 0)
280 break; /* allocation error */
281
282 p = nxt;
283 }
284
285 return (0);
286 }
287
288 int
parse_tracks_add(struct track_pair_head * head,u_char first,u_char last,int issorted)289 parse_tracks_add(struct track_pair_head *head, u_char first, u_char last,
290 int issorted)
291 {
292
293 return _add_pair(head, first, last, issorted);
294 }
295
296 static int
write_sector(int fd,u_char * sec,u_int32_t secsize)297 write_sector(int fd, u_char *sec, u_int32_t secsize)
298 {
299 ssize_t res;
300
301 while (secsize > 0) {
302 res = write(fd, sec, secsize);
303 if (res < 0)
304 return (-1);
305
306 sec += res;
307 secsize -= res;
308 }
309
310 return (0);
311 }
312
313 /*
314 * ERRORS
315 * The function can return
316 * [EBUSY] Device is busy.
317 * [ETIMEDOUT] Operation timeout.
318 * [EIO] Any other errors.
319 * [EAGAIN] The operation must be made again. XXX - not implemented
320 */
321 int
read_data_sector(u_int32_t lba,u_char * sec,u_int32_t secsize)322 read_data_sector(u_int32_t lba, u_char *sec, u_int32_t secsize)
323 {
324 scsireq_t scr;
325 u_char *cmd;
326 int error;
327
328 memset(&scr, 0, sizeof(scr));
329
330 cmd = (u_char *)scr.cmd;
331 cmd[0] = 0xbe; /* READ CD */
332 _lto4b(lba, cmd + 2); /* Starting Logical Block Address */
333 _lto3b(1, cmd + 6); /* Transfer Length in Blocks */
334 cmd[9] = 0x10; /* User Data field */
335
336 scr.flags = SCCMD_ESCAPE | SCCMD_READ;
337 scr.databuf = sec;
338 scr.datalen = secsize;
339 scr.cmdlen = 12;
340 scr.timeout = 120000;
341 scr.senselen = SENSEBUFLEN;
342
343 /* XXX - what's wrong with DVD? */
344
345 error = ioctl(fd, SCIOCCOMMAND, &scr);
346 if (error == -1)
347 return (EIO);
348 else if (scr.retsts == SCCMD_BUSY)
349 return (EBUSY);
350 else if (scr.retsts == SCCMD_TIMEOUT)
351 return (ETIMEDOUT);
352 else if (scr.retsts != SCCMD_OK)
353 return (EIO);
354
355 return (0);
356 }
357
358 int
read_track(int fd,struct track_info * ti)359 read_track(int fd, struct track_info *ti)
360 {
361 struct timeval tv, otv, atv;
362 u_int32_t i, blksize, n_sec;
363 u_char *sec;
364 int error;
365
366 n_sec = ti->end_lba - ti->start_lba;
367 blksize = (ti->isaudio) ? 2352 : 2048;
368 sec = (u_char *)malloc(blksize);
369 if (sec == NULL)
370 return (-1);
371
372 timerclear(&otv);
373 atv.tv_sec = 1;
374 atv.tv_usec = 0;
375
376 for (i = 0; i < n_sec; ) {
377 gettimeofday(&tv, NULL);
378 if (timercmp(&tv, &otv, >)) {
379 fprintf(stderr, "\rtrack %u '%c' %08u/%08u %3u%%",
380 ti->track,
381 (ti->isaudio) ? 'a' : 'd', i, n_sec,
382 100 * i / n_sec);
383 timeradd(&tv, &atv, &otv);
384 }
385
386 error = read_data_sector(i + ti->start_lba, sec, blksize);
387 if (error == 0) {
388 if (write_sector(ti->fd, sec, blksize) != 0) {
389 free(sec);
390 warnx("\nerror while writing to the %s file",
391 ti->name);
392 return (-1);
393 }
394
395 i++;
396 } else if (error != EAGAIN) {
397 free(sec);
398 warnx("\nerror while reading from device");
399 return (-1);
400 }
401 }
402
403 free(sec);
404 fprintf(stderr, "\rtrack %u '%c' %08u/%08u 100%%\n",
405 ti->track,
406 (ti->isaudio) ? 'a' : 'd', i, n_sec);
407 return (0);
408 }
409
410 int
rip_next_track(struct track_info * info)411 rip_next_track(struct track_info *info)
412 {
413 int error;
414 u_int32_t size;
415
416 info->fd = open(info->name, O_CREAT | O_TRUNC | O_RDWR,
417 S_IRUSR | S_IWUSR);
418 if (info->fd == -1) {
419 warnx("can't open %s file", info->name);
420 return (NXTRACK_FAIL);
421 }
422
423 if (info->isaudio) {
424 /*
425 * Prepend audio track with Wave header
426 */
427 size = 2352 * (info->end_lba - info->start_lba);
428 *(u_int32_t *)(wavehdr + 4) = htole32(size + 36);
429 *(u_int32_t *)(wavehdr + 40) = htole32(size);
430 error = write_sector(info->fd, wavehdr, sizeof(wavehdr));
431 if (error == -1) {
432 warnx("can't write WAVE header for %s file",
433 info->name);
434 return (NXTRACK_FAIL);
435 }
436 }
437
438 return (NXTRACK_OK);
439 }
440
441 int
play_next_track(struct track_info * info)442 play_next_track(struct track_info *info)
443 {
444 int fd, error;
445 audio_info_t ai;
446
447 if (!info->isaudio)
448 return (NXTRACK_SKIP);
449
450 info->fd = open("/dev/audio", O_CREAT | O_TRUNC | O_RDWR,
451 S_IRUSR | S_IWUSR);
452 if (info->fd == -1) {
453 warnx("can't open /dev/audio");
454 return (NXTRACK_FAIL);
455 }
456
457 fd = open("/dev/audioctl", O_RDWR);
458 if (fd != -1) {
459 AUDIO_INITINFO(&ai);
460 ai.play.sample_rate = 44100;
461 ai.play.channels = 2;
462 ai.play.precision = 16;
463 ai.play.encoding = AUDIO_ENCODING_SLINEAR_LE;
464 error = ioctl(fd, AUDIO_SETINFO, &ai);
465 close(fd);
466 } else
467 error = -1;
468
469 if (error == -1) {
470 warnx("can't configure audio device");
471 close(info->fd);
472 info->fd = -1;
473 return (NXTRACK_FAIL);
474 }
475
476 return (NXTRACK_OK);
477 }
478
479 static int
rip_tracks_loop(struct track_pair * tp,u_int n_tracks,int (* next_track)(struct track_info *))480 rip_tracks_loop(struct track_pair *tp, u_int n_tracks,
481 int (*next_track)(struct track_info *))
482 {
483 struct track_info info;
484 u_char trk;
485 u_int i;
486 char order;
487 int error;
488
489 order = (tp->start > tp->end) ? -1 : 1;
490 trk = tp->start;
491 for (;;) {
492 error = 0;
493 for (i = 0; i < n_tracks; i++) {
494 if (trk == toc_buffer[i].track)
495 break;
496 }
497
498 if (i != n_tracks) {
499 /* Track is found */
500 info.track = toc_buffer[i].track;
501 info.isaudio = (toc_buffer[i].control & 4) == 0;
502 snprintf(info.name, sizeof(info.name), "track%02u.%s",
503 toc_buffer[i].track,
504 (info.isaudio) ? "wav" : "dat");
505
506 if (msf) {
507 info.start_lba = msf2lba(
508 toc_buffer[i].addr.msf.minute,
509 toc_buffer[i].addr.msf.second,
510 toc_buffer[i].addr.msf.frame);
511 info.end_lba = msf2lba(
512 toc_buffer[i + 1].addr.msf.minute,
513 toc_buffer[i + 1].addr.msf.second,
514 toc_buffer[i + 1].addr.msf.frame);
515 } else {
516 info.start_lba = toc_buffer[i].addr.lba;
517 info.end_lba = toc_buffer[i + 1].addr.lba;
518 }
519
520 error = next_track(&info);
521 if (error == NXTRACK_FAIL) {
522 error = -1;
523 break;
524 } else if (error != NXTRACK_SKIP) {
525 error = read_track(fd, &info);
526 close(info.fd);
527
528 if (error != 0) {
529 warnx("can't rip %u track",
530 toc_buffer[i].track);
531 break;
532 }
533 }
534 }
535
536 if (trk == tp->end)
537 break;
538 trk += order;
539 }
540
541 return (error);
542 }
543
544 int
rip_tracks(char * arg,int (* next_track)(struct track_info *),int issorted)545 rip_tracks(char *arg, int (*next_track)(struct track_info *), int issorted)
546 {
547 struct track_pair_head list;
548 struct track_pair *tp;
549 struct ioc_toc_header h;
550 u_int n;
551 int rc;
552
553 rc = ioctl(fd, CDIOREADTOCHEADER, &h);
554 if (rc < 0)
555 return (rc);
556
557 if (h.starting_track > h.ending_track) {
558 warnx("TOC starting_track > TOC ending_track");
559 return (0);
560 }
561
562 n = h.ending_track - h.starting_track + 1;
563 rc = read_toc_entrys((n + 1) * sizeof(struct cd_toc_entry));
564 if (rc < 0)
565 return (rc);
566
567 parse_tracks_init(&list);
568 /* We assume that all spaces are skipped in ``arg''. */
569 if (arg == NULL || *arg == '\0') {
570 rc = parse_tracks_add(&list, h.starting_track, h.ending_track,
571 0);
572 } else {
573 rc = parse_tracks(&list, h.starting_track, h.ending_track, arg,
574 issorted);
575 }
576 if (rc < 0) {
577 warnx("can't create track list");
578 parse_tracks_final(&list);
579 return (rc);
580 }
581
582 TAILQ_FOREACH(tp, &list, list) {
583 rc = rip_tracks_loop(tp, n, next_track);
584 if (rc < 0)
585 break;
586 }
587
588 parse_tracks_final(&list);
589 return (0);
590 }
591
592 int
cdrip(char * arg)593 cdrip(char *arg)
594 {
595 return rip_tracks(arg, rip_next_track, 1);
596 }
597
598 int
cdplay(char * arg)599 cdplay(char *arg)
600 {
601 return rip_tracks(arg, play_next_track, 0);
602 }
603