Modify the build system and the source files to support building a CLI-only version with only glib2 as a dependency. Use CLI_ONLY=1 ./configure, then make clean all.
1492 lines
45 KiB
C
1492 lines
45 KiB
C
/* dvdisaster: Additional error correction for optical media.
|
|
* Copyright (C) 2004-2017 Carsten Gnoerlich.
|
|
*
|
|
* Email: carsten@dvdisaster.org -or- cgnoerlich@fsfe.org
|
|
* Project homepage: http://www.dvdisaster.org
|
|
*
|
|
* This file is part of dvdisaster.
|
|
*
|
|
* dvdisaster is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* dvdisaster is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with dvdisaster. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "dvdisaster.h"
|
|
|
|
#include "read-linear.h"
|
|
#include "scsi-layer.h"
|
|
#include "udf.h"
|
|
|
|
/*
|
|
* IO buffer states
|
|
*/
|
|
|
|
enum { BUF_EMPTY, BUF_FULL, BUF_DEAD, BUF_EOF };
|
|
|
|
/*
|
|
* Send EOF to the worker thread
|
|
*/
|
|
|
|
static void send_eof(read_closure *rc)
|
|
{
|
|
g_mutex_lock(rc->mutex);
|
|
while(rc->bufState[rc->readPtr] != BUF_EMPTY)
|
|
g_cond_wait(rc->canRead, rc->mutex);
|
|
|
|
rc->bufState[rc->readPtr] = BUF_EOF;
|
|
rc->readPtr++;
|
|
if(rc->readPtr >= READ_BUFFERS)
|
|
rc->readPtr = 0;
|
|
|
|
g_cond_signal(rc->canWrite);
|
|
g_mutex_unlock(rc->mutex);
|
|
}
|
|
|
|
/*
|
|
* Cleanup.
|
|
*/
|
|
|
|
static void cleanup(gpointer data)
|
|
{ read_closure *rc = (read_closure*)data;
|
|
#ifndef CLI
|
|
int full_read = FALSE;
|
|
int aborted = rc->earlyTermination;
|
|
int scan_mode = rc->scanMode;
|
|
#endif
|
|
int i;
|
|
int renderers_left = TRUE;
|
|
|
|
/* Make sure that all spiral/curve render idle functions have finished.
|
|
They depend on some structures we are going to free now.
|
|
If Closure->stopActions == STOP_SHUTDOWN_ALL, the main thread is
|
|
waiting for this thread to terminate and will not invoke any more
|
|
idle functions. Executing the while loop would create a deadlock
|
|
in that case. */
|
|
|
|
while(renderers_left
|
|
#ifndef CLI
|
|
&& Closure->stopActions != STOP_SHUTDOWN_ALL
|
|
#endif
|
|
)
|
|
{ g_mutex_lock(rc->rendererMutex);
|
|
if(rc->activeRenderers<=0)
|
|
renderers_left=FALSE;
|
|
g_mutex_unlock(rc->rendererMutex);
|
|
|
|
g_usleep(G_USEC_PER_SEC/10);
|
|
}
|
|
|
|
/* In reading passes > 1, Closure->sectorSkip is forced to be one.
|
|
Restore the old value now. */
|
|
|
|
Closure->sectorSkip = rc->savedSectorSkip;
|
|
|
|
/* Reset temporary ignoring of fatal errors.
|
|
User has to set this in the preferences to make it permanent. */
|
|
|
|
if(Closure->ignoreFatalSense == 2)
|
|
Closure->ignoreFatalSense = 0;
|
|
|
|
/* This is a failure condition */
|
|
|
|
if(g_thread_self() == rc->worker)
|
|
{ g_printf("Reading/Scanning terminated from worker thread - trouble ahead\n");
|
|
return;
|
|
}
|
|
|
|
/* Make sure worker thread exits gracefully */
|
|
|
|
if(rc->worker && !rc->workerError)
|
|
{ send_eof(rc);
|
|
g_thread_join(rc->worker);
|
|
}
|
|
|
|
/* Clean up reader thread */
|
|
|
|
#ifndef CLI
|
|
if(rc->image)
|
|
full_read = (rc->readOK == rc->image->dh->sectors && !Closure->crcErrors);
|
|
#endif
|
|
|
|
UnregisterCleanup();
|
|
|
|
#ifndef CLI
|
|
if(Closure->guiMode)
|
|
{ if(rc->unreportedError)
|
|
SwitchAndSetFootline(Closure->readLinearNotebook, 1, Closure->readLinearFootline,
|
|
_("<span %s>Aborted by unrecoverable error.</span> %lld sectors read, %lld sectors unreadable/skipped so far."),
|
|
Closure->redMarkup, rc->readOK, Closure->readErrors);
|
|
}
|
|
#endif
|
|
|
|
if(rc->readerImage)
|
|
if(!LargeClose(rc->readerImage))
|
|
Stop(_("Error closing image file:\n%s"), strerror(errno));
|
|
if(rc->writerImage)
|
|
if(!LargeClose(rc->writerImage))
|
|
Stop(_("Error closing image file:\n%s"), strerror(errno));
|
|
|
|
if(rc->image) CloseImage(rc->image);
|
|
|
|
if(rc->mutex)
|
|
{ g_mutex_clear(rc->mutex);
|
|
g_free(rc->mutex);
|
|
}
|
|
if(rc->canRead)
|
|
{ g_cond_clear(rc->canRead);
|
|
g_free(rc->canRead);
|
|
}
|
|
if(rc->canWrite)
|
|
{ g_cond_clear(rc->canWrite);
|
|
g_free(rc->canWrite);
|
|
}
|
|
if(rc->workerError) g_free(rc->workerError);
|
|
|
|
for(i=0; i<READ_BUFFERS; i++)
|
|
if(rc->alignedBuf[i])
|
|
FreeAlignedBuffer(rc->alignedBuf[i]);
|
|
|
|
if(rc->msg) g_free(rc->msg);
|
|
if(rc->speedTimer) g_timer_destroy(rc->speedTimer);
|
|
if(rc->readTimer) g_timer_destroy(rc->readTimer);
|
|
if(rc->readMap) FreeBitmap(rc->readMap);
|
|
if(rc->volumeLabel) g_free(rc->volumeLabel);
|
|
|
|
if(rc->rendererMutex)
|
|
{ g_mutex_clear(rc->rendererMutex);
|
|
g_free(rc->rendererMutex);
|
|
}
|
|
|
|
/* trigger failure if some threads are still accessing this */
|
|
memset(rc, sizeof(read_closure), 0xff);
|
|
g_free(rc);
|
|
|
|
/* Continue with ecc file creation after read.
|
|
NOTE: Images are NOT automatically augmented after a read. */
|
|
|
|
#ifndef CLI
|
|
if(Closure->readAndCreate && Closure->guiMode && !scan_mode && !aborted) /* General prerequisites */
|
|
{ if( !strncmp(Closure->methodName, "RS01", 4) /* codec prerequisites */
|
|
|| (!strncmp(Closure->methodName, "RS03", 4) && Closure->eccTarget == ECC_FILE) )
|
|
{ if(!full_read)
|
|
{ ModalDialog(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, NULL,
|
|
_("Automatic error correction file creation\n"
|
|
"is only possible after a full reading pass.\n"));
|
|
AllowActions(TRUE);
|
|
}
|
|
else ContinueWithAction(ACTION_CREATE_CONT);
|
|
}
|
|
else AllowActions(TRUE);
|
|
}
|
|
else
|
|
if(Closure->guiMode)
|
|
AllowActions(TRUE);
|
|
|
|
/* In GUI mode both the reader and worker are spawned sub threads;
|
|
however in CLI mode the reader is the main thread and must not be terminated. */
|
|
|
|
if(Closure->guiMode)
|
|
g_thread_exit(0);
|
|
#endif
|
|
}
|
|
|
|
/***
|
|
*** Helper functions for the reader
|
|
***/
|
|
|
|
/*
|
|
* Register with different label texts depending on rc->scanMode
|
|
*/
|
|
|
|
static void register_reader(read_closure *rc)
|
|
{
|
|
if(rc->scanMode) /* Output messages differ in read and scan mode */
|
|
{ RegisterCleanup(_("Scanning aborted"), cleanup, rc);
|
|
#ifndef CLI
|
|
if(Closure->guiMode)
|
|
SetLabelText(GTK_LABEL(Closure->readLinearHeadline),
|
|
"<big>%s</big>\n<i>%s</i>",
|
|
_("Scanning medium for read errors."),
|
|
_("Medium: not yet determined"));
|
|
#endif
|
|
}
|
|
else
|
|
{ RegisterCleanup(_("Reading aborted"), cleanup, rc);
|
|
#ifndef CLI
|
|
if(Closure->guiMode)
|
|
SetLabelText(GTK_LABEL(Closure->readLinearHeadline),
|
|
"<big>%s</big>\n<i>%s</i>",
|
|
_("Preparing for reading the medium image."),
|
|
_("Medium: not yet determined"));
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Find out current which mode we are operating in:
|
|
* 1. Scanning
|
|
* 2. Creating a new image
|
|
* 3. Completing an existing image
|
|
* Output respective messages and prepare the image file.
|
|
*/
|
|
|
|
static void determine_mode(read_closure *rc)
|
|
{ guint8 medium_fp[16], image_fp[16];
|
|
guint64 image_size;
|
|
unsigned char *buf = rc->alignedBuf[0]->buf;
|
|
int unknown_fingerprint = FALSE;
|
|
|
|
/*** In scan mode we simply need to output some messages. */
|
|
|
|
if(rc->scanMode)
|
|
{
|
|
rc->msg = g_strdup(_("Scanning medium for read errors."));
|
|
|
|
PrintLog("%s\n", rc->msg);
|
|
#ifndef CLI
|
|
if(Closure->guiMode)
|
|
{ if(rc->eccMethod)
|
|
SetLabelText(GTK_LABEL(Closure->readLinearHeadline),
|
|
"<big>%s</big>\n<i>- %s -</i>", rc->msg,
|
|
_("Reading CRC information"));
|
|
|
|
else
|
|
SetLabelText(GTK_LABEL(Closure->readLinearHeadline),
|
|
"<big>%s</big>\n<i>%s</i>", rc->msg, rc->image->dh->mediumDescr);
|
|
}
|
|
#endif
|
|
|
|
rc->readMarker = 0;
|
|
|
|
#ifndef CLI
|
|
if(Closure->guiMode)
|
|
InitializeCurve(rc, rc->image->dh->maxRate, rc->image->dh->canC2Scan);
|
|
#endif
|
|
|
|
return;
|
|
}
|
|
|
|
/*** If no image file exists, open a new one. */
|
|
|
|
#ifndef CLI
|
|
reopen_image:
|
|
#endif
|
|
if(!LargeStat(Closure->imageName, &image_size))
|
|
{
|
|
if(rc->msg) g_free(rc->msg);
|
|
rc->msg = g_strdup(_("Reading new medium image."));
|
|
|
|
if(!(rc->writerImage = LargeOpen(Closure->imageName, O_WRONLY | O_CREAT, IMG_PERMS)))
|
|
Stop(_("Can't open %s:\n%s"),Closure->imageName,strerror(errno));
|
|
if(!(rc->readerImage = LargeOpen(Closure->imageName, O_RDONLY, IMG_PERMS)))
|
|
Stop(_("Can't open %s:\n%s"),Closure->imageName,strerror(errno));
|
|
|
|
PrintLog(_("Creating new %s image.\n"),Closure->imageName);
|
|
#ifndef CLI
|
|
if(Closure->guiMode)
|
|
{ if(rc->eccMethod)
|
|
SetLabelText(GTK_LABEL(Closure->readLinearHeadline),
|
|
"<big>%s</big>\n<i>%s</i>", rc->msg,
|
|
_("Reading CRC information"));
|
|
else
|
|
SetLabelText(GTK_LABEL(Closure->readLinearHeadline),
|
|
"<big>%s</big>\n<i>%s</i>", rc->msg, rc->image->dh->mediumDescr);
|
|
}
|
|
#endif
|
|
rc->rereading = FALSE;
|
|
rc->readMarker = 0;
|
|
|
|
#ifndef CLI
|
|
if(Closure->guiMode)
|
|
InitializeCurve(rc, rc->image->dh->maxRate, rc->image->dh->canC2Scan);
|
|
#endif
|
|
|
|
return;
|
|
}
|
|
|
|
/*** Examine the given image file */
|
|
|
|
if(rc->msg) g_free(rc->msg);
|
|
rc->msg = g_strdup(_("Completing existing medium image."));
|
|
|
|
/* Use the existing file as a starting point.
|
|
Set the read marker at the end of the file
|
|
so that the reader looks for "dead_sector" markers
|
|
and skips already read blocks. */
|
|
|
|
if(!(rc->readerImage = LargeOpen(Closure->imageName, O_RDONLY, IMG_PERMS)))
|
|
Stop(_("Can't open %s:\n%s"),Closure->imageName,strerror(errno));
|
|
if(!(rc->writerImage = LargeOpen(Closure->imageName, O_WRONLY, IMG_PERMS)))
|
|
Stop(_("Can't open %s:\n%s"),Closure->imageName,strerror(errno));
|
|
|
|
rc->rereading = 1;
|
|
rc->readMarker = image_size / 2048;
|
|
|
|
/* Try reading the media and image fingerprints. */
|
|
|
|
if(!LargeSeek(rc->readerImage, (gint64)(2048*FINGERPRINT_SECTOR)))
|
|
unknown_fingerprint = TRUE;
|
|
else
|
|
{ struct MD5Context md5ctxt;
|
|
int n = LargeRead(rc->readerImage, buf, 2048);
|
|
int fp_read;
|
|
|
|
MD5Init(&md5ctxt);
|
|
MD5Update(&md5ctxt, buf, 2048);
|
|
MD5Final(image_fp, &md5ctxt);
|
|
|
|
fp_read = GetImageFingerprint(rc->image, medium_fp, FINGERPRINT_SECTOR);
|
|
|
|
if(n != 2048 || !fp_read || (CheckForMissingSector(buf, FINGERPRINT_SECTOR, NULL, 0) != SECTOR_PRESENT))
|
|
unknown_fingerprint = TRUE;
|
|
}
|
|
|
|
/* If fingerprints could be read, compare them. */
|
|
|
|
if(!unknown_fingerprint && memcmp(image_fp, medium_fp, 16))
|
|
{
|
|
#ifndef CLI
|
|
if(!Closure->guiMode)
|
|
#endif
|
|
Stop(_("Image file does not match the optical disc."));
|
|
#ifndef CLI
|
|
else
|
|
{ int answer = ConfirmImageDeletion(Closure->imageName);
|
|
|
|
if(!answer)
|
|
{ rc->unreportedError = FALSE;
|
|
SwitchAndSetFootline(Closure->readLinearNotebook, 1, Closure->readLinearFootline,
|
|
_("<span %s>Reading aborted.</span> Please select a different image file."),
|
|
Closure->redMarkup);
|
|
cleanup((gpointer)rc);
|
|
}
|
|
else /* Start over with new file */
|
|
{ LargeClose(rc->readerImage);
|
|
LargeClose(rc->writerImage);
|
|
LargeUnlink(Closure->imageName);
|
|
goto reopen_image;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/*** If the image is not complete yet, first aim to read the
|
|
unvisited sectors before trying to re-read the missing ones.
|
|
Exception: We must start from the beginning if multiple reading passes are requested. */
|
|
|
|
if(!Closure->readStart && !Closure->readEnd
|
|
&& rc->readMarker < rc->image->dh->sectors-1 && Closure->readingPasses <= 1)
|
|
{ PrintLog(_("Completing image %s. Continuing with sector %lld.\n"),
|
|
Closure->imageName, rc->readMarker);
|
|
rc->firstSector = rc->readMarker;
|
|
#ifndef CLI
|
|
Closure->additionalSpiralColor = 0; /* blue */
|
|
#endif
|
|
}
|
|
else
|
|
{ PrintLog(_("Completing image %s. Only missing sectors will be read.\n"), Closure->imageName);
|
|
#ifndef CLI
|
|
Closure->additionalSpiralColor = 3; /* dark green*/
|
|
#endif
|
|
}
|
|
|
|
#ifndef CLI
|
|
if(Closure->guiMode)
|
|
SetLabelText(GTK_LABEL(Closure->readLinearHeadline),
|
|
"<big>%s</big>\n<i>%s</i>",rc->msg,rc->image->dh->mediumDescr);
|
|
|
|
if(Closure->guiMode)
|
|
InitializeCurve(rc, rc->image->dh->maxRate, rc->image->dh->canC2Scan);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Fill the gap between rc->readMarker and rc->firstSector
|
|
* with dead sector markers.
|
|
*/
|
|
|
|
static void fill_gap(read_closure *rc)
|
|
{ gint64 s;
|
|
|
|
if(!rc->scanMode && rc->firstSector > rc->readMarker)
|
|
{ unsigned char buf[2048];
|
|
|
|
s = rc->readMarker;
|
|
|
|
if(!LargeSeek(rc->writerImage, (gint64)(2048*s)))
|
|
Stop(_("Failed seeking to sector %lld in image [%s]: %s"),
|
|
s, "fill", strerror(errno));
|
|
|
|
while(s < rc->firstSector)
|
|
{ int n;
|
|
|
|
CreateMissingSector(buf, s, rc->image->imageFP, FINGERPRINT_SECTOR, rc->volumeLabel);
|
|
n = LargeWrite(rc->writerImage, buf, 2048);
|
|
if(n != 2048)
|
|
Stop(_("Failed writing to sector %lld in image [%s]: %s"),
|
|
s, "fill", strerror(errno));
|
|
s++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Allocate memory for CRC32 sums and preload the cache.
|
|
*/
|
|
|
|
static void prepare_crc_cache(read_closure *rc)
|
|
{
|
|
/*** Memory for the CRC32 sums is needed in two cases: */
|
|
|
|
/* a) We have suitable ecc data and want to compare CRC32sums
|
|
against it while reading. Note that for augmented images
|
|
the checksums may be incomplete due to unreadable CRC sectors,
|
|
so we keep them in the CrcBuf struct which deals with lost
|
|
sectors internally. */
|
|
|
|
if(rc->eccMethod)
|
|
{ char method_name[5];
|
|
|
|
strncpy(method_name, rc->eccMethod->name, 4);
|
|
method_name[4] = 0;
|
|
PrintCLI("%s (%s) ... ",_("Reading CRC information from ecc data"),
|
|
method_name);
|
|
|
|
// FIXME: reuse CrcBuf and write respective message
|
|
|
|
#ifndef CLI
|
|
if(Closure->guiMode)
|
|
SetLabelText(GTK_LABEL(Closure->readLinearHeadline),
|
|
"<big>%s</big>\n<i>%s</i>",
|
|
_("Reading CRC information from ecc data"),
|
|
rc->image->dh->mediumDescr);
|
|
#endif
|
|
|
|
if(rc->eccMethod->getCrcBuf)
|
|
{ Closure->crcBuf = rc->eccMethod->getCrcBuf(rc->image);
|
|
Closure->crcBuf->crcCached = TRUE;
|
|
#ifndef CLI
|
|
if(Closure->guiMode)
|
|
RedrawReadLinearWindow();
|
|
#endif
|
|
|
|
/* Augmented image codecs provide the CRCs and md5sums for
|
|
the data portion, but not for the full image.
|
|
Request calculation of them from the image. */
|
|
|
|
if(Closure->crcBuf->md5State & MD5_BUILDING)
|
|
rc->doChecksumsFromImage = CRCBUF_UPDATE_MD5 | CRCBUF_UPDATE_CRC_AFTER_DATA;
|
|
}
|
|
else Closure->crcBuf = NULL;
|
|
|
|
if(rc->eccMethod->resetCksums)
|
|
{ rc->doChecksumsFromCodec = TRUE; // FIXME - remove?
|
|
rc->eccMethod->resetCksums(rc->image);
|
|
}
|
|
|
|
#ifndef CLI
|
|
if(Closure->guiMode)
|
|
SetLabelText(GTK_LABEL(Closure->readLinearHeadline),
|
|
"<big>%s</big>\n<i>%s</i>", rc->msg, rc->image->dh->mediumDescr);
|
|
#endif
|
|
PrintCLI(_("done.\n"));
|
|
return;
|
|
}
|
|
|
|
/* b) No ecc is available, and a full image read is attempted.
|
|
The image CRC32 and md5sum are calculated on the fly,
|
|
as they may be used for ecc creation later. */
|
|
|
|
Closure->crcBuf = CreateCrcBuf(rc->image); // FIXME: reuse, delete, ...
|
|
rc->doChecksumsFromImage = CRCBUF_UPDATE_ALL;
|
|
}
|
|
|
|
/*
|
|
* Wait for the drive to spin up and prepare the timer
|
|
*/
|
|
|
|
static void prepare_timer(read_closure *rc)
|
|
{
|
|
#ifndef CLI
|
|
if(Closure->guiMode && Closure->spinupDelay)
|
|
SwitchAndSetFootline(Closure->readLinearNotebook, 1, Closure->readLinearFootline,
|
|
_("Waiting %d seconds for drive to spin up...\n"), Closure->spinupDelay);
|
|
#endif
|
|
|
|
SpinupDevice(rc->image->dh);
|
|
|
|
#ifndef CLI
|
|
if(Closure->guiMode && Closure->spinupDelay)
|
|
SwitchAndSetFootline(Closure->readLinearNotebook, 0, Closure->readLinearFootline, "ignore");
|
|
#endif
|
|
|
|
if(Closure->spinupDelay) /* eliminate initial seek time from timing */
|
|
ReadSectors(rc->image->dh, rc->alignedBuf[0]->buf, rc->firstSector, 1);
|
|
g_timer_start(rc->speedTimer);
|
|
g_timer_start(rc->readTimer);
|
|
}
|
|
|
|
/*
|
|
* Update the various progress indicators
|
|
*/
|
|
|
|
static void show_progress(read_closure *rc)
|
|
{ int percent;
|
|
|
|
#ifndef CLI
|
|
if(Closure->guiMode && rc->lastErrorsPrinted != Closure->readErrors)
|
|
{ SetLabelText(GTK_LABEL(Closure->readLinearErrors),
|
|
_("Unreadable / skipped sectors: %lld"), Closure->readErrors);
|
|
rc->lastErrorsPrinted = Closure->readErrors;
|
|
}
|
|
#endif
|
|
|
|
if(rc->readPos>rc->readMarker) rc->readMarker=rc->readPos;
|
|
percent = (1000*rc->readPos)/rc->image->dh->sectors;
|
|
|
|
if(rc->lastPercent != percent)
|
|
{ gulong ignore;
|
|
#ifndef CLI
|
|
int color;
|
|
|
|
if(Closure->guiMode)
|
|
ChangeSpiralCursor(Closure->readLinearSpiral, percent);
|
|
#endif
|
|
|
|
if(rc->readOK <= rc->lastReadOK) /* nothing read since last sample? */
|
|
{ rc->speed = 0.0;
|
|
#ifndef CLI
|
|
if(Closure->readErrors - rc->previousReadErrors > 0)
|
|
color = 2;
|
|
else if(Closure->crcErrors - rc->previousCRCErrors > 0)
|
|
color = 4;
|
|
else color = Closure->additionalSpiralColor;
|
|
|
|
if(Closure->guiMode)
|
|
AddCurveValues(rc, percent, color, rc->maxC2);
|
|
#endif
|
|
rc->lastPercent = percent;
|
|
rc->lastSpeed = rc->speed;
|
|
rc->previousReadErrors = Closure->readErrors;
|
|
rc->previousCRCErrors = Closure->crcErrors;
|
|
rc->lastReadOK = rc->readOK;
|
|
}
|
|
else /* something was read since last sample */
|
|
{ double kb_read = (rc->readOK - rc->lastReadOK) * 2.0;
|
|
double elapsed = g_timer_elapsed(rc->speedTimer, &ignore);
|
|
double kb_sec = kb_read / elapsed;
|
|
|
|
#ifndef CLI
|
|
if(Closure->readErrors - rc->previousReadErrors > 0)
|
|
color = 2;
|
|
else if(Closure->crcErrors - rc->previousCRCErrors > 0)
|
|
color = 4;
|
|
else color = 1;
|
|
#endif
|
|
|
|
if(rc->firstSpeedValue)
|
|
{ rc->speed = kb_sec / rc->image->dh->singleRate;
|
|
|
|
#ifndef CLI
|
|
if(Closure->guiMode)
|
|
{ AddCurveValues(rc, rc->lastPercent, color, rc->maxC2);
|
|
AddCurveValues(rc, percent, color, rc->maxC2);
|
|
}
|
|
#endif
|
|
|
|
rc->firstSpeedValue = FALSE;
|
|
rc->lastPercent = percent;
|
|
rc->lastSpeed = rc->speed;
|
|
rc->previousReadErrors = Closure->readErrors;
|
|
rc->previousCRCErrors = Closure->crcErrors;
|
|
rc->lastReadOK = rc->readOK;
|
|
}
|
|
else
|
|
{ static int cut_peaks = 3;
|
|
|
|
/* If reading is interrupted by a requester, following
|
|
reads might be extremely fast due to read-ahead in
|
|
the kernel. Try to mask these out. */
|
|
|
|
if(kb_sec / rc->image->dh->singleRate > 100.0 && cut_peaks)
|
|
cut_peaks--;
|
|
else
|
|
{ rc->speed = (rc->speed + kb_sec / rc->image->dh->singleRate) / 2.0;
|
|
cut_peaks=3;
|
|
}
|
|
|
|
#ifndef CLI
|
|
if(Closure->guiMode)
|
|
AddCurveValues(rc, percent, color, rc->maxC2);
|
|
#endif
|
|
|
|
if(Closure->speedWarning && rc->lastSpeed > 0.5)
|
|
{ double delta = rc->speed - rc->lastSpeed;
|
|
double abs_delta = fabs(delta);
|
|
double sp = (100.0*abs_delta) / rc->lastSpeed;
|
|
|
|
if(sp >= Closure->speedWarning)
|
|
{ if(delta > 0.0)
|
|
PrintCLI(_("Sector %lld: Speed increased to %4.1fx\n"),
|
|
rc->readPos, fabs(rc->speed));
|
|
else
|
|
PrintCLI(_("Sector %lld: Speed dropped to %4.1fx\n"),
|
|
rc->readPos, fabs(rc->speed));
|
|
}
|
|
}
|
|
|
|
if(Closure->fixedSpeedValues)
|
|
PrintProgress(_("Read position: %3d.%1d%% (nn.nx)"),
|
|
percent/10,percent%10);
|
|
else PrintProgress(_("Read position: %3d.%1d%% (%4.1fx)"),
|
|
percent/10,percent%10,rc->speed);
|
|
|
|
rc->lastPercent = percent;
|
|
rc->lastSpeed = rc->speed;
|
|
rc->previousReadErrors = Closure->readErrors;
|
|
rc->previousCRCErrors = Closure->crcErrors;
|
|
rc->lastReadOK = rc->readOK;
|
|
g_timer_start(rc->speedTimer);
|
|
}
|
|
}
|
|
rc->maxC2 = 0;
|
|
}
|
|
}
|
|
|
|
/***
|
|
*** Try reading the medium and create the image and map.
|
|
***/
|
|
|
|
/*
|
|
* The writer / checksum part
|
|
*/
|
|
|
|
static gpointer worker_thread(read_closure *rc)
|
|
{ gint64 s;
|
|
int nsectors;
|
|
int i;
|
|
|
|
for(;;)
|
|
{
|
|
/* See if more buffers are available for processing */
|
|
|
|
g_mutex_lock(rc->mutex);
|
|
|
|
while(rc->bufState[rc->writePtr] == BUF_EMPTY)
|
|
{ g_cond_wait(rc->canWrite, rc->mutex);
|
|
}
|
|
|
|
if(rc->bufState[rc->writePtr] == BUF_EOF)
|
|
{ g_mutex_unlock(rc->mutex);
|
|
return 0;
|
|
}
|
|
|
|
s = rc->bufferedSector[rc->writePtr];
|
|
nsectors = rc->nSectors[rc->writePtr];
|
|
g_mutex_unlock(rc->mutex);
|
|
|
|
/* Write out buffer */
|
|
|
|
if(!rc->scanMode)
|
|
{ int n;
|
|
|
|
if(!LargeSeek(rc->writerImage, (gint64)(2048*s)))
|
|
{ rc->workerError = g_strdup_printf(_("Failed seeking to sector %lld in image [%s]: %s"),
|
|
s, "store", strerror(errno));
|
|
goto update_mutex;
|
|
}
|
|
|
|
n = LargeWrite(rc->writerImage, rc->alignedBuf[rc->writePtr]->buf, 2048*nsectors);
|
|
if(n != 2048*nsectors)
|
|
{ rc->workerError = g_strdup_printf(_("Failed writing to sector %lld in image [%s]: %s"),
|
|
s, "store", strerror(errno));
|
|
goto update_mutex;
|
|
}
|
|
}
|
|
|
|
/* Do on-the-fly CRC / md5sum testing. This is the only action carried out
|
|
in scan mode, but also done while reading. */
|
|
|
|
if(rc->bufState[rc->writePtr] != BUF_DEAD)
|
|
{
|
|
for(i=0; i<nsectors; i++)
|
|
{ unsigned char *buf = rc->alignedBuf[rc->writePtr]->buf+2048*i;
|
|
gint64 sector = s+i;
|
|
|
|
/* Update the global checksum buffer */
|
|
|
|
if(rc->doChecksumsFromImage)
|
|
AddSectorToCrcBuffer(Closure->crcBuf, rc->doChecksumsFromImage, sector, buf, 2048);
|
|
|
|
if(!rc->eccMethod) /* Nothing to do when no ecc data available */
|
|
continue;
|
|
|
|
/* Have the codec update its internal checksums */
|
|
|
|
if(rc->doChecksumsFromCodec)
|
|
rc->eccMethod->updateCksums(rc->image, sector, buf);
|
|
|
|
/* Check against CRCs in the ecc data */
|
|
|
|
if(Closure->crcBuf && sector < Closure->crcBuf->coveredSectors)
|
|
{ switch(CheckAgainstCrcBuffer(Closure->crcBuf, sector, buf))
|
|
{ case CRC_BAD:
|
|
ClearProgress();
|
|
PrintCLI(_("* CRC error, sector: %lld\n"), (long long int)s+i);
|
|
Closure->crcErrors++;
|
|
if(rc->readMap) /* trigger re-read FIXME*/
|
|
ClearBit(rc->readMap, sector);
|
|
break;
|
|
|
|
case CRC_UNKNOWN: /* CRC data missing or detected as defective */
|
|
rc->crcIncomplete = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Release this buffer */
|
|
|
|
update_mutex:
|
|
g_mutex_lock(rc->mutex);
|
|
rc->bufState[rc->writePtr] = BUF_EMPTY;
|
|
rc->writePtr++;
|
|
if(rc->writePtr >= READ_BUFFERS)
|
|
rc->writePtr = 0;
|
|
g_cond_signal(rc->canRead);
|
|
g_mutex_unlock(rc->mutex);
|
|
|
|
if(rc->workerError)
|
|
return NULL;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/***
|
|
*** The reader part
|
|
***/
|
|
|
|
#ifndef CLI
|
|
static void insert_buttons(GtkDialog *dialog)
|
|
{
|
|
gtk_dialog_add_buttons(dialog,
|
|
_utf("Ignore once"), 1,
|
|
_utf("Ignore always"), 2,
|
|
_utf("Abort"), 0, NULL);
|
|
}
|
|
#endif
|
|
|
|
void ReadMediumLinear(gpointer data)
|
|
{ read_closure *rc = g_malloc0(sizeof(read_closure));
|
|
int md5_failure = 0;
|
|
int unrecoverable_sectors = 0;
|
|
GError *err = NULL;
|
|
int nsectors;
|
|
char *t = NULL;
|
|
int status,n;
|
|
int tao_tail;
|
|
int i;
|
|
|
|
/*** This value might be temporarily changed later. */
|
|
|
|
rc->savedSectorSkip = Closure->sectorSkip;
|
|
|
|
/*** Register the cleanup procedure so that Stop() can abort us properly. */
|
|
|
|
rc->unreportedError = TRUE;
|
|
rc->earlyTermination = TRUE;
|
|
rc->scanMode = GPOINTER_TO_INT(data);
|
|
|
|
/* Register with different labels depending on rc->scanMode */
|
|
|
|
register_reader(rc);
|
|
|
|
/*** Timer setup */
|
|
|
|
rc->speedTimer = g_timer_new();
|
|
rc->readTimer = g_timer_new();
|
|
|
|
/*** Mutex for spiral renderer synchronization */
|
|
|
|
rc->rendererMutex = g_malloc(sizeof(GMutex));
|
|
g_mutex_init(rc->rendererMutex);
|
|
|
|
/*** Create the aligned buffers. */
|
|
|
|
for(i=0; i<READ_BUFFERS; i++)
|
|
rc->alignedBuf[i] = CreateAlignedBuffer(MAX_CLUSTER_SIZE);
|
|
|
|
/*** Open Device and query medium properties:
|
|
rc->image will point to the optical medium,
|
|
and possibly the respective ecc file.
|
|
The on disk image is maintained in rc->reader|writerImage. */
|
|
|
|
rc->image = OpenImageFromDevice(Closure->device);
|
|
Closure->readErrors = Closure->crcErrors = rc->readOK = 0;
|
|
|
|
/*** Save some useful information for the missing sector marker */
|
|
|
|
if(rc->image->isoInfo && rc->image->isoInfo->volumeLabel[0])
|
|
rc->volumeLabel = g_strdup(rc->image->isoInfo->volumeLabel);
|
|
|
|
/*** See if we have an ecc file which belongs to the medium,
|
|
and attach it with the Image struct. */
|
|
|
|
OpenEccFileForImage(rc->image, Closure->eccName, O_RDONLY, IMG_PERMS);
|
|
|
|
/*** If ecc information is available in both the image and the
|
|
ecc file, the ecc file gets precedence. */
|
|
|
|
if(rc->image->eccFileMethod)
|
|
{ rc->eccMethod = rc->image->eccFileMethod;
|
|
rc->eccHeader = rc->image->eccFileHeader;
|
|
}
|
|
else if(rc->image->eccMethod)
|
|
{ rc->eccMethod = rc->image->eccMethod;
|
|
rc->eccHeader = rc->image->eccHeader;
|
|
}
|
|
|
|
/*** Some very old versions set eh->inLast to 0 when the last sector
|
|
is completely filled. Set it to 2048 to avoid confusion. */
|
|
|
|
if(rc->eccHeader && rc->eccHeader->inLast == 0)
|
|
rc->eccHeader->inLast = 2048;
|
|
|
|
/*** Make sure we are compatible with the ecc data */
|
|
|
|
if(rc->eccHeader && (Closure->version < rc->eccHeader->neededVersion))
|
|
{
|
|
int answer;
|
|
|
|
if(rc->image->eccFileState == ECCFILE_PRESENT)
|
|
answer = ModalWarningOrCLI(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK_CANCEL, NULL,
|
|
_("This ecc file requires dvdisaster-%d.%d!\n\n"
|
|
"Proceeding could trigger incorrect behaviour.\n"
|
|
"Please read the image without using this ecc file\n"
|
|
"or visit http://www.dvdisaster.org for an upgrade.\n\n"),
|
|
rc->eccHeader->neededVersion/10000,
|
|
(rc->eccHeader->neededVersion%10000)/100);
|
|
else
|
|
answer = ModalWarningOrCLI(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK_CANCEL, NULL,
|
|
_("This image requires dvdisaster-%d.%d!\n\n"
|
|
"Proceeding could trigger incorrect behaviour.\n"
|
|
"Please visit http://www.dvdisaster.org for an upgrade.\n\n"),
|
|
rc->eccHeader->neededVersion/10000,
|
|
(rc->eccHeader->neededVersion%10000)/100);
|
|
|
|
if(!answer)
|
|
{
|
|
#ifndef CLI
|
|
SwitchAndSetFootline(Closure->readLinearNotebook, 1, Closure->readLinearFootline,
|
|
_("<span %s>Aborted by user request!</span>"),
|
|
Closure->redMarkup, rc->readOK,Closure->readErrors);
|
|
#endif
|
|
rc->unreportedError = FALSE; /* suppress respective error message */
|
|
goto terminate;
|
|
}
|
|
}
|
|
|
|
/*** See if user wants to limit the read range. */
|
|
|
|
GetReadingRange(rc->image->dh->sectors, &rc->firstSector, &rc->lastSector);
|
|
if(rc->firstSector > 0) /* Mark skipped sectors */
|
|
#ifndef CLI
|
|
Closure->additionalSpiralColor = 0; /* blue */
|
|
#endif
|
|
|
|
/*** Determine the reading mode. There are three possibilities:
|
|
1. scanning (rc->scanMode == TRUE)
|
|
2. reading into a new image file.
|
|
3. completing an existing image file.
|
|
After this function, files are prepared
|
|
and respective UI messages have been output. */
|
|
|
|
determine_mode(rc);
|
|
|
|
/*** If rc->firstSector > read_marker, fill the gap with dead sector markers. */
|
|
|
|
fill_gap(rc);
|
|
|
|
/*** Allocate and preload the CRC sum cache if necessary */
|
|
|
|
prepare_crc_cache(rc);
|
|
|
|
/*** Allocate a bitmap of read sectors to speed up multiple reading passes */
|
|
|
|
if(Closure->readingPasses > 1)
|
|
rc->readMap = CreateBitmap0(rc->image->dh->sectors);
|
|
|
|
/*** Start the worker thread. We concentrate on reading from the drive here;
|
|
writing the image file and calculating the checksums is done in a
|
|
concurrent thread. */
|
|
|
|
rc->mutex = g_malloc(sizeof(GMutex));
|
|
g_mutex_init(rc->mutex);
|
|
rc->canRead = g_malloc(sizeof(GCond));
|
|
g_cond_init(rc->canRead);
|
|
rc->canWrite = g_malloc(sizeof(GCond));
|
|
g_cond_init(rc->canWrite);
|
|
|
|
rc->worker = g_thread_try_new("readlinear_worker", (GThreadFunc)worker_thread, (gpointer)rc, &err);
|
|
if(!rc->worker)
|
|
Stop("Could not create worker thread: %s", err->message);
|
|
|
|
/*** Prepare the speed timing */
|
|
|
|
prepare_timer(rc);
|
|
|
|
/*** Reset for the next reading pass */
|
|
|
|
rc->lastReadOK = 0; /* keep between passes */
|
|
rc->lastPercent = (1000*rc->readPos)/rc->image->dh->sectors;
|
|
|
|
next_reading_pass:
|
|
if(rc->pass > 0)
|
|
{ Closure->readErrors = Closure->crcErrors = 0;
|
|
switch(rc->image->dh->mainType)
|
|
{ case BD:
|
|
if(Closure->sectorSkip > 32)
|
|
Closure->sectorSkip = 32;
|
|
break;
|
|
case DVD:
|
|
if(Closure->sectorSkip > 16)
|
|
Closure->sectorSkip = 16;
|
|
break;
|
|
default:
|
|
Closure->sectorSkip = 0;
|
|
break;
|
|
}
|
|
Closure->sectorSkip = 0;
|
|
#ifndef CLI
|
|
if(Closure->guiMode)
|
|
MarkExistingSectors();
|
|
#endif
|
|
rc->lastCopied = 0; /* Start rendering the spiral from the beginning */
|
|
}
|
|
|
|
/*** Read the medium image. */
|
|
|
|
rc->readPos = rc->firstSector;
|
|
rc->lastErrorsPrinted = 0;
|
|
rc->previousReadErrors = rc->previousCRCErrors = 0;
|
|
rc->speed = 0;
|
|
rc->lastSpeed = -1.0;
|
|
rc->firstSpeedValue = TRUE;
|
|
tao_tail = 0;
|
|
|
|
while(rc->readPos<=rc->lastSector)
|
|
{ int cluster_mask = rc->image->dh->clusterSize-1;
|
|
|
|
#ifndef CLI
|
|
if(Closure->stopActions) /* somebody hit the Stop button */
|
|
{
|
|
if(Closure->stopActions == STOP_CURRENT_ACTION) /* suppress memleak warning when closing window */
|
|
{ SwitchAndSetFootline(Closure->readLinearNotebook, 1, Closure->readLinearFootline,
|
|
_("<span %s>Aborted by user request!</span> %lld sectors read, %lld sectors unreadable/skipped so far."),
|
|
Closure->redMarkup, rc->readOK,Closure->readErrors);
|
|
}
|
|
rc->unreportedError = FALSE; /* suppress respective error message */
|
|
goto terminate;
|
|
}
|
|
#endif
|
|
|
|
/*** Decide between reading in fast mode (dh->clusterSize sectors at once)
|
|
or reading one sector at a time.
|
|
Fast mode gains some reading speed due to transfering fewer
|
|
but larger data blocks from the device.
|
|
Switching to fast mode is done only on cluster boundaries
|
|
-- this matches the internal structure of DVD and later media better.
|
|
In order to treat the 2 read errors at the end of TAO discs correctly,
|
|
we switch back to per sector reading at the end of the medium. */
|
|
|
|
if( rc->readPos & cluster_mask
|
|
|| rc->readPos >= ((rc->image->dh->sectors - 2) & ~cluster_mask) )
|
|
nsectors = 1;
|
|
else nsectors = rc->image->dh->clusterSize;
|
|
|
|
if(rc->readPos+nsectors > rc->lastSector) /* don't read past the (CD) media end */
|
|
nsectors = rc->lastSector-rc->readPos+1;
|
|
|
|
/*** If rc->readPos is lower than the read marker,
|
|
check if the sector has already been read
|
|
in a previous session. */
|
|
|
|
reread:
|
|
if(!rc->scanMode && rc->readPos < rc->readMarker)
|
|
{ int i,ok = 0;
|
|
int num_compare = nsectors;
|
|
|
|
/* Get image state from bitmap created at earlier reading pass */
|
|
|
|
if(rc->pass && rc->readMap)
|
|
{ for(i=0; i<num_compare; i++)
|
|
if(GetBit(rc->readMap, rc->readPos+i))
|
|
ok++;
|
|
}
|
|
|
|
/* else query dead sectors from image */
|
|
|
|
else
|
|
{ if(!LargeSeek(rc->readerImage, (gint64)(2048*rc->readPos)))
|
|
Stop(_("Failed seeking to sector %lld in image [%s]: %s"),
|
|
rc->readPos, "reread", strerror(errno));
|
|
|
|
if(rc->readPos+nsectors > rc->readMarker)
|
|
num_compare = rc->readMarker-rc->readPos;
|
|
|
|
for(i=0; i<num_compare; i++)
|
|
{ unsigned char sector_buf[2048];
|
|
int err;
|
|
|
|
n = LargeRead(rc->readerImage, sector_buf, 2048);
|
|
if(n != 2048)
|
|
Stop(_("unexpected read error in image for sector %lld"),rc->readPos);
|
|
err = CheckForMissingSector(sector_buf, rc->readPos+i,
|
|
rc->image->fpState == 2 ? rc->image->imageFP : NULL,
|
|
rc->image->fpSector);
|
|
if(err != SECTOR_PRESENT)
|
|
ExplainMissingSector(sector_buf, rc->readPos+i, err, SOURCE_IMAGE, &unrecoverable_sectors);
|
|
else
|
|
{ if(!Closure->crcBuf
|
|
|| CheckAgainstCrcBuffer(Closure->crcBuf, rc->readPos+i, sector_buf) != CRC_BAD)
|
|
{ ok++; /* CRC unavailable or good */
|
|
if(rc->readMap)
|
|
SetBit(rc->readMap, rc->readPos+i);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(ok == nsectors) /* All sectors already present. */
|
|
{
|
|
goto step_counter;
|
|
}
|
|
else /* Some sectors still missing */
|
|
{
|
|
if(nsectors > 1 && ok > 0)
|
|
{ nsectors = 1;
|
|
goto reread;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*** Try reading the next <nsectors> sector(s). */
|
|
|
|
g_mutex_lock(rc->mutex);
|
|
if(rc->workerError) /* something went wrong in the worker thread */
|
|
{ g_mutex_unlock(rc->mutex);
|
|
Stop(rc->workerError);
|
|
}
|
|
while(rc->bufState[rc->readPtr] != BUF_EMPTY)
|
|
{ g_cond_wait(rc->canRead, rc->mutex);
|
|
}
|
|
g_mutex_unlock(rc->mutex);
|
|
|
|
status = ReadSectors(rc->image->dh, rc->alignedBuf[rc->readPtr]->buf, rc->readPos, nsectors);
|
|
|
|
/*** Medium Error (3) and Illegal Request (5) may result from
|
|
a medium read problem, but other errors are regarded as fatal. */
|
|
|
|
if(status && !Closure->ignoreFatalSense
|
|
&& rc->image->dh->sense.sense_key && rc->image->dh->sense.sense_key != 3 && rc->image->dh->sense.sense_key != 5)
|
|
{
|
|
#ifndef CLI
|
|
int answer;
|
|
|
|
if(!Closure->guiMode)
|
|
#endif
|
|
Stop(_("Sector %lld: %s\nCan not recover from above error.\n"
|
|
"Use the --ignore-fatal-sense option to override."),
|
|
rc->readPos, GetLastSenseString(FALSE));
|
|
|
|
#ifndef CLI
|
|
answer = ModalDialog(GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, insert_buttons,
|
|
_("Sector %lld: %s\n\n"
|
|
"It may not be possible to recover from this error.\n"
|
|
"Should the reading continue and ignore this error?"),
|
|
rc->readPos, GetLastSenseString(FALSE));
|
|
|
|
if(answer == 2)
|
|
Closure->ignoreFatalSense = 2;
|
|
|
|
if(!answer)
|
|
{
|
|
SwitchAndSetFootline(Closure->readLinearNotebook, 1, Closure->readLinearFootline,
|
|
_("<span %s>Aborted by user request!</span> %lld sectors read, %lld sectors unreadable/skipped so far."),
|
|
Closure->redMarkup, rc->readOK,Closure->readErrors);
|
|
rc->unreportedError = FALSE; /* suppress respective error message */
|
|
goto terminate;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/*** Evaluate C2 scan results */
|
|
|
|
if(rc->image->dh->canC2Scan)
|
|
{ int i;
|
|
|
|
for(i=0; i<nsectors; i++)
|
|
{ if(rc->image->dh->c2[i])
|
|
{ if(!status) /* Do not print C2 and error messages together */
|
|
PrintCLI(_("Sector %lld: %3d C2 errors.%s\n"),
|
|
rc->readPos+i, rc->image->dh->c2[i], " ");
|
|
|
|
if(rc->image->dh->c2[i] > rc->maxC2) /* remember highest value */
|
|
rc->maxC2 = rc->image->dh->c2[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
/*** Warn the user if we see dead sector markers on the image. */
|
|
|
|
if(!status)
|
|
{ for(i=0; i<nsectors; i++)
|
|
{ unsigned char *sector_buf = rc->alignedBuf[rc->readPtr]->buf;
|
|
int err;
|
|
|
|
/* Note: providing the fingerprint is not necessary as any
|
|
incoming missing sector marker indicates a huge problem. */
|
|
|
|
err = CheckForMissingSector(sector_buf+i*2048, rc->readPos+i,
|
|
rc->image->fpState == 2 ? rc->image->imageFP : NULL,
|
|
rc->image->fpSector);
|
|
if(err != SECTOR_PRESENT)
|
|
ExplainMissingSector(sector_buf+i*2048, rc->readPos+i, err, SOURCE_MEDIUM, &unrecoverable_sectors);
|
|
}
|
|
}
|
|
|
|
/*** Pass sector(s) to the worker thread (if reading succeeded) */
|
|
|
|
if(!status)
|
|
{ gint64 sidx;
|
|
|
|
/* Mark the sectors as read (preliminary).
|
|
The worker thread may later reset the bit if it finds
|
|
a CRC error. Careful: Do this here before the worker
|
|
is invoked; else we get a nice race condition setting/
|
|
unsetting the bit on CRC errors */
|
|
|
|
if(rc->readMap)
|
|
for(sidx=rc->readPos, i=0; i<nsectors; sidx++,i++)
|
|
SetBit(rc->readMap, sidx);
|
|
|
|
/* Kick off the worker thread */
|
|
|
|
g_mutex_lock(rc->mutex);
|
|
rc->bufferedSector[rc->readPtr] = rc->readPos;
|
|
rc->nSectors[rc->readPtr] = nsectors;
|
|
rc->bufState[rc->readPtr] = BUF_FULL;
|
|
rc->readPtr++;
|
|
if(rc->readPtr >= READ_BUFFERS)
|
|
rc->readPtr = 0;
|
|
g_cond_signal(rc->canWrite);
|
|
g_mutex_unlock(rc->mutex);
|
|
|
|
rc->readOK += nsectors;
|
|
|
|
}
|
|
|
|
/*** Process the read error if reading failed. */
|
|
|
|
if(status)
|
|
{ int nfill;
|
|
|
|
/* Disable on the fly checksum calculation.
|
|
Do NOT free the CRC cache here to avoid race condition
|
|
with the worker thread! */
|
|
|
|
if(Closure->crcBuf)
|
|
{ rc->doChecksumsFromCodec = FALSE;
|
|
Closure->crcBuf->md5State = MD5_INVALID;
|
|
}
|
|
|
|
/* Determine number of sectors to skip forward.
|
|
Make sure not to skip past the media end
|
|
and to land at a multiple of dh->clusterSize. */
|
|
|
|
if(nsectors>=Closure->sectorSkip) nfill = nsectors;
|
|
else
|
|
{ int skip = rc->image->dh->clusterSize > Closure->sectorSkip ? rc->image->dh->clusterSize : Closure->sectorSkip;
|
|
if(rc->readPos+skip > rc->lastSector) nfill = rc->lastSector-rc->readPos+1;
|
|
else nfill = skip - ((rc->readPos + skip) & cluster_mask);
|
|
}
|
|
|
|
/* If we are reading past the read marker we must take care
|
|
to fill up any holes with dead sector markers before skipping forward.
|
|
When sectorSkip is 0 and nsectors > 1, we will re-read all these sectors
|
|
again one by one, so we catch this case in order not to write the markers twice. */
|
|
|
|
if(!rc->scanMode && rc->readPos+nfill > rc->readMarker
|
|
&& (Closure->sectorSkip || nsectors == 1))
|
|
{ int i;
|
|
|
|
/* Write nfill of "dead sector" markers so that
|
|
they are tried again in the following iterations / sessions. */
|
|
|
|
for(i=0; i<nfill; i++)
|
|
{
|
|
g_mutex_lock(rc->mutex);
|
|
if(rc->workerError) /* something went wrong in the worker thread */
|
|
{ g_mutex_unlock(rc->mutex);
|
|
Stop(rc->workerError);
|
|
}
|
|
|
|
while(rc->bufState[rc->readPtr] != BUF_EMPTY)
|
|
{ g_cond_wait(rc->canRead, rc->mutex);
|
|
}
|
|
|
|
CreateMissingSector(rc->alignedBuf[rc->readPtr]->buf, rc->readPos+i,
|
|
rc->image->imageFP, FINGERPRINT_SECTOR, rc->volumeLabel);
|
|
|
|
rc->bufferedSector[rc->readPtr] = rc->readPos+i;
|
|
rc->nSectors[rc->readPtr] = 1;
|
|
rc->bufState[rc->readPtr] = BUF_DEAD;
|
|
rc->readPtr++;
|
|
if(rc->readPtr >= READ_BUFFERS)
|
|
rc->readPtr = 0;
|
|
g_cond_signal(rc->canWrite);
|
|
g_mutex_unlock(rc->mutex);
|
|
}
|
|
}
|
|
|
|
/* If sectorSkip is set, perform the skip.
|
|
nfill has been calculated so that the skip lands
|
|
at a multiple of dh->clusterSize. Therefore nsectors can remain
|
|
at its former value as skipping forward will not
|
|
destroy cluster size aligned reads.
|
|
The nsectors>1 case can only happen when processing the tao_tail. */
|
|
|
|
if(Closure->sectorSkip && nsectors > 1)
|
|
{ int i;
|
|
|
|
PrintCLIorLabel(
|
|
#ifndef CLI
|
|
Closure->status,
|
|
#else
|
|
NULL,
|
|
#endif
|
|
_("Sector %lld: %s Skipping %d sectors.\n"),
|
|
rc->readPos, GetLastSenseString(FALSE), nfill-1);
|
|
for(i=0; i<nfill; i++) /* workaround: large values for nfill */
|
|
{ Closure->readErrors++; /* would exceed sampling of green/red */
|
|
rc->readPos++; /* in the spiral. Internal NOTE01 */
|
|
show_progress(rc);
|
|
}
|
|
rc->readPos-=nsectors; /* nsectors will be added again after the goto */
|
|
#if 0
|
|
Closure->readErrors+=nfill; /* pre-workaround code */
|
|
rc->readPos+=nfill-nsectors; /* nsectors will be added again after the goto */
|
|
#endif
|
|
goto step_counter;
|
|
}
|
|
|
|
/* However, if no sector skipping is requested
|
|
and we are currently in fast read mode (nsectors > 1),
|
|
slow down to reading 1 sectors at once
|
|
and try to re-read the first sector. */
|
|
|
|
else
|
|
{
|
|
if(nsectors > 1)
|
|
{ nsectors = 1;
|
|
goto reread;
|
|
}
|
|
else
|
|
{ PrintCLIorLabel(
|
|
#ifndef CLI
|
|
Closure->status,
|
|
#else
|
|
NULL,
|
|
#endif
|
|
_("Sector %lld: %s\n"),
|
|
rc->readPos, GetLastSenseString(FALSE));
|
|
if(rc->readPos >= rc->image->dh->sectors - 2) tao_tail++;
|
|
Closure->readErrors++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*** Step the progress counter */
|
|
|
|
step_counter:
|
|
rc->readPos += nsectors; /* advance the reading position */
|
|
|
|
show_progress(rc);
|
|
}
|
|
|
|
/*** If multiple reading passes are allowed, see if we need another pass.
|
|
Note: Checksum errors do not trigger another pass as only sectors
|
|
marked dead are tried on a re-read. This does not hurt as being able
|
|
to checksum means we have ecc data - we can fix the image using ecc
|
|
rather than by re-reading it. */
|
|
|
|
#ifndef CLI
|
|
if(Closure->guiMode)
|
|
ChangeSpiralCursor(Closure->readLinearSpiral, -1); /* switch cursor off */
|
|
#endif
|
|
|
|
rc->pass++;
|
|
rc->image->dh->pass = rc->pass;
|
|
|
|
if( !rc->scanMode
|
|
&& (Closure->readErrors || Closure->crcErrors)
|
|
&& rc->pass < Closure->readingPasses)
|
|
{ int renderers_left = TRUE;
|
|
|
|
#ifndef CLI
|
|
if(Closure->guiMode)
|
|
SetLabelText(GTK_LABEL(Closure->readLinearHeadline),
|
|
_("<big>Trying to complete image, reading pass %d of %d.</big>\n%s"),
|
|
rc->pass+1, Closure->readingPasses, rc->image->dh->mediumDescr);
|
|
else
|
|
#endif
|
|
PrintCLI(_("\nTrying to complete image, reading pass %d of %d.\n"),
|
|
rc->pass+1, Closure->readingPasses);
|
|
|
|
|
|
/* Make sure that all spiral renderers have finished before resetting
|
|
and starting the next iteration. */
|
|
|
|
while(renderers_left)
|
|
{ g_mutex_lock(rc->rendererMutex);
|
|
if(rc->activeRenderers<=0)
|
|
renderers_left=FALSE;
|
|
g_mutex_unlock(rc->rendererMutex);
|
|
|
|
g_usleep(G_USEC_PER_SEC/10);
|
|
}
|
|
|
|
goto next_reading_pass;
|
|
}
|
|
|
|
/*** Signal EOF to writer thread; wait for it to finish */
|
|
|
|
send_eof(rc);
|
|
g_thread_join(rc->worker);
|
|
rc->worker = NULL;
|
|
|
|
/*** Finalize on-the-fly checksum calculation */
|
|
|
|
if(rc->doChecksumsFromCodec && rc->eccMethod)
|
|
md5_failure = rc->eccMethod->finalizeCksums(rc->image);
|
|
|
|
/*** Print summary */
|
|
|
|
t = NULL;
|
|
|
|
/* We were re-reading an incomplete image */
|
|
|
|
if(rc->rereading)
|
|
{ if(!Closure->readErrors) t = g_strdup_printf(_("%lld sectors read. "),rc->readOK);
|
|
else t = g_strdup_printf(_("%lld sectors read; %lld unreadable sectors."),rc->readOK,Closure->readErrors);
|
|
}
|
|
|
|
/* We were reading the image for the first time */
|
|
|
|
else /* not rereading */
|
|
{ /* Image was fully readable and had no CRC errors (if CRC was available at all!).
|
|
So we had no faulty sectors, but ... */
|
|
if(!Closure->readErrors && !Closure->crcErrors)
|
|
{
|
|
if(rc->image->eccFile) /* ...maybe wrong image size? */
|
|
{ if(rc->image->dh->sectors != rc->image->expectedSectors)
|
|
t = g_strdup_printf(_("All sectors successfully read, but wrong image length (%lld sectors difference)"), rc->image->dh->sectors - rc->image->expectedSectors);
|
|
}
|
|
/* ...or bad ecc md5 sum (theoretically impossible by now)? */
|
|
if( rc->readOK == rc->image->dh->sectors /* no user limited range */
|
|
&& rc->pass == 1 /* md5sum invalid after first reading pass */
|
|
&& md5_failure && !t) /* and we're signalled a mismatch */
|
|
{ t = g_strdup(_("All sectors successfully read, but wrong ecc md5sum."));
|
|
}
|
|
|
|
if(!t)
|
|
{ if(rc->eccMethod) /* we had CRC sums to compare against */
|
|
{ if(rc->crcIncomplete)
|
|
t = g_strdup(_("All sectors successfully read, but incomplete or damaged checksums."));
|
|
else t = g_strdup(_("All sectors successfully read. Checksums match."));
|
|
}
|
|
else t = g_strdup(_("All sectors successfully read."));
|
|
}
|
|
}
|
|
else /* we have unreadable or damaged sectors */
|
|
{ if(Closure->readErrors && !Closure->crcErrors)
|
|
t = g_strdup_printf(_("%lld unreadable sectors."),Closure->readErrors);
|
|
else if(!Closure->readErrors && Closure->crcErrors)
|
|
{ if(md5_failure & CRC_MD5_BAD)
|
|
t = g_strdup_printf(_("%lld CRC errors and a md5sum mismatch in the CRC section."),Closure->crcErrors);
|
|
else
|
|
t = g_strdup_printf(_("%lld CRC errors."),Closure->crcErrors);
|
|
}
|
|
else t = g_strdup_printf(_("%lld CRC errors, %lld unreadable sectors."),
|
|
Closure->crcErrors, Closure->readErrors);
|
|
}
|
|
}
|
|
|
|
PrintLog("\n%s\n",t);
|
|
#ifndef CLI
|
|
if(Closure->guiMode)
|
|
{ if(rc->scanMode) SwitchAndSetFootline(Closure->readLinearNotebook, 1, Closure->readLinearFootline,
|
|
"%s%s",_("Scanning finished: "),t);
|
|
else SwitchAndSetFootline(Closure->readLinearNotebook, 1, Closure->readLinearFootline,
|
|
"%s%s",_("Reading finished: "),t);
|
|
}
|
|
#endif
|
|
if(t) g_free(t);
|
|
|
|
if(!Closure->fixedSpeedValues)
|
|
PrintTimeToLog(rc->readTimer, "for reading/scanning.\n");
|
|
|
|
if(rc->image->dh->mainType == CD && tao_tail && tao_tail == Closure->readErrors && !Closure->noTruncate)
|
|
{ int answer;
|
|
|
|
#ifndef CLI
|
|
if(Closure->guiMode)
|
|
answer = ModalWarningOrCLI(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK_CANCEL, NULL,
|
|
_("%d sectors missing at the end of the disc.\n"
|
|
"This is okay if the CD was written in TAO (track at once) mode.\n"
|
|
"The Image will be truncated accordingly. See the manual for details.\n"),
|
|
tao_tail);
|
|
else
|
|
#endif
|
|
answer = ModalWarningOrCLI(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK_CANCEL, NULL,
|
|
_("%d sectors missing at the end of the disc.\n"
|
|
"This is okay if the CD was written in TAO (track at once) mode.\n"
|
|
"The Image will be truncated accordingly. See the manual for details.\n"
|
|
"Use the --dao option to disable image truncating.\n"),
|
|
tao_tail);
|
|
|
|
if(!rc->scanMode && answer)
|
|
if(!LargeTruncate(rc->writerImage, (gint64)(2048*(rc->image->dh->sectors-tao_tail))))
|
|
Stop(_("Could not truncate %s: %s\n"),Closure->imageName,strerror(errno));
|
|
}
|
|
else if(Closure->readErrors) exitCode = EXIT_FAILURE;
|
|
|
|
/*** Eject medium */
|
|
|
|
if(Closure->eject && !Closure->readErrors)
|
|
LoadMedium(rc->image->dh, FALSE);
|
|
|
|
/*** Close and clean up */
|
|
|
|
if(Closure->readErrors || Closure->crcErrors)
|
|
Closure->crcBuf->md5State = MD5_INVALID;
|
|
|
|
rc->unreportedError = FALSE;
|
|
rc->earlyTermination = FALSE;
|
|
|
|
terminate:
|
|
PrintCrcBuf(Closure->crcBuf);
|
|
cleanup((gpointer)rc);
|
|
}
|