d164d0552960d5e8bbe74665e6703ebf3d01e2f3
[dragonfly.git] / usr.sbin / burncd / burncd.c
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 }