xref: /dragonfly/usr.sbin/burncd/burncd.c (revision 0db87cb7)
1 /*-
2  * Copyright (c) 2000,2001,2002 Søren Schmidt <sos@freebsd.org>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer,
10  *    without modification, immediately at the beginning of the file.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  * 3. The name of the author may not be used to endorse or promote products
15  *    derived from this software without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  *
28  * $FreeBSD: src/usr.sbin/burncd/burncd.c,v 1.10.2.6 2002/11/20 00:26:18 njl Exp $
29  * $DragonFly: src/usr.sbin/burncd/burncd.c,v 1.4 2008/06/05 18:06:33 swildner Exp $
30  */
31 
32 #include <unistd.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <err.h>
37 #include <sysexits.h>
38 #include <fcntl.h>
39 #include <sys/errno.h>
40 #include <sys/ioctl.h>
41 #include <sys/stat.h>
42 #include <sys/cdio.h>
43 #include <sys/cdrio.h>
44 #include <sys/param.h>
45 
46 #define BLOCKS	16
47 
48 struct track_info {
49 	int	file;
50 	char	*file_name;
51 	u_int	file_size;
52 	int	block_size;
53 	int	block_type;
54 	int	pregap;
55 	int	addr;
56 };
57 static struct track_info tracks[100];
58 static int fd, quiet, verbose, saved_block_size, notracks;
59 
60 void add_track(char *, int, int, int);
61 void do_DAO(int, int);
62 void do_TAO(int, int);
63 int write_file(struct track_info *);
64 int roundup_blocks(struct track_info *);
65 void cue_ent(struct cdr_cue_entry *, int, int, int, int, int, int, int);
66 void cleanup(int);
67 void usage(void);
68 
69 int
70 main(int argc, char **argv)
71 {
72 	int ch, arg, addr;
73 	int dao = 0, eject = 0, fixate = 0, list = 0, multi = 0, preemp = 0;
74 	int nogap = 0, speed = 4 * 177, test_write = 0;
75 	int block_size = 0, block_type = 0, cdopen = 0;
76 	const char *dev = "/dev/acd0c";
77 
78 	while ((ch = getopt(argc, argv, "def:lmnpqs:tv")) != -1) {
79 		switch (ch) {
80 		case 'd':
81 			dao = 1;
82 			break;
83 
84 		case 'e':
85 			eject = 1;
86 			break;
87 
88 		case 'f':
89 			dev = optarg;
90 			break;
91 
92 		case 'l':
93 			list = 1;
94 			break;
95 
96 		case 'm':
97 			multi = 1;
98 			break;
99 
100 		case 'n':
101 			nogap = 1;
102 			break;
103 
104 		case 'p':
105 			preemp = 1;
106 			break;
107 
108 		case 'q':
109 			quiet = 1;
110 			break;
111 
112 		case 's':
113 			if (strcasecmp("max", optarg) == 0)
114 				speed = CDR_MAX_SPEED;
115 			else
116 				speed = atoi(optarg) * 177;
117 			if (speed <= 0)
118 				errx(EX_USAGE, "Invalid speed: %s", optarg);
119 			break;
120 
121 		case 't':
122 			test_write = 1;
123 			break;
124 
125 		case 'v':
126 			verbose = 1;
127 			break;
128 
129 		default:
130 			usage();
131 		}
132 	}
133 	argc -= optind;
134 	argv += optind;
135 
136 	if (argc == 0)
137 		usage();
138 
139 	if ((fd = open(dev, O_RDWR, 0)) < 0)
140 		err(EX_NOINPUT, "open(%s)", dev);
141 
142 	if (ioctl(fd, CDRIOCGETBLOCKSIZE, &saved_block_size) < 0)
143        		err(EX_IOERR, "ioctl(CDRIOCGETBLOCKSIZE)");
144 
145 	if (ioctl(fd, CDRIOCWRITESPEED, &speed) < 0)
146        		err(EX_IOERR, "ioctl(CDRIOCWRITESPEED)");
147 
148 	err_set_exit(cleanup);
149 
150 	for (arg = 0; arg < argc; arg++) {
151 		if (!strcasecmp(argv[arg], "fixate")) {
152 			fixate = 1;
153 			break;
154 		}
155 		if (!strcasecmp(argv[arg], "msinfo")) {
156 		        struct ioc_read_toc_single_entry entry;
157 			struct ioc_toc_header header;
158 
159 			if (ioctl(fd, CDIOREADTOCHEADER, &header) < 0)
160 				err(EX_IOERR, "ioctl(CDIOREADTOCHEADER)");
161 			bzero(&entry, sizeof(struct ioc_read_toc_single_entry));
162 			entry.address_format = CD_LBA_FORMAT;
163 			entry.track = header.ending_track;
164 			if (ioctl(fd, CDIOREADTOCENTRY, &entry) < 0)
165 				err(EX_IOERR, "ioctl(CDIOREADTOCENTRY)");
166 			if (ioctl(fd, CDRIOCNEXTWRITEABLEADDR, &addr) < 0)
167 				err(EX_IOERR, "ioctl(CDRIOCNEXTWRITEABLEADDR)");
168 			fprintf(stdout, "%d,%d\n",
169 				ntohl(entry.entry.addr.lba), addr);
170 
171 			break;
172 		}
173 		if (!strcasecmp(argv[arg], "erase") || !strcasecmp(argv[arg], "blank")){
174 		    	int error, blank, percent;
175 
176 			if (!strcasecmp(argv[arg], "erase"))
177 				blank = CDR_B_ALL;
178 			else
179 				blank = CDR_B_MIN;
180 			if (!quiet)
181 				fprintf(stderr, "%sing CD, please wait..\r",
182 					blank == CDR_B_ALL ? "eras" : "blank");
183 
184 			if (ioctl(fd, CDRIOCBLANK, &blank) < 0)
185         			err(EX_IOERR, "ioctl(CDRIOCBLANK)");
186 			while (1) {
187 				sleep(1);
188 				error = ioctl(fd, CDRIOCGETPROGRESS, &percent);
189 				if (percent > 0 && !quiet)
190 					fprintf(stderr,
191 						"%sing CD - %d %% done     \r",
192 						blank == CDR_B_ALL ?
193 						"eras" : "blank", percent);
194 				if (error || percent == 100)
195 					break;
196 			}
197 			if (!quiet)
198 				printf("\n");
199 			continue;
200 		}
201 		if (!strcasecmp(argv[arg], "audio") || !strcasecmp(argv[arg], "raw")) {
202 			block_type = CDR_DB_RAW;
203 			block_size = 2352;
204 			continue;
205 		}
206 		if (!strcasecmp(argv[arg], "data") || !strcasecmp(argv[arg], "mode1")) {
207 			block_type = CDR_DB_ROM_MODE1;
208 			block_size = 2048;
209 			continue;
210 		}
211 		if (!strcasecmp(argv[arg], "mode2")) {
212 			block_type = CDR_DB_ROM_MODE2;
213 			block_size = 2336;
214 			continue;
215 		}
216 		if (!strcasecmp(argv[arg], "xamode1")) {
217 			block_type = CDR_DB_XA_MODE1;
218 			block_size = 2048;
219 			continue;
220 		}
221 		if (!strcasecmp(argv[arg], "xamode2")) {
222 			block_type = CDR_DB_XA_MODE2_F2;
223 			block_size = 2324;
224 			continue;
225 		}
226 		if (!strcasecmp(argv[arg], "vcd")) {
227 			block_type = CDR_DB_XA_MODE2_F2;
228 			block_size = 2352;
229 			dao = 1;
230 			nogap = 1;
231 			continue;
232 		}
233 		if (!block_size)
234 			errx(EX_NOINPUT, "no data format selected");
235 		if (list) {
236 			char file_buf[MAXPATHLEN + 1], *eol;
237 			FILE *fp;
238 
239 			if ((fp = fopen(argv[arg], "r")) == NULL)
240 	 			err(EX_NOINPUT, "fopen(%s)", argv[arg]);
241 
242 			while (fgets(file_buf, sizeof(file_buf), fp) != NULL) {
243 				if (*file_buf == '#' || *file_buf == '\n')
244 					continue;
245 				if ((eol = strchr(file_buf, '\n')))
246 					*eol = 0;
247 				add_track(file_buf, block_size, block_type, nogap);
248 			}
249 			if (feof(fp))
250 				fclose(fp);
251 			else
252 				err(EX_IOERR, "fgets(%s)", file_buf);
253 		}
254 		else
255 			add_track(argv[arg], block_size, block_type, nogap);
256 	}
257 	if (notracks) {
258 		if (ioctl(fd, CDIOCSTART, 0) < 0)
259 			err(EX_IOERR, "ioctl(CDIOCSTART)");
260 		if (!cdopen) {
261 			if (ioctl(fd, CDRIOCINITWRITER, &test_write) < 0)
262 				err(EX_IOERR, "ioctl(CDRIOCINITWRITER)");
263 			cdopen = 1;
264 		}
265 		if (dao)
266 			do_DAO(test_write, multi);
267 		else
268 			do_TAO(test_write, preemp);
269 	}
270 	if (fixate && !dao) {
271 		if (!quiet)
272 			fprintf(stderr, "fixating CD, please wait..\n");
273 		if (ioctl(fd, CDRIOCFIXATE, &multi) < 0)
274         		err(EX_IOERR, "ioctl(CDRIOCFIXATE)");
275 	}
276 
277 	if (ioctl(fd, CDRIOCSETBLOCKSIZE, &saved_block_size) < 0) {
278 		err_set_exit(NULL);
279 		err(EX_IOERR, "ioctl(CDRIOCSETBLOCKSIZE)");
280 	}
281 
282 	if (eject)
283 		if (ioctl(fd, CDIOCEJECT) < 0)
284 			err(EX_IOERR, "ioctl(CDIOCEJECT)");
285 	close(fd);
286 	exit(EX_OK);
287 }
288 
289 void
290 add_track(char *name, int block_size, int block_type, int nogap)
291 {
292 	struct stat sb;
293 	int file;
294 	static int done_stdin = 0;
295 
296 	if (!strcmp(name, "-")) {
297 		if (done_stdin) {
298 			warn("skipping multiple usages of stdin");
299 			return;
300 		}
301 		file = STDIN_FILENO;
302 		done_stdin = 1;
303 	}
304 	else if ((file = open(name, O_RDONLY, 0)) < 0)
305 		err(EX_NOINPUT, "open(%s)", name);
306 	if (fstat(file, &sb) < 0)
307 		err(EX_IOERR, "fstat(%s)", name);
308 	tracks[notracks].file = file;
309 	tracks[notracks].file_name = name;
310 	if (file == STDIN_FILENO)
311 		tracks[notracks].file_size = -1;
312 	else
313 		tracks[notracks].file_size = sb.st_size;
314 	tracks[notracks].block_size = block_size;
315 	tracks[notracks].block_type = block_type;
316 
317 	if (nogap && notracks)
318 		tracks[notracks].pregap = 0;
319 	else {
320 		if (tracks[notracks - (notracks > 0)].block_type == block_type)
321 			tracks[notracks].pregap = 150;
322 		else
323 			tracks[notracks].pregap = 255;
324 	}
325 
326 	if (verbose) {
327 		int pad = 0;
328 
329 		if (tracks[notracks].file_size / tracks[notracks].block_size !=
330 		    roundup_blocks(&tracks[notracks]))
331 			pad = 1;
332 		fprintf(stderr,
333 			"adding type 0x%02x file %s size %d KB %d blocks %s\n",
334 			tracks[notracks].block_type, name, (int)sb.st_size/1024,
335 			roundup_blocks(&tracks[notracks]),
336 			pad ? "(0 padded)" : "");
337 	}
338 	notracks++;
339 }
340 
341 void
342 do_DAO(int test_write, int multi)
343 {
344 	struct cdr_cuesheet sheet;
345 	struct cdr_cue_entry cue[100];
346 	int format = CDR_SESS_CDROM;
347 	int addr, i, j = 0;
348 
349 	int bt2ctl[16] = { 0x0,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
350 			   0x4, 0x4, 0x4, 0x4, 0x4, 0x4,  -1,  -1 };
351 
352 	int bt2df[16] = { 0x0,    -1,   -1,   -1,   -1,   -1,   -1,   -1,
353 			  0x10, 0x30, 0x20,   -1, 0x21,   -1,   -1,   -1 };
354 
355 	if (ioctl(fd, CDRIOCNEXTWRITEABLEADDR, &addr) < 0)
356 		err(EX_IOERR, "ioctl(CDRIOCNEXTWRITEABLEADDR)");
357 	if (verbose)
358 		fprintf(stderr, "next writeable LBA %d\n", addr);
359 
360 	cue_ent(&cue[j++], bt2ctl[tracks[0].block_type], 0x01, 0x00, 0x0,
361 		(bt2df[tracks[0].block_type] & 0xf0) |
362 		(tracks[0].block_type < 8 ? 0x01 : 0x04), 0x00, addr);
363 
364 	for (i = 0; i < notracks; i++) {
365 		if (bt2ctl[tracks[i].block_type] < 0 ||
366 		    bt2df[tracks[i].block_type] < 0)
367 			err(EX_IOERR, "track type not supported in DAO mode");
368 
369 		if (tracks[i].block_type >= CDR_DB_XA_MODE1)
370 			format = CDR_SESS_CDROM_XA;
371 
372 		if (i == 0) {
373 			addr += tracks[i].pregap;
374 			tracks[i].addr = addr;
375 
376 			cue_ent(&cue[j++], bt2ctl[tracks[i].block_type],
377 				0x01, i+1, 0x1, bt2df[tracks[i].block_type],
378 				0x00, addr);
379 
380 		}
381 		else {
382 			if (tracks[i].pregap) {
383 				if (tracks[i].block_type > 0x7) {
384 					cue_ent(&cue[j++],bt2ctl[tracks[i].block_type],
385 						0x01, i+1, 0x0,
386 						(bt2df[tracks[i].block_type] & 0xf0) |
387 						(tracks[i].block_type < 8 ? 0x01 :0x04),
388 						0x00, addr);
389 				}
390 				else
391 					cue_ent(&cue[j++],bt2ctl[tracks[i].block_type],
392 						0x01, i+1, 0x0,
393 						bt2df[tracks[i].block_type],
394 						0x00, addr);
395 			}
396 			tracks[i].addr = tracks[i - 1].addr +
397 				roundup_blocks(&tracks[i - 1]);
398 
399 			cue_ent(&cue[j++], bt2ctl[tracks[i].block_type],
400 				0x01, i+1, 0x1, bt2df[tracks[i].block_type],
401 				0x00, addr + tracks[i].pregap);
402 
403 			if (tracks[i].block_type > 0x7)
404 				addr += tracks[i].pregap;
405 		}
406 		addr += roundup_blocks(&tracks[i]);
407 	}
408 
409 	cue_ent(&cue[j++], bt2ctl[tracks[i - 1].block_type], 0x01, 0xaa, 0x01,
410 		(bt2df[tracks[i - 1].block_type] & 0xf0) |
411 		(tracks[i - 1].block_type < 8 ? 0x01 : 0x04), 0x00, addr);
412 
413 	sheet.len = j * 8;
414 	sheet.entries = cue;
415 	sheet.test_write = test_write;
416 	sheet.session_type = multi ? CDR_SESS_MULTI : CDR_SESS_NONE;
417 	sheet.session_format = format;
418 	if (verbose) {
419 		u_int8_t *ptr = (u_int8_t *)sheet.entries;
420 
421 		fprintf(stderr,"CUE sheet:");
422 		for (i = 0; i < sheet.len; i++)
423 			if (i % 8)
424 				fprintf(stderr," %02x", ptr[i]);
425 			else
426 				fprintf(stderr,"\n%02x", ptr[i]);
427 		fprintf(stderr,"\n");
428 	}
429 
430 	if (ioctl(fd, CDRIOCSENDCUE, &sheet) < 0)
431 		err(EX_IOERR, "ioctl(CDRIOCSENDCUE)");
432 
433 	for (i = 0; i < notracks; i++) {
434 		if (write_file(&tracks[i]))
435 			err(EX_IOERR, "write_file");
436 	}
437 
438 	ioctl(fd, CDRIOCFLUSH);
439 }
440 
441 void
442 do_TAO(int test_write, int preemp)
443 {
444 	struct cdr_track track;
445 	int i;
446 
447 	for (i = 0; i < notracks; i++) {
448 		track.test_write = test_write;
449 		track.datablock_type = tracks[i].block_type;
450 		track.preemp = preemp;
451 		if (ioctl(fd, CDRIOCINITTRACK, &track) < 0)
452 			err(EX_IOERR, "ioctl(CDRIOCINITTRACK)");
453 
454 		if (ioctl(fd, CDRIOCNEXTWRITEABLEADDR, &tracks[i].addr) < 0)
455 			err(EX_IOERR, "ioctl(CDRIOCNEXTWRITEABLEADDR)");
456 		if (!quiet)
457 			fprintf(stderr, "next writeable LBA %d\n",
458 				tracks[i].addr);
459 		if (write_file(&tracks[i]))
460 			err(EX_IOERR, "write_file");
461 		if (ioctl(fd, CDRIOCFLUSH) < 0)
462 			err(EX_IOERR, "ioctl(CDRIOCFLUSH)");
463 	}
464 }
465 
466 int
467 write_file(struct track_info *track_info)
468 {
469 	int size, count, filesize;
470 	char buf[2352*BLOCKS];
471 	static int tot_size = 0;
472 
473 	filesize = track_info->file_size / 1024;
474 
475 	if (ioctl(fd, CDRIOCSETBLOCKSIZE, &track_info->block_size) < 0)
476 		err(EX_IOERR, "ioctl(CDRIOCSETBLOCKSIZE)");
477 
478 	if (track_info->addr >= 0)
479 		lseek(fd, track_info->addr * track_info->block_size, SEEK_SET);
480 
481 	if (verbose)
482 		fprintf(stderr, "addr = %d size = %d blocks = %d\n",
483 			track_info->addr, track_info->file_size,
484 			roundup_blocks(track_info));
485 
486 	if (!quiet) {
487 		if (track_info->file == STDIN_FILENO)
488 			fprintf(stderr, "writing from stdin\n");
489 		else
490 			fprintf(stderr,
491 				"writing from file %s size %d KB\n",
492 				track_info->file_name, filesize);
493 	}
494 	size = 0;
495 
496 	while ((count = read(track_info->file, buf,
497 			     MIN((track_info->file_size - size),
498 				 track_info->block_size * BLOCKS))) > 0) {
499 		int res;
500 
501 		if (count % track_info->block_size) {
502 			/* pad file to % block_size */
503 			bzero(&buf[count],
504 			      (track_info->block_size * BLOCKS) - count);
505 			count = ((count / track_info->block_size) + 1) *
506 				track_info->block_size;
507 		}
508 		if ((res = write(fd, buf, count)) != count) {
509 			fprintf(stderr, "\nonly wrote %d of %d bytes err=%d\n",
510 				res, count, errno);
511 			break;
512 		}
513 		size += count;
514 		tot_size += count;
515 		if (!quiet) {
516 			int pct;
517 
518 			fprintf(stderr, "written this track %d KB", size/1024);
519 			if (track_info->file != STDIN_FILENO && filesize) {
520 				pct = (size / 1024) * 100 / filesize;
521 				fprintf(stderr, " (%d%%)", pct);
522 			}
523 			fprintf(stderr, " total %d KB\r", tot_size/1024);
524 		}
525 		if (size >= track_info->file_size)
526 			break;
527 	}
528 
529 	if (!quiet)
530 		fprintf(stderr, "\n");
531 	close(track_info->file);
532 	return 0;
533 }
534 
535 int
536 roundup_blocks(struct track_info *track)
537 {
538 	return ((track->file_size + track->block_size - 1) / track->block_size);
539 }
540 
541 void
542 cue_ent(struct cdr_cue_entry *cue, int ctl, int adr, int track, int idx,
543 	int dataform, int scms, int lba)
544 {
545 	cue->adr = adr;
546 	cue->ctl = ctl;
547 	cue->track = track;
548 	cue->index = idx;
549 	cue->dataform = dataform;
550 	cue->scms = scms;
551 	lba += 150;
552 	cue->min = lba / (60*75);
553 	cue->sec = (lba % (60*75)) / 75;
554 	cue->frame = (lba % (60*75)) % 75;
555 }
556 
557 void
558 cleanup(int dummy __unused)
559 {
560 	if (ioctl(fd, CDRIOCSETBLOCKSIZE, &saved_block_size) < 0)
561 		err(EX_IOERR, "ioctl(CDRIOCSETBLOCKSIZE)");
562 }
563 
564 void
565 usage(void)
566 {
567 	fprintf(stderr,
568 	    "Usage: %s [-delmnpqtv] [-f device] [-s speed] [command]"
569 	    " [command file ...]\n", getprogname());
570 	exit(EX_USAGE);
571 }
572