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