copycd.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2004 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <errno.h>
#include <limits.h>
#include <unistd.h>
#include <libintl.h>
#include "main.h"
#include "util.h"
#include "misc_scsi.h"
#include "mmc.h"
#include "bstream.h"
#include "device.h"
#include "msgs.h"
#include "transport.h"
struct t_data {
bstreamhandle h;
struct track_info ti;
};
int read_audio_track(cd_device *dev, struct track_info *ti, bstreamhandle h);
#define READ_BURST 24 /* < 64K in all cases */
/*
* This reads the data off of a cd while updating the progress indicator.
* We want to do this in smaller chunks since some CD drives have
* problems with larger reads.
*/
static int
read_data_track(cd_device *dev, struct track_info *ti, bstreamhandle h)
{
int blksize;
uint32_t blks_read, cblk, read_chunk, read_size;
uchar_t *buf;
int ret, sav;
int link_blks_count;
buf = NULL;
ret = 0;
/*
* the last link_blks_count blocks may not exist or be completely
* filled. We need to record the amount to avoid bailing out if
* they cannot be read.
*/
if (dev->d_blksize == 512) {
blksize = 512;
link_blks_count = 8;
} else {
blksize = 2048;
link_blks_count = 2;
}
buf = (uchar_t *)my_zalloc(READ_BURST * blksize);
print_n_flush(gettext("Reading track %d..."), ti->ti_track_no);
if (verbose)
print_n_flush("Track size is %u...", ti->ti_track_size);
init_progress();
cblk = ti->ti_start_address;
blks_read = 0;
while (blks_read < ti->ti_track_size) {
/* Last few are special */
read_chunk = ti->ti_track_size - blks_read - link_blks_count;
read_chunk = (read_chunk > READ_BURST) ? READ_BURST :
read_chunk;
if (read_chunk == 0) {
/* Time for last link blocks */
read_chunk = link_blks_count;
}
read_size = read_chunk * blksize;
if (read10(dev->d_fd, cblk, read_chunk, buf, read_size)) {
if (h->bstr_write(h, buf, read_size) != read_size) {
goto read_data_track_failed;
}
} else {
if (blks_read !=
(ti->ti_track_size - link_blks_count)) {
goto read_data_track_failed;
} else {
/* Read can fail for last link sectors */
errno = 0;
}
}
blks_read += read_chunk;
cblk += read_chunk;
(void) progress((void *)(ti->ti_track_size), blks_read);
}
/* l10n_NOTE : 'done' as in "Reading track 1...done" */
(void) str_print(gettext("done.\n"), progress_pos);
ret = 1;
read_data_track_failed:
sav = errno;
free(buf);
errno = sav;
return (ret);
}
static void
ensure_media_space(uint32_t total_nblks, uchar_t end_tno)
{
off_t nblks_avail;
uint_t bsize;
get_media_type(target->d_fd);
if (use_media_stated_capacity) {
nblks_avail = get_last_possible_lba(target);
if (nblks_avail <= 0) {
/* most newer drives use READ FORMAT CAPACITY */
nblks_avail = read_format_capacity(target->d_fd,
&bsize);
/* if both methods fail no choice but to bail out */
if (nblks_avail <= 0) {
err_msg(gettext(
"Cannot find out media capacity.\n"));
exit(1);
}
}
} else {
if (device_type == CD_RW) {
nblks_avail = MAX_CD_BLKS;
} else {
/*
* For DVD drives use read_format_capacity as default
* retrieve the media size, it can be 3.6, 3.9, 4.2,
* 4.7, or 9.2 GB
*/
nblks_avail =
read_format_capacity(target->d_fd, &bsize);
/* sanity check. if not reasonable default to 4.7 GB */
if (nblks_avail < MAX_CD_BLKS) {
nblks_avail = MAX_DVD_BLKS;
}
}
}
if ((total_nblks + end_tno*300) > nblks_avail) {
err_msg(gettext("Not enough space on the media.\n"));
if (debug) {
(void) printf("Need %u only found %u \n",
(total_nblks + end_tno*300),
(uint32_t)nblks_avail);
}
exit(1);
}
}
/*
* This copies both audio and data CDs. It first reads the TOC of the source CD
* and creates a temp file with the CD image. After this is completed it creates
* the target CD using TAO mode.
*/
void
copy_cd(void)
{
cd_device *src;
char *p;
uchar_t *toc, end_tno;
int blksize, i;
int audio_cd, data_cd;
uint32_t total_nblks;
int ret;
struct t_data *tlist;
print_n_flush(gettext("Analyzing source CD..."));
(void) check_device(target,
CHECK_DEVICE_NOT_WRITABLE|EXIT_IF_CHECK_FAILED);
/* if source drive is specified on the command line */
if (copy_src) {
p = my_zalloc(PATH_MAX);
if (lookup_device(copy_src, p) == 0) {
err_msg(gettext("Cannot find device %s"), copy_src);
err_msg(gettext(" or no media in the drive\n"));
exit(1);
}
src = get_device(copy_src, p);
if (src == NULL) {
err_msg(gettext("Unable to open %s\n"), copy_src);
exit(1);
}
free(p);
} else {
/* source is same as target drive */
src = target;
}
(void) check_device(src, CHECK_TYPE_NOT_CDROM | CHECK_NO_MEDIA |
CHECK_DEVICE_NOT_READY | EXIT_IF_CHECK_FAILED);
toc = (uchar_t *)my_zalloc(4);
if (!read_toc(src->d_fd, 0, 0, 4, toc)) {
err_msg(gettext("Cannot read table of contents\n"));
exit(1);
}
end_tno = toc[3];
free(toc);
tlist = (struct t_data *)my_zalloc(end_tno * sizeof (struct t_data));
audio_cd = data_cd = 0;
total_nblks = 0;
/* build track information so we can copy it over */
for (i = 1; i <= end_tno; i++) {
struct track_info *ti;
ti = &tlist[i - 1].ti;
if (!build_track_info(src, i, ti)) {
err_msg(gettext(
"Cannot get information for track %d\n"), i);
exit(1);
}
total_nblks += ti->ti_track_size;
if (ti->ti_track_mode & 4)
data_cd = 1;
else
audio_cd = 1;
/* Now some sanity checks on the track information */
if ((ti->ti_flags & TI_SESSION_NO_VALID) &&
(ti->ti_session_no != 1)) {
err_msg(
gettext("Copying multisession CD is not supported\n"));
exit(1);
}
if ((ti->ti_flags & TI_PACKET_MODE) ||
(ti->ti_flags & TI_BLANK_TRACK) ||
(ti->ti_flags & TI_DAMAGED_TRACK) ||
(data_cd && audio_cd) || (ti->ti_data_mode == 2)) {
err_msg(gettext("CD format is not supported\n"));
exit(1);
}
if ((ti->ti_flags & TI_NWA_VALID) &&
(ti->ti_nwa != 0xffffffff)) {
err_msg(gettext("Cannot copy incomplete discs\n"));
exit(1);
}
}
/* l10n_NOTE : 'done' as in "Analyzing source CD...done" */
(void) printf(gettext("done.\n"));
if (data_cd) {
blksize = 2048;
} else {
/* audio cd */
blksize = 2352;
}
/* In case of audio CDs, build_track_info() returns 2352 sized nblks */
if (src->d_blksize == 512 && data_cd) {
total_nblks /= 4;
}
(void) printf(gettext("\nCopying %d %s track%s : %ld kbytes\n\n"),
end_tno, (audio_cd == 1) ? gettext("audio") : gettext("data"),
(end_tno > 1) ? "s" : "", (long)((total_nblks*blksize)/1024));
if (!check_avail_temp_space(total_nblks*blksize)) {
err_msg(gettext("Not enough space in temporary directory\n"));
err_msg(
gettext("Use -m to specify alternate temporary directory\n"));
exit(1);
}
/*
* If we can check available space on the target media at this
* Stage, then it is always better. We cannot check DVD+R(W)
* as this media may be formatted and not blank.
*/
if (target && (src != target) && (device_type != DVD_PLUS) &&
(device_type != DVD_PLUS_W) && (!check_device(target,
CHECK_NO_MEDIA|CHECK_MEDIA_IS_NOT_BLANK))) {
ensure_media_space(total_nblks, end_tno);
}
/* for each track */
for (i = 1; i <= end_tno; i++) {
tlist[i - 1].h = open_temp_file_stream();
if (tlist[i - 1].h == NULL) {
err_msg(gettext("Cannot create temporary file : %s\n"),
get_err_str());
exit(1);
}
if (audio_cd)
ret = read_audio_track(src, &tlist[i - 1].ti,
tlist[i - 1].h);
else
ret = read_data_track(src, &tlist[i - 1].ti,
tlist[i - 1].h);
if (ret == 0) {
err_msg(gettext("Error reading track %d : %s\n"), i,
get_err_str());
if (debug)
(void) printf("%x %x %x %x\n", uscsi_status,
SENSE_KEY(rqbuf), ASC(rqbuf), ASCQ(rqbuf));
exit(1);
}
}
/*
* We've finished copying the CD. If source and destination are the same
* or they where not specified then eject the disk and wait for a new
* disk to be inserted.
*/
while ((target == NULL) ||
check_device(target, CHECK_NO_MEDIA|CHECK_MEDIA_IS_NOT_BLANK)) {
if (target != NULL) {
(void) eject_media(target);
}
(void) printf("\n");
print_n_flush(
gettext("Insert a blank media in the drive and press Enter."));
(void) fflush(stdin);
if (target) {
fini_device(target);
target = NULL;
}
(void) getchar();
(void) sleep(4);
(void) setup_target(SCAN_WRITERS);
}
(void) printf("\n");
(void) setreuid(ruid, 0);
if ((device_type != DVD_PLUS) && (device_type != DVD_PLUS_W)) {
ensure_media_space(total_nblks, end_tno);
write_init(audio_cd ? TRACK_MODE_AUDIO : TRACK_MODE_DATA);
}
/* for each track */
for (i = 0; i < end_tno; i++) {
/*
* DVD's dont contain tracks and need to be written in DAO
* mode.
*/
if (device_type != CD_RW) {
if (end_tno > 1) {
err_msg(gettext(
"Media state is not suitable for this"
" write mode.\n"));
}
write_mode = DAO_MODE;
/*
* DVD-R(W) and DVD+R needs to have space reserved
* prior to writing.
*/
if ((device_type == DVD_MINUS) ||
(device_type == DVD_PLUS)) {
if (!set_reservation(target->d_fd,
total_nblks + 1)) {
(void) printf(gettext(
"Setting reservation failed\n"));
exit(1);
}
}
}
write_next_track(audio_cd ? TRACK_MODE_AUDIO : TRACK_MODE_DATA,
tlist[i].h);
/*
* Running in simulation mode and writing several tracks is
* useless so bail after the first track is done.
*/
if (simulation && (end_tno != 1)) {
(void) printf(gettext(
"Simulation mode : skipping remaining tracks\n"));
break;
}
}
write_fini();
/* close the temp file handles */
for (i = 0; i < end_tno; i++)
(tlist[i].h)->bstr_close(tlist[i].h);
free(tlist);
fini_device(target);
exit(0);
}