mirror of
https://github.com/martijnvanbrummelen/nwipe.git
synced 2026-02-25 08:22:11 +00:00
This fixes the following problems. 1. Fdatasync is incorrectly called when in direct I/O mode when no --directio command line option is present, i.e when it automatically determines whether direct or cached should be used. 2. When multiple drives are wiped if any of those drives do not support direct I/O then they would have would have had fdatasync incorrectly disabled. There was no record of I/O mode on a per drive basis. 3. Direct I/O on a static pass always called fdatasync as there was no code present to set the SyncRate to 0 when in direct I/O mode.
1040 lines
30 KiB
C
1040 lines
30 KiB
C
/*
|
|
* pass.c: Routines that read and write patterns to block devices.
|
|
*
|
|
* Copyright Darik Horn <dajhorn-dban@vanadac.com>.
|
|
*
|
|
* Modifications to original dwipe Copyright Andy Beverley <andy@andybev.com>
|
|
*
|
|
* This program 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, version 2.
|
|
*
|
|
* This program 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
|
|
* this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
*/
|
|
|
|
#define _POSIX_C_SOURCE 200809L
|
|
|
|
#include <stdint.h>
|
|
#include <stdlib.h> /* posix_memalign, malloc, free */
|
|
#include <string.h> /* memset, memcpy, memcmp */
|
|
#include <errno.h>
|
|
#include <limits.h>
|
|
|
|
#include "nwipe.h"
|
|
#include "context.h"
|
|
#include "method.h"
|
|
#include "prng.h"
|
|
#include "options.h"
|
|
#include "pass.h"
|
|
#include "logging.h"
|
|
#include "gui.h"
|
|
|
|
/*
|
|
* Tunable sizes for the wiping / verification I/O path.
|
|
*
|
|
* NWIPE_BUFFER_SIZE:
|
|
* - Size of the generic scratch buffer used by passes.
|
|
* - Default is 16 MiB, which is a good compromise between memory usage and
|
|
* reducing syscall count.
|
|
*
|
|
* NWIPE_IO_BLOCKSIZE:
|
|
* - Target size of individual read()/write() operations.
|
|
* - Default is 4 MiB, so each syscall moves a lot of data instead of only
|
|
* 4 KiB, drastically reducing syscall overhead.
|
|
*
|
|
* Notes:
|
|
* - We do NOT depend on O_DIRECT here; all code works fine with normal,
|
|
* buffered I/O.
|
|
* - But all I/O buffers are allocated aligned to the device block size so
|
|
* that the same code also works with O_DIRECT when the device is opened
|
|
* with it.
|
|
*/
|
|
#ifndef NWIPE_BUFFER_SIZE
|
|
#define NWIPE_BUFFER_SIZE ( 16 * 1024 * 1024UL ) /* 16 MiB generic buffer */
|
|
#endif
|
|
|
|
#ifndef NWIPE_IO_BLOCKSIZE
|
|
#define NWIPE_IO_BLOCKSIZE ( 4 * 1024 * 1024UL ) /* 4 MiB I/O block */
|
|
#endif
|
|
|
|
/*
|
|
* Compute the effective I/O block size for a given device:
|
|
*
|
|
* - Must be at least the device's reported st_blksize (usually 4 KiB).
|
|
* - Starts from NWIPE_IO_BLOCKSIZE (4 MiB by default) and adjusts.
|
|
* - Rounded down to a multiple of st_blksize so it is compatible with
|
|
* O_DIRECT alignment rules.
|
|
* - Never exceeds the device size.
|
|
*/
|
|
static size_t nwipe_effective_io_blocksize( const nwipe_context_t* c )
|
|
{
|
|
size_t bs = (size_t) c->device_stat.st_blksize;
|
|
|
|
if( bs == 0 )
|
|
{
|
|
/* Should not happen for normal block devices; use a sane default. */
|
|
bs = 4096;
|
|
}
|
|
|
|
size_t io_bs = (size_t) NWIPE_IO_BLOCKSIZE;
|
|
|
|
if( io_bs < bs )
|
|
{
|
|
io_bs = bs;
|
|
}
|
|
|
|
/* Round down to a multiple of the device block size. */
|
|
if( io_bs % bs != 0 )
|
|
{
|
|
io_bs -= ( io_bs % bs );
|
|
}
|
|
|
|
if( io_bs == 0 )
|
|
{
|
|
io_bs = bs;
|
|
}
|
|
|
|
if( (u64) io_bs > c->device_size )
|
|
{
|
|
io_bs = (size_t) c->device_size;
|
|
}
|
|
|
|
return io_bs;
|
|
}
|
|
|
|
/*
|
|
* Allocate an I/O buffer aligned to the device block size.
|
|
*
|
|
* This is done with posix_memalign() so that the buffer can safely be used
|
|
* with O_DIRECT if the device was opened with it. The same allocation is also
|
|
* perfectly fine for normal buffered I/O.
|
|
*
|
|
* Parameters:
|
|
* c - device context (for block size / logging)
|
|
* size - number of bytes to allocate
|
|
* clear - if non-zero, the buffer is zeroed after allocation
|
|
* label - short description for logging
|
|
*/
|
|
static void* nwipe_alloc_io_buffer( const nwipe_context_t* c, size_t size, int clear, const char* label )
|
|
{
|
|
size_t align = (size_t) c->device_stat.st_blksize;
|
|
if( align < 512 )
|
|
{
|
|
/* O_DIRECT usually requires at least 512-byte alignment. */
|
|
align = 512;
|
|
}
|
|
|
|
void* ptr = NULL;
|
|
int rc = posix_memalign( &ptr, align, size );
|
|
if( rc != 0 || ptr == NULL )
|
|
{
|
|
nwipe_log( NWIPE_LOG_FATAL,
|
|
"%s: posix_memalign failed for %s (size=%zu, align=%zu, rc=%d).",
|
|
__FUNCTION__,
|
|
label,
|
|
size,
|
|
align,
|
|
rc );
|
|
return NULL;
|
|
}
|
|
|
|
if( clear )
|
|
{
|
|
memset( ptr, 0, size );
|
|
}
|
|
|
|
return ptr;
|
|
}
|
|
|
|
/*
|
|
* Compute the per-write sync rate for a given device and I/O block size.
|
|
*
|
|
* Historically, --sync=N meant "fdatasync() every N * st_blksize bytes".
|
|
* Now that we use large I/O blocks, we convert that into "sync every K writes",
|
|
* where each write is of size io_blocksize.
|
|
*
|
|
* For O_DIRECT we return 0 because write() already reports I/O errors directly.
|
|
*/
|
|
static int nwipe_compute_sync_rate_for_device( const nwipe_context_t* c, size_t io_blocksize )
|
|
{
|
|
int syncRate = nwipe_options.sync;
|
|
|
|
/* No periodic sync in direct I/O mode. */
|
|
if( nwipe_options.io_mode == NWIPE_IO_MODE_DIRECT )
|
|
return 0;
|
|
|
|
if( syncRate <= 0 )
|
|
return 0;
|
|
|
|
if( io_blocksize == 0 )
|
|
return 0;
|
|
|
|
/* Old semantics: bytes between syncs = sync * st_blksize. */
|
|
unsigned long long bytes_between_sync =
|
|
(unsigned long long) syncRate * (unsigned long long) c->device_stat.st_blksize;
|
|
|
|
if( bytes_between_sync == 0 )
|
|
return 0;
|
|
|
|
/* Convert to "writes between syncs". */
|
|
unsigned long long tmp = bytes_between_sync / (unsigned long long) io_blocksize;
|
|
|
|
if( tmp == 0 )
|
|
return 1; /* at least every write */
|
|
|
|
if( tmp > (unsigned long long) INT_MAX )
|
|
return INT_MAX; /* just in case */
|
|
|
|
return (int) tmp;
|
|
}
|
|
|
|
/*
|
|
* nwipe_random_verify
|
|
*
|
|
* Verifies that a random pass was correctly written to the device.
|
|
* The PRNG is re-seeded with the stored seed, and the same random byte
|
|
* stream is generated again and compared against what is read from disk.
|
|
*
|
|
* This version uses large I/O blocks (e.g. 4 MiB) instead of tiny
|
|
* st_blksize-sized chunks to reduce syscall overhead and speed up verification.
|
|
*/
|
|
int nwipe_random_verify( nwipe_context_t* c )
|
|
{
|
|
int r;
|
|
size_t blocksize;
|
|
size_t io_blocksize;
|
|
off64_t offset;
|
|
char* b; /* input buffer from device */
|
|
char* d; /* pattern buffer generated by PRNG */
|
|
u64 z = c->device_size; /* bytes remaining in this pass */
|
|
|
|
if( c->prng_seed.s == NULL )
|
|
{
|
|
nwipe_log( NWIPE_LOG_SANITY, "Null seed pointer." );
|
|
return -1;
|
|
}
|
|
|
|
if( c->prng_seed.length <= 0 )
|
|
{
|
|
nwipe_log( NWIPE_LOG_SANITY, "The entropy length member is %i.", c->prng_seed.length );
|
|
return -1;
|
|
}
|
|
|
|
io_blocksize = nwipe_effective_io_blocksize( c );
|
|
|
|
/* Allocate I/O buffers of the chosen block size (aligned for possible O_DIRECT). */
|
|
b = (char*) nwipe_alloc_io_buffer( c, io_blocksize, 0, "random_verify input buffer" );
|
|
if( !b )
|
|
return -1;
|
|
|
|
d = (char*) nwipe_alloc_io_buffer( c, io_blocksize, 0, "random_verify pattern buffer" );
|
|
if( !d )
|
|
{
|
|
free( b );
|
|
return -1;
|
|
}
|
|
|
|
/* Rewind device to the beginning. */
|
|
offset = lseek( c->device_fd, 0, SEEK_SET );
|
|
c->pass_done = 0;
|
|
|
|
if( offset == (off64_t) -1 )
|
|
{
|
|
nwipe_perror( errno, __FUNCTION__, "lseek" );
|
|
nwipe_log( NWIPE_LOG_FATAL, "Unable to reset the '%s' file offset.", c->device_name );
|
|
free( b );
|
|
free( d );
|
|
return -1;
|
|
}
|
|
|
|
if( offset != 0 )
|
|
{
|
|
nwipe_log( NWIPE_LOG_SANITY, "lseek() returned a bogus offset on '%s'.", c->device_name );
|
|
free( b );
|
|
free( d );
|
|
return -1;
|
|
}
|
|
|
|
/* Ensure all previous writes are on disk before we verify. */
|
|
c->sync_status = 1;
|
|
r = fdatasync( c->device_fd );
|
|
c->sync_status = 0;
|
|
|
|
if( r != 0 )
|
|
{
|
|
nwipe_perror( errno, __FUNCTION__, "fdatasync" );
|
|
nwipe_log( NWIPE_LOG_WARNING, "Buffer flush failure on '%s'.", c->device_name );
|
|
c->fsyncdata_errors++;
|
|
}
|
|
|
|
/* Reseed the PRNG so it produces the same stream as during the pass. */
|
|
c->prng->init( &c->prng_state, &c->prng_seed );
|
|
|
|
while( z > 0 )
|
|
{
|
|
if( z >= (u64) io_blocksize )
|
|
{
|
|
blocksize = io_blocksize;
|
|
}
|
|
else
|
|
{
|
|
blocksize = (size_t) z;
|
|
|
|
/* Seatbelt: device size should normally be a multiple of st_blksize. */
|
|
if( (u64) c->device_stat.st_blksize > z )
|
|
{
|
|
nwipe_log( NWIPE_LOG_WARNING,
|
|
"%s: The size of '%s' is not a multiple of its block size %i.",
|
|
__FUNCTION__,
|
|
c->device_name,
|
|
c->device_stat.st_blksize );
|
|
}
|
|
}
|
|
|
|
/* Generate expected random data into pattern buffer. */
|
|
c->prng->read( &c->prng_state, d, blocksize );
|
|
|
|
/* Read data from device. */
|
|
r = (int) read( c->device_fd, b, blocksize );
|
|
if( r < 0 )
|
|
{
|
|
nwipe_perror( errno, __FUNCTION__, "read" );
|
|
nwipe_log( NWIPE_LOG_ERROR, "Unable to read from '%s'.", c->device_name );
|
|
free( b );
|
|
free( d );
|
|
return -1;
|
|
}
|
|
|
|
if( r != (int) blocksize )
|
|
{
|
|
/*
|
|
* Partial reads are treated as warnings and verification errors.
|
|
* We keep the semantics of the original code: increment error
|
|
* counter and try to skip forward by the missing amount.
|
|
*/
|
|
int s = (int) blocksize - r;
|
|
|
|
nwipe_log(
|
|
NWIPE_LOG_WARNING, "%s: Partial read from '%s', %i bytes short.", __FUNCTION__, c->device_name, s );
|
|
|
|
c->verify_errors += 1;
|
|
|
|
offset = lseek( c->device_fd, s, SEEK_CUR );
|
|
if( offset == (off64_t) -1 )
|
|
{
|
|
nwipe_perror( errno, __FUNCTION__, "lseek" );
|
|
nwipe_log(
|
|
NWIPE_LOG_ERROR, "Unable to bump the '%s' file offset after a partial read.", c->device_name );
|
|
free( b );
|
|
free( d );
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* Compare the bytes we actually read (r) against the generated pattern. */
|
|
if( r > 0 && memcmp( b, d, (size_t) r ) != 0 )
|
|
{
|
|
c->verify_errors += 1;
|
|
}
|
|
|
|
z -= (u64) r;
|
|
c->pass_done += r;
|
|
c->round_done += r;
|
|
|
|
pthread_testcancel();
|
|
|
|
} /* while bytes remaining */
|
|
|
|
free( b );
|
|
free( d );
|
|
|
|
return 0;
|
|
|
|
} /* nwipe_random_verify */
|
|
|
|
/*
|
|
* nwipe_random_pass
|
|
*
|
|
* Writes a random pattern to the device using the configured PRNG.
|
|
*
|
|
* This version uses:
|
|
* - a generic buffer (default 16 MiB), zero-initialized to avoid leaking
|
|
* previous memory content in case of PRNG bugs, and
|
|
* - large write() calls (default 4 MiB per syscall) instead of tiny
|
|
* st_blksize-sized writes.
|
|
*
|
|
* The PRNG interface (init/read) and the integrity check that verifies that
|
|
* the PRNG produced non-zero data for the first block are kept intact.
|
|
*/
|
|
int nwipe_random_pass( NWIPE_METHOD_SIGNATURE )
|
|
{
|
|
int r;
|
|
size_t blocksize;
|
|
size_t io_blocksize;
|
|
size_t bufsize;
|
|
off64_t offset;
|
|
char* b;
|
|
u64 z = c->device_size; /* bytes remaining */
|
|
|
|
int syncRate = nwipe_options.sync;
|
|
|
|
/* Select effective I/O block size (e.g. 4 MiB, never smaller than st_blksize). */
|
|
io_blocksize = nwipe_effective_io_blocksize( c );
|
|
|
|
/* For direct I/O we do not need periodic fdatasync(), I/O errors are detected
|
|
* at write() time. Keep sync for cached I/O only. */
|
|
|
|
if( c->io_mode == NWIPE_IO_MODE_DIRECT )
|
|
{
|
|
syncRate = 0;
|
|
nwipe_log( NWIPE_LOG_NOTICE, "Disabled fdatasync for %s, DirectI/O in use.", c->device_name );
|
|
}
|
|
else /* for cached I/O only */
|
|
{
|
|
/* Compute the per-write sync rate based on io_blocksize and old semantics. */
|
|
syncRate = nwipe_compute_sync_rate_for_device( c, io_blocksize );
|
|
}
|
|
|
|
int i = 0;
|
|
int idx;
|
|
|
|
if( c->prng_seed.s == NULL )
|
|
{
|
|
nwipe_log( NWIPE_LOG_SANITY, "__FUNCTION__: Null seed pointer." );
|
|
return -1;
|
|
}
|
|
|
|
if( c->prng_seed.length <= 0 )
|
|
{
|
|
nwipe_log( NWIPE_LOG_SANITY, "__FUNCTION__: The entropy length member is %i.", c->prng_seed.length );
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Allocate a generic 16 MiB buffer (by default) that is used as the
|
|
* scratch area for random data. We will only fill and write "blocksize"
|
|
* bytes per iteration, which is at most io_blocksize.
|
|
*/
|
|
bufsize = (size_t) NWIPE_BUFFER_SIZE;
|
|
if( bufsize < io_blocksize )
|
|
bufsize = io_blocksize;
|
|
|
|
b = (char*) nwipe_alloc_io_buffer( c, bufsize, 1, "random_pass output buffer" );
|
|
if( !b )
|
|
return -1;
|
|
|
|
/* Seed the PRNG for this pass. */
|
|
c->prng->init( &c->prng_state, &c->prng_seed );
|
|
|
|
/* Rewind device. */
|
|
offset = lseek( c->device_fd, 0, SEEK_SET );
|
|
c->pass_done = 0;
|
|
|
|
if( offset == (off64_t) -1 )
|
|
{
|
|
nwipe_perror( errno, __FUNCTION__, "lseek" );
|
|
nwipe_log( NWIPE_LOG_FATAL, "Unable to reset the '%s' file offset.", c->device_name );
|
|
free( b );
|
|
return -1;
|
|
}
|
|
|
|
if( offset != 0 )
|
|
{
|
|
nwipe_log( NWIPE_LOG_SANITY, "__FUNCTION__: lseek() returned a bogus offset on '%s'.", c->device_name );
|
|
free( b );
|
|
return -1;
|
|
}
|
|
|
|
while( z > 0 )
|
|
{
|
|
/*
|
|
* Use large writes of size "io_blocksize" as long as enough data is
|
|
* left. The final iteration may use a smaller block if the device size
|
|
* is not an exact multiple.
|
|
*/
|
|
if( z >= (u64) io_blocksize )
|
|
{
|
|
blocksize = io_blocksize;
|
|
}
|
|
else
|
|
{
|
|
blocksize = (size_t) z;
|
|
|
|
if( (u64) c->device_stat.st_blksize > z )
|
|
{
|
|
nwipe_log( NWIPE_LOG_WARNING,
|
|
"%s: The size of '%s' is not a multiple of its block size %i.",
|
|
__FUNCTION__,
|
|
c->device_name,
|
|
c->device_stat.st_blksize );
|
|
}
|
|
}
|
|
|
|
/* Ask the PRNG to fill "blocksize" bytes into the output buffer. */
|
|
c->prng->read( &c->prng_state, b, blocksize );
|
|
|
|
/*
|
|
* For the first block only, verify that the PRNG actually wrote
|
|
* something non-zero into the buffer. This preserves the original
|
|
* sanity check but works even if the I/O block size is larger than
|
|
* st_blksize.
|
|
*/
|
|
if( z == c->device_size )
|
|
{
|
|
size_t check_len = (size_t) c->device_stat.st_blksize;
|
|
if( check_len > blocksize )
|
|
check_len = blocksize;
|
|
|
|
idx = (int) check_len - 1;
|
|
|
|
while( idx >= 0 )
|
|
{
|
|
if( b[idx] != 0 )
|
|
{
|
|
nwipe_log( NWIPE_LOG_NOTICE, "prng stream is active" );
|
|
break;
|
|
}
|
|
idx--;
|
|
}
|
|
if( idx < 0 )
|
|
{
|
|
nwipe_log( NWIPE_LOG_FATAL, "ERROR, prng wrote nothing to the buffer" );
|
|
if( c->bytes_erased < ( c->device_size - z ) )
|
|
{
|
|
c->bytes_erased = c->device_size - z;
|
|
}
|
|
free( b );
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* Write the generated random data to the device. */
|
|
r = (int) write( c->device_fd, b, blocksize );
|
|
|
|
if( r < 0 )
|
|
{
|
|
nwipe_perror( errno, __FUNCTION__, "write" );
|
|
nwipe_log( NWIPE_LOG_FATAL, "Unable to write to '%s'.", c->device_name );
|
|
if( c->bytes_erased < ( c->device_size - z ) )
|
|
{
|
|
c->bytes_erased = c->device_size - z;
|
|
}
|
|
free( b );
|
|
return -1;
|
|
}
|
|
|
|
if( r != (int) blocksize )
|
|
{
|
|
/*
|
|
* Partial writes are rare on block devices, but they can happen.
|
|
* We keep the original behavior: count the missing bytes as
|
|
* errors and try to skip forward by that amount.
|
|
*/
|
|
int s = (int) blocksize - r;
|
|
|
|
c->pass_errors += s;
|
|
|
|
nwipe_log( NWIPE_LOG_WARNING, "Partial write on '%s', %i bytes short.", c->device_name, s );
|
|
|
|
offset = lseek( c->device_fd, s, SEEK_CUR );
|
|
if( offset == (off64_t) -1 )
|
|
{
|
|
nwipe_perror( errno, __FUNCTION__, "lseek" );
|
|
nwipe_log(
|
|
NWIPE_LOG_ERROR, "Unable to bump the '%s' file offset after a partial write.", c->device_name );
|
|
if( c->bytes_erased < ( c->device_size - z ) )
|
|
{
|
|
c->bytes_erased = c->device_size - z;
|
|
}
|
|
free( b );
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
z -= (u64) r;
|
|
c->pass_done += r;
|
|
c->round_done += r;
|
|
|
|
/* Periodic fdatasync after 'syncRate' writes, if configured. */
|
|
if( syncRate > 0 )
|
|
{
|
|
i++;
|
|
|
|
if( i >= syncRate )
|
|
{
|
|
c->sync_status = 1;
|
|
r = fdatasync( c->device_fd );
|
|
c->sync_status = 0;
|
|
|
|
if( r != 0 )
|
|
{
|
|
nwipe_perror( errno, __FUNCTION__, "fdatasync" );
|
|
nwipe_log( NWIPE_LOG_WARNING, "Buffer flush failure on '%s'.", c->device_name );
|
|
nwipe_log( NWIPE_LOG_WARNING, "Wrote %llu bytes on '%s'.", c->pass_done, c->device_name );
|
|
c->fsyncdata_errors++;
|
|
free( b );
|
|
if( c->bytes_erased < ( c->device_size - z ) )
|
|
{
|
|
c->bytes_erased = c->device_size - z;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
i = 0;
|
|
}
|
|
}
|
|
|
|
pthread_testcancel();
|
|
|
|
/* Track how much of the device has been successfully erased so far. */
|
|
if( c->bytes_erased < ( c->device_size - z ) )
|
|
{
|
|
c->bytes_erased = c->device_size - z;
|
|
}
|
|
|
|
} /* /remaining bytes */
|
|
|
|
free( b );
|
|
|
|
/* Final sync at end of pass. */
|
|
c->sync_status = 1;
|
|
r = fdatasync( c->device_fd );
|
|
c->sync_status = 0;
|
|
|
|
if( r != 0 )
|
|
{
|
|
nwipe_perror( errno, __FUNCTION__, "fdatasync" );
|
|
nwipe_log( NWIPE_LOG_WARNING, "Buffer flush failure on '%s'.", c->device_name );
|
|
c->fsyncdata_errors++;
|
|
|
|
/*
|
|
* Keep the original semantics: adjust bytes_erased based on the last
|
|
* known good block and fail the pass.
|
|
*/
|
|
if( c->bytes_erased < ( c->device_size - z - blocksize ) )
|
|
{
|
|
c->bytes_erased = c->device_size - z - blocksize;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
|
|
} /* nwipe_random_pass */
|
|
|
|
/*
|
|
* nwipe_static_verify
|
|
*
|
|
* Verifies that a static pattern pass was correctly written to the device.
|
|
*
|
|
* We pre-build a pattern buffer that repeats the user-chosen pattern and
|
|
* then, for each block we read from the device, compare it to the appropriate
|
|
* "window" into that pattern buffer.
|
|
*
|
|
* This version uses large I/O blocks (e.g. 4 MiB) instead of tiny
|
|
* st_blksize-sized chunks.
|
|
*/
|
|
int nwipe_static_verify( NWIPE_METHOD_SIGNATURE, nwipe_pattern_t* pattern )
|
|
{
|
|
int r;
|
|
size_t blocksize;
|
|
size_t io_blocksize;
|
|
off64_t offset;
|
|
char* b; /* read buffer */
|
|
char* d; /* pre-built pattern buffer */
|
|
char* q;
|
|
int w = 0; /* window offset into pattern buffer */
|
|
u64 z = c->device_size;
|
|
|
|
if( pattern == NULL )
|
|
{
|
|
nwipe_log( NWIPE_LOG_SANITY, "nwipe_static_verify: Null entropy pointer." );
|
|
return -1;
|
|
}
|
|
|
|
if( pattern->length <= 0 )
|
|
{
|
|
nwipe_log( NWIPE_LOG_SANITY, "nwipe_static_verify: The pattern length member is %i.", pattern->length );
|
|
return -1;
|
|
}
|
|
|
|
io_blocksize = nwipe_effective_io_blocksize( c );
|
|
|
|
b = (char*) nwipe_alloc_io_buffer( c, io_blocksize, 0, "static_verify input buffer" );
|
|
if( !b )
|
|
return -1;
|
|
|
|
/*
|
|
* Pattern buffer length:
|
|
* io_blocksize + pattern->length * 2
|
|
* to ensure we can always take a contiguous window of size <= io_blocksize
|
|
* starting at any offset w within [0, pattern->length).
|
|
*/
|
|
d = (char*) nwipe_alloc_io_buffer( c, io_blocksize + pattern->length * 2, 0, "static_verify pattern buffer" );
|
|
if( !d )
|
|
{
|
|
free( b );
|
|
return -1;
|
|
}
|
|
|
|
for( q = d; q < d + io_blocksize + pattern->length; q += pattern->length )
|
|
{
|
|
memcpy( q, pattern->s, pattern->length );
|
|
}
|
|
|
|
/* Ensure all writes are flushed before verification. */
|
|
c->sync_status = 1;
|
|
r = fdatasync( c->device_fd );
|
|
c->sync_status = 0;
|
|
|
|
if( r != 0 )
|
|
{
|
|
nwipe_perror( errno, __FUNCTION__, "fdatasync" );
|
|
nwipe_log( NWIPE_LOG_WARNING, "Buffer flush failure on '%s'.", c->device_name );
|
|
c->fsyncdata_errors++;
|
|
}
|
|
|
|
/* Rewind. */
|
|
offset = lseek( c->device_fd, 0, SEEK_SET );
|
|
c->pass_done = 0;
|
|
|
|
if( offset == (off64_t) -1 )
|
|
{
|
|
nwipe_perror( errno, __FUNCTION__, "lseek" );
|
|
nwipe_log( NWIPE_LOG_FATAL, "Unable to reset the '%s' file offset.", c->device_name );
|
|
free( b );
|
|
free( d );
|
|
return -1;
|
|
}
|
|
|
|
if( offset != 0 )
|
|
{
|
|
nwipe_log( NWIPE_LOG_SANITY, "nwipe_static_verify: lseek() returned a bogus offset on '%s'.", c->device_name );
|
|
free( b );
|
|
free( d );
|
|
return -1;
|
|
}
|
|
|
|
while( z > 0 )
|
|
{
|
|
if( z >= (u64) io_blocksize )
|
|
{
|
|
blocksize = io_blocksize;
|
|
}
|
|
else
|
|
{
|
|
blocksize = (size_t) z;
|
|
|
|
if( (u64) c->device_stat.st_blksize > z )
|
|
{
|
|
nwipe_log( NWIPE_LOG_WARNING,
|
|
"%s: The size of '%s' is not a multiple of its block size %i.",
|
|
__FUNCTION__,
|
|
c->device_name,
|
|
c->device_stat.st_blksize );
|
|
}
|
|
}
|
|
|
|
r = (int) read( c->device_fd, b, blocksize );
|
|
if( r < 0 )
|
|
{
|
|
nwipe_perror( errno, __FUNCTION__, "read" );
|
|
nwipe_log( NWIPE_LOG_ERROR, "Unable to read from '%s'.", c->device_name );
|
|
free( b );
|
|
free( d );
|
|
return -1;
|
|
}
|
|
|
|
if( r == (int) blocksize )
|
|
{
|
|
/* Compare every byte in the buffer against the current pattern window. */
|
|
if( memcmp( b, &d[w], (size_t) r ) != 0 )
|
|
{
|
|
c->verify_errors += 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int s = (int) blocksize - r;
|
|
|
|
c->verify_errors += 1;
|
|
|
|
nwipe_log( NWIPE_LOG_WARNING, "Partial read on '%s', %i bytes short.", c->device_name, s );
|
|
|
|
offset = lseek( c->device_fd, s, SEEK_CUR );
|
|
if( offset == (off64_t) -1 )
|
|
{
|
|
nwipe_perror( errno, __FUNCTION__, "lseek" );
|
|
nwipe_log(
|
|
NWIPE_LOG_ERROR, "Unable to bump the '%s' file offset after a partial read.", c->device_name );
|
|
free( b );
|
|
free( d );
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Advance the pattern window by r bytes, modulo pattern->length.
|
|
* This keeps the pattern alignment in sync with the device offset.
|
|
*/
|
|
if( pattern->length > 0 && r > 0 )
|
|
{
|
|
size_t adv = (size_t) r % (size_t) pattern->length;
|
|
w = (int) ( ( w + (int) adv ) % pattern->length );
|
|
}
|
|
|
|
z -= (u64) r;
|
|
c->pass_done += r;
|
|
c->round_done += r;
|
|
|
|
pthread_testcancel();
|
|
|
|
} /* while bytes remaining */
|
|
|
|
free( b );
|
|
free( d );
|
|
|
|
return 0;
|
|
|
|
} /* nwipe_static_verify */
|
|
|
|
/*
|
|
* nwipe_static_pass
|
|
*
|
|
* Writes a static pattern to the device.
|
|
*
|
|
* The pattern is repeated into a large buffer and then written in equally
|
|
* large I/O blocks (e.g. 4 MiB). The "window" offset w keeps track of where
|
|
* in the repeating pattern we are when moving across the device.
|
|
*/
|
|
int nwipe_static_pass( NWIPE_METHOD_SIGNATURE, nwipe_pattern_t* pattern )
|
|
{
|
|
int r;
|
|
size_t blocksize;
|
|
size_t io_blocksize;
|
|
size_t bufsize;
|
|
off64_t offset;
|
|
char* b;
|
|
char* p;
|
|
int w = 0; /* window offset into pattern */
|
|
u64 z = c->device_size;
|
|
|
|
int syncRate = nwipe_options.sync;
|
|
|
|
io_blocksize = nwipe_effective_io_blocksize( c );
|
|
|
|
/* For direct I/O we do not need periodic fdatasync(), I/O errors are detected
|
|
* at write() time. Keep sync for cached I/O only. */
|
|
if( c->io_mode == NWIPE_IO_MODE_DIRECT )
|
|
{
|
|
syncRate = 0;
|
|
nwipe_log( NWIPE_LOG_NOTICE, "Disabled fdatasync for %s, DirectI/O in use.", c->device_name );
|
|
}
|
|
else /* for cached I/O only */
|
|
{
|
|
/* Compute per-write sync rate (same semantics as random pass). */
|
|
syncRate = nwipe_compute_sync_rate_for_device( c, io_blocksize );
|
|
}
|
|
|
|
int i = 0;
|
|
|
|
if( pattern == NULL )
|
|
{
|
|
nwipe_log( NWIPE_LOG_SANITY, "__FUNCTION__: Null pattern pointer." );
|
|
return -1;
|
|
}
|
|
|
|
if( pattern->length <= 0 )
|
|
{
|
|
nwipe_log( NWIPE_LOG_SANITY, "__FUNCTION__: The pattern length member is %i.", pattern->length );
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* For static patterns we want enough buffer space to always have a
|
|
* contiguous window of "io_blocksize" bytes available starting at any
|
|
* offset w in [0, pattern->length). Using:
|
|
*
|
|
* buffer_size >= io_blocksize + pattern->length * 2
|
|
*
|
|
* guarantees that we can wrap around the repeating pattern safely.
|
|
* We also honour NWIPE_BUFFER_SIZE if it is larger, to keep the "generic
|
|
* 16 MiB buffer" behavior.
|
|
*/
|
|
bufsize = io_blocksize + pattern->length * 2;
|
|
if( bufsize < (size_t) NWIPE_BUFFER_SIZE )
|
|
bufsize = (size_t) NWIPE_BUFFER_SIZE;
|
|
|
|
b = (char*) nwipe_alloc_io_buffer( c, bufsize, 0, "static_pass pattern buffer" );
|
|
if( !b )
|
|
return -1;
|
|
|
|
for( p = b; p < b + bufsize; p += pattern->length )
|
|
{
|
|
memcpy( p, pattern->s, pattern->length );
|
|
}
|
|
|
|
/* Rewind device. */
|
|
offset = lseek( c->device_fd, 0, SEEK_SET );
|
|
c->pass_done = 0;
|
|
|
|
if( offset == (off64_t) -1 )
|
|
{
|
|
nwipe_perror( errno, __FUNCTION__, "lseek" );
|
|
nwipe_log( NWIPE_LOG_FATAL, "Unable to reset the '%s' file offset.", c->device_name );
|
|
free( b );
|
|
return -1;
|
|
}
|
|
|
|
if( offset != 0 )
|
|
{
|
|
nwipe_log( NWIPE_LOG_SANITY, "__FUNCTION__: lseek() returned a bogus offset on '%s'.", c->device_name );
|
|
free( b );
|
|
return -1;
|
|
}
|
|
|
|
while( z > 0 )
|
|
{
|
|
if( z >= (u64) io_blocksize )
|
|
{
|
|
blocksize = io_blocksize;
|
|
}
|
|
else
|
|
{
|
|
blocksize = (size_t) z;
|
|
|
|
if( (u64) c->device_stat.st_blksize > z )
|
|
{
|
|
nwipe_log( NWIPE_LOG_WARNING,
|
|
"%s: The size of '%s' is not a multiple of its block size %i.",
|
|
__FUNCTION__,
|
|
c->device_name,
|
|
c->device_stat.st_blksize );
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Write "blocksize" bytes starting at offset w in the repeating
|
|
* pattern buffer. Because we filled the entire buffer with the
|
|
* pattern (and made it large enough), &b[w] is always valid.
|
|
*/
|
|
r = (int) write( c->device_fd, &b[w], blocksize );
|
|
if( r < 0 )
|
|
{
|
|
nwipe_perror( errno, __FUNCTION__, "write" );
|
|
nwipe_log( NWIPE_LOG_FATAL, "Unable to write to '%s'.", c->device_name );
|
|
if( c->bytes_erased < ( c->device_size - z ) )
|
|
{
|
|
c->bytes_erased = c->device_size - z;
|
|
}
|
|
free( b );
|
|
return -1;
|
|
}
|
|
|
|
if( r != (int) blocksize )
|
|
{
|
|
int s = (int) blocksize - r;
|
|
|
|
c->pass_errors += s;
|
|
|
|
nwipe_log( NWIPE_LOG_WARNING, "Partial write on '%s', %i bytes short.", c->device_name, s );
|
|
|
|
offset = lseek( c->device_fd, s, SEEK_CUR );
|
|
if( offset == (off64_t) -1 )
|
|
{
|
|
nwipe_perror( errno, __FUNCTION__, "lseek" );
|
|
nwipe_log(
|
|
NWIPE_LOG_ERROR, "Unable to bump the '%s' file offset after a partial write.", c->device_name );
|
|
if( c->bytes_erased < ( c->device_size - z ) )
|
|
{
|
|
c->bytes_erased = c->device_size - z;
|
|
}
|
|
free( b );
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Advance the pattern window by r bytes (not blocksize; we use the
|
|
* actual number of bytes written) modulo pattern length.
|
|
*/
|
|
if( pattern->length > 0 && r > 0 )
|
|
{
|
|
size_t adv = (size_t) r % (size_t) pattern->length;
|
|
w = (int) ( ( w + (int) adv ) % pattern->length );
|
|
}
|
|
|
|
z -= (u64) r;
|
|
c->pass_done += r;
|
|
c->round_done += r;
|
|
|
|
/* Periodic sync if requested. */
|
|
if( syncRate > 0 )
|
|
{
|
|
i++;
|
|
|
|
if( i >= syncRate )
|
|
{
|
|
c->sync_status = 1;
|
|
r = fdatasync( c->device_fd );
|
|
c->sync_status = 0;
|
|
|
|
if( r != 0 )
|
|
{
|
|
nwipe_perror( errno, __FUNCTION__, "fdatasync" );
|
|
nwipe_log( NWIPE_LOG_WARNING, "Buffer flush failure on '%s'.", c->device_name );
|
|
nwipe_log( NWIPE_LOG_WARNING, "Wrote %llu bytes on '%s'.", c->pass_done, c->device_name );
|
|
c->fsyncdata_errors++;
|
|
free( b );
|
|
if( c->bytes_erased < ( c->device_size - z ) )
|
|
{
|
|
c->bytes_erased = c->device_size - z;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
i = 0;
|
|
}
|
|
}
|
|
|
|
pthread_testcancel();
|
|
|
|
if( c->bytes_erased < ( c->device_size - z ) )
|
|
{
|
|
c->bytes_erased = c->device_size - z;
|
|
}
|
|
|
|
} /* /remaining bytes */
|
|
|
|
/* Final sync at end of pass. */
|
|
c->sync_status = 1;
|
|
r = fdatasync( c->device_fd );
|
|
c->sync_status = 0;
|
|
|
|
if( r != 0 )
|
|
{
|
|
nwipe_perror( errno, __FUNCTION__, "fdatasync" );
|
|
nwipe_log( NWIPE_LOG_WARNING, "Buffer flush failure on '%s'.", c->device_name );
|
|
c->fsyncdata_errors++;
|
|
if( c->bytes_erased < ( c->device_size - z - blocksize ) )
|
|
{
|
|
c->bytes_erased = c->device_size - z - blocksize;
|
|
}
|
|
free( b );
|
|
return -1;
|
|
}
|
|
|
|
free( b );
|
|
|
|
return 0;
|
|
|
|
} /* nwipe_static_pass */
|