diff --git a/src/nwipe.c b/src/nwipe.c index 55afe0f..e86d700 100644 --- a/src/nwipe.c +++ b/src/nwipe.c @@ -27,6 +27,11 @@ #define _POSIX_SOURCE #endif +/* Enable GNU extensions so that O_DIRECT is visible from . */ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE 1 +#endif + #include #include #include @@ -59,6 +64,7 @@ #include "hpa_dco.h" #include "conf.h" #include +#include /* O_DIRECT, O_RDWR, ... */ int terminate_signal; int user_abort; @@ -478,8 +484,34 @@ int main( int argc, char** argv ) /* Initialise the wipe_status flag, -1 = wipe not yet started */ c2[i]->wipe_status = -1; - /* Open the file for reads and writes. */ - c2[i]->device_fd = open( c2[i]->device_name, O_RDWR ); + /* Open the file for reads and writes. Optionally use O_DIRECT. */ + int open_flags = O_RDWR; +#ifdef NWIPE_USE_DIRECT_IO + open_flags |= O_DIRECT; +#endif + + c2[i]->device_fd = open( c2[i]->device_name, open_flags ); + +#ifdef NWIPE_USE_DIRECT_IO + /* If O_DIRECT is not supported (or rejected by the FS), fall back to buffered I/O. */ + if( c2[i]->device_fd < 0 && ( errno == EINVAL || errno == EOPNOTSUPP ) ) + { + nwipe_log( NWIPE_LOG_WARNING, + "O_DIRECT not supported on '%s', retrying without O_DIRECT.", + c2[i]->device_name ); + open_flags &= ~O_DIRECT; + c2[i]->device_fd = open( c2[i]->device_name, open_flags ); + } +#endif + + /* Check the open() result. */ + if( c2[i]->device_fd < 0 ) + { + nwipe_perror( errno, __FUNCTION__, "open" ); + nwipe_log( NWIPE_LOG_WARNING, "Unable to open device '%s'.", c2[i]->device_name ); + c2[i]->select = NWIPE_SELECT_DISABLED; + continue; + } /* Check the open() result. */ if( c2[i]->device_fd < 0 ) diff --git a/src/pass.c b/src/pass.c index fc48ac1..aadab8b 100644 --- a/src/pass.c +++ b/src/pass.c @@ -23,6 +23,10 @@ #define _POSIX_C_SOURCE 200809L #include +#include /* posix_memalign, malloc, free */ +#include /* memset, memcpy, memcmp */ +#include + #include "nwipe.h" #include "context.h" #include "method.h" @@ -32,30 +36,142 @@ #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; +} + +/* + * 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 ) { - /** - * Verifies that a random pass was correctly written to the device. - * - */ - - /* The result holder. */ int r; - - /* The IO size. */ size_t blocksize; - - /* The result buffer for calls to lseek. */ + size_t io_blocksize; off64_t offset; - - /* The input buffer. */ - char* b; - - /* The pattern buffer that is used to check the input buffer. */ - char* d; - - /* The number of bytes remaining in the pass. */ - u64 z = c->device_size; + 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 ) { @@ -69,33 +185,22 @@ int nwipe_random_verify( nwipe_context_t* c ) return -1; } - /* Create the input buffer. */ - b = malloc( c->device_stat.st_blksize ); + io_blocksize = nwipe_effective_io_blocksize( c ); - /* Check the memory allocation. */ + /* 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 ) - { - nwipe_perror( errno, __FUNCTION__, "malloc" ); - nwipe_log( NWIPE_LOG_FATAL, "Unable to allocate memory for the input buffer." ); return -1; - } - /* Create the pattern buffer */ - d = malloc( c->device_stat.st_blksize ); - - /* Check the memory allocation. */ + d = (char*) nwipe_alloc_io_buffer( c, io_blocksize, 0, "random_verify pattern buffer" ); if( !d ) { - nwipe_perror( errno, __FUNCTION__, "malloc" ); - nwipe_log( NWIPE_LOG_FATAL, "Unable to allocate memory for the pattern buffer." ); free( b ); return -1; } - /* Reset the file pointer. */ + /* Rewind device to the beginning. */ offset = lseek( c->device_fd, 0, SEEK_SET ); - - /* Reset the pass byte counter. */ c->pass_done = 0; if( offset == (off64_t) -1 ) @@ -109,20 +214,15 @@ int nwipe_random_verify( nwipe_context_t* c ) if( offset != 0 ) { - /* This is system insanity. */ nwipe_log( NWIPE_LOG_SANITY, "lseek() returned a bogus offset on '%s'.", c->device_name ); free( b ); free( d ); return -1; } - /* Tell our parent that we are syncing the device. */ + /* Ensure all previous writes are on disk before we verify. */ c->sync_status = 1; - - /* Sync the device. */ r = fdatasync( c->device_fd ); - - /* Tell our parent that we have finished syncing the device. */ c->sync_status = 0; if( r != 0 ) @@ -132,78 +232,77 @@ int nwipe_random_verify( nwipe_context_t* c ) c->fsyncdata_errors++; } - /* Reseed the PRNG. */ + /* 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( c->device_stat.st_blksize <= z ) + if( z >= (u64) io_blocksize ) { - blocksize = c->device_stat.st_blksize; + blocksize = io_blocksize; } else { - /* This is a seatbelt for buggy drivers and programming errors because */ - /* the device size should always be an even multiple of its blocksize. */ - blocksize = 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 ); + 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 ); + } } - /* Fill the output buffer with the random pattern. */ + /* Generate expected random data into pattern buffer. */ c->prng->read( &c->prng_state, d, blocksize ); - /* Read the buffer in from the device. */ - r = read( c->device_fd, b, blocksize ); - - /* Check the result. */ + /* 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; } - /* Check for a partial read. */ - if( r != blocksize ) + if( r != (int) blocksize ) { - /* TODO: Handle a partial read. */ - - /* The number of bytes that were not read. */ - int s = blocksize - r; + /* + * 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 ); - /* Increment the error count. */ c->verify_errors += 1; - /* Bump the file pointer to the next block. */ 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; } + } - } /* partial read */ - - /* Compare buffer contents. */ - if( memcmp( b, d, blocksize ) != 0 ) + /* 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; } - /* Decrement the bytes remaining in this pass. */ - z -= r; - - /* Increment the total progress counters. */ + z -= (u64) r; c->pass_done += r; c->round_done += r; @@ -211,44 +310,39 @@ int nwipe_random_verify( nwipe_context_t* c ) } /* while bytes remaining */ - /* Release the buffers. */ free( b ); free( d ); - /* We're done. */ 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 ) { - /** - * Writes a random pattern to the device. - * - */ - - /* The result holder. */ int r; - - /* The IO size. */ size_t blocksize; - - /* The result buffer for calls to lseek. */ + size_t io_blocksize; + size_t bufsize; off64_t offset; - - /* The output buffer. */ char* b; + u64 z = c->device_size; /* bytes remaining */ - /* The number of bytes remaining in the pass. */ - u64 z = c->device_size; - - /* Number of writes to do before a fdatasync. */ int syncRate = nwipe_options.sync; - - /* Counter to track when to do a fdatasync. */ int i = 0; - - /* general index counter */ int idx; if( c->prng_seed.s == NULL ) @@ -263,25 +357,27 @@ int nwipe_random_pass( NWIPE_METHOD_SIGNATURE ) return -1; } - /* Create the initialised output buffer. Initialised because we don't want memory leaks - * to disk in the event of some future undetected bug in a prng or its implementation. */ - b = calloc( c->device_stat.st_blksize, sizeof( char ) ); + /* Select effective I/O block size (e.g. 4 MiB, never smaller than st_blksize). */ + io_blocksize = nwipe_effective_io_blocksize( c ); - /* Check the memory allocation. */ + /* + * 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 ) - { - nwipe_perror( errno, __FUNCTION__, "malloc" ); - nwipe_log( NWIPE_LOG_FATAL, "Unable to allocate memory for the output buffer." ); return -1; - } - /* Seed the PRNG. */ + /* Seed the PRNG for this pass. */ c->prng->init( &c->prng_state, &c->prng_seed ); - /* Reset the file pointer. */ + /* Rewind device. */ offset = lseek( c->device_fd, 0, SEEK_SET ); - - /* Reset the pass byte counter. */ c->pass_done = 0; if( offset == (off64_t) -1 ) @@ -294,7 +390,6 @@ int nwipe_random_pass( NWIPE_METHOD_SIGNATURE ) if( offset != 0 ) { - /* This is system insanity. */ nwipe_log( NWIPE_LOG_SANITY, "__FUNCTION__: lseek() returned a bogus offset on '%s'.", c->device_name ); free( b ); return -1; @@ -302,30 +397,47 @@ int nwipe_random_pass( NWIPE_METHOD_SIGNATURE ) while( z > 0 ) { - if( c->device_stat.st_blksize <= z ) + /* + * 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 = c->device_stat.st_blksize; + blocksize = io_blocksize; } else { - /* This is a seatbelt for buggy drivers and programming errors because */ - /* the device size should always be an even multiple of its blocksize. */ - blocksize = 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 ); + 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 ); + } } - /* Fill the output buffer with the random pattern. */ + /* Ask the PRNG to fill "blocksize" bytes into the output buffer. */ c->prng->read( &c->prng_state, b, blocksize ); - /* For the first block only, check the prng actually wrote something to the buffer */ + /* + * 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 ) { - idx = c->device_stat.st_blksize - 1; - while( idx > 0 ) + 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 ) { @@ -334,83 +446,74 @@ int nwipe_random_pass( NWIPE_METHOD_SIGNATURE ) } idx--; } - if( idx == 0 ) + if( idx < 0 ) { nwipe_log( NWIPE_LOG_FATAL, "ERROR, prng wrote nothing to the buffer" ); - if( c->bytes_erased < ( c->device_size - z ) ) // How much of the device has been erased? + if( c->bytes_erased < ( c->device_size - z ) ) { c->bytes_erased = c->device_size - z; } + free( b ); return -1; } } - /* Write the next block out to the device. */ - r = write( c->device_fd, b, blocksize ); + /* Write the generated random data to the device. */ + r = (int) write( c->device_fd, b, blocksize ); - /* Check the result for a fatal error. */ if( r < 0 ) { nwipe_perror( errno, __FUNCTION__, "write" ); - nwipe_log( NWIPE_LOG_FATAL, "Unable to read from '%s'.", c->device_name ); - if( c->bytes_erased < ( c->device_size - z ) ) // How much of the device has been erased? + 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; } - /* Check for a partial write. */ - if( r != blocksize ) + if( r != (int) blocksize ) { - /* TODO: Handle a partial write. */ + /* + * 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; - /* The number of bytes that were not written. */ - int s = blocksize - r; - - /* Increment the error count by the number of bytes that were not written. */ c->pass_errors += s; nwipe_log( NWIPE_LOG_WARNING, "Partial write on '%s', %i bytes short.", c->device_name, s ); - /* Bump the file pointer to the next block. */ 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 ) ) // How much of the device has been erased? + if( c->bytes_erased < ( c->device_size - z ) ) { c->bytes_erased = c->device_size - z; } + free( b ); return -1; } + } - } /* partial write */ - - /* Decrement the bytes remaining in this pass. */ - z -= r; - - /* Increment the total progress counters. */ + z -= (u64) r; c->pass_done += r; c->round_done += r; - /* Perodic Sync */ + /* Periodic fdatasync after 'syncRate' writes, if configured. */ if( syncRate > 0 ) { i++; if( i >= syncRate ) { - /* Tell our parent that we are syncing the device. */ c->sync_status = 1; - - /* Sync the device. */ r = fdatasync( c->device_fd ); - - /* Tell our parent that we have finished syncing the device. */ c->sync_status = 0; if( r != 0 ) @@ -420,7 +523,7 @@ int nwipe_random_pass( NWIPE_METHOD_SIGNATURE ) 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 ) ) // How much of the device has been erased? + if( c->bytes_erased < ( c->device_size - z ) ) { c->bytes_erased = c->device_size - z; } @@ -433,24 +536,19 @@ int nwipe_random_pass( NWIPE_METHOD_SIGNATURE ) pthread_testcancel(); - /* If statement required so that it does not reset on subsequent passes */ - if( c->bytes_erased < ( c->device_size - z ) ) // How much of the device has been erased? + /* 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 */ - /* Release the output buffer. */ free( b ); - /* Tell our parent that we are syncing the device. */ + /* Final sync at end of pass. */ c->sync_status = 1; - - /* Sync the device. */ r = fdatasync( c->device_fd ); - - /* Tell our parent that we have finished syncing the device. */ c->sync_status = 0; if( r != 0 ) @@ -458,98 +556,85 @@ int nwipe_random_pass( NWIPE_METHOD_SIGNATURE ) 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 ) ) // How much of the device has been erased? + + /* + * 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; } - /* We're done. */ 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 ) { - /** - * Verifies that a static pass was correctly written to the device. - */ - - /* The result holder. */ int r; - - /* The IO size. */ size_t blocksize; - - /* The result buffer for calls to lseek. */ + size_t io_blocksize; off64_t offset; - - /* The input buffer. */ - char* b; - - /* The pattern buffer that is used to check the input buffer. */ - char* d; - - /* A pointer into the pattern buffer. */ + char* b; /* read buffer */ + char* d; /* pre-built pattern buffer */ char* q; - - /* The pattern buffer window offset. */ - int w = 0; - - /* The number of bytes remaining in the pass. */ + int w = 0; /* window offset into pattern buffer */ u64 z = c->device_size; if( pattern == NULL ) { - /* Caught insanity. */ nwipe_log( NWIPE_LOG_SANITY, "nwipe_static_verify: Null entropy pointer." ); return -1; } if( pattern->length <= 0 ) { - /* Caught insanity. */ nwipe_log( NWIPE_LOG_SANITY, "nwipe_static_verify: The pattern length member is %i.", pattern->length ); return -1; } - /* Create the input buffer. */ - b = malloc( c->device_stat.st_blksize ); + io_blocksize = nwipe_effective_io_blocksize( c ); - /* Check the memory allocation. */ + b = (char*) nwipe_alloc_io_buffer( c, io_blocksize, 0, "static_verify input buffer" ); if( !b ) - { - nwipe_perror( errno, __FUNCTION__, "malloc" ); - nwipe_log( NWIPE_LOG_FATAL, "Unable to allocate memory for the input buffer." ); return -1; - } - /* Create the pattern buffer */ - d = malloc( c->device_stat.st_blksize + pattern->length * 2 ); - - /* Check the memory allocation. */ + /* + * 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 ) { - nwipe_perror( errno, __FUNCTION__, "malloc" ); - nwipe_log( NWIPE_LOG_FATAL, "Unable to allocate memory for the pattern buffer." ); free( b ); return -1; } - for( q = d; q < d + c->device_stat.st_blksize + pattern->length; q += pattern->length ) + for( q = d; q < d + io_blocksize + pattern->length; q += pattern->length ) { - /* Fill the pattern buffer with the pattern. */ memcpy( q, pattern->s, pattern->length ); } - /* Tell our parent that we are syncing the device. */ + /* Ensure all writes are flushed before verification. */ c->sync_status = 1; - - /* Sync the device. */ r = fdatasync( c->device_fd ); - - /* Tell our parent that we have finished syncing the device. */ c->sync_status = 0; if( r != 0 ) @@ -559,10 +644,8 @@ int nwipe_static_verify( NWIPE_METHOD_SIGNATURE, nwipe_pattern_t* pattern ) c->fsyncdata_errors++; } - /* Reset the file pointer. */ + /* Rewind. */ offset = lseek( c->device_fd, 0, SEEK_SET ); - - /* Reset the pass byte counter. */ c->pass_done = 0; if( offset == (off64_t) -1 ) @@ -570,12 +653,12 @@ int nwipe_static_verify( NWIPE_METHOD_SIGNATURE, nwipe_pattern_t* pattern ) 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 ) { - /* This is system insanity. */ nwipe_log( NWIPE_LOG_SANITY, "nwipe_static_verify: lseek() returned a bogus offset on '%s'.", c->device_name ); free( b ); free( d ); @@ -584,80 +667,73 @@ int nwipe_static_verify( NWIPE_METHOD_SIGNATURE, nwipe_pattern_t* pattern ) while( z > 0 ) { - if( c->device_stat.st_blksize <= z ) + if( z >= (u64) io_blocksize ) { - blocksize = c->device_stat.st_blksize; + blocksize = io_blocksize; } else { - /* This is a seatbelt for buggy drivers and programming errors because */ - /* the device size should always be an even multiple of its blocksize. */ - blocksize = 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 ); + 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 ); + } } - /* Fill the output buffer with the random pattern. */ - /* Read the buffer in from the device. */ - r = read( c->device_fd, b, blocksize ); - - /* Check the result. */ + 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; } - /* Check for a partial read. */ - if( r == blocksize ) + if( r == (int) blocksize ) { - /* Check every byte in the buffer. */ - if( memcmp( b, &d[w], r ) != 0 ) + /* 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 { - /* The number of bytes that were not read. */ - int s = blocksize - r; + int s = (int) blocksize - r; - /* TODO: Handle a partial read. */ - - /* Increment the error count. */ c->verify_errors += 1; nwipe_log( NWIPE_LOG_WARNING, "Partial read on '%s', %i bytes short.", c->device_name, s ); - /* Bump the file pointer to the next block. */ 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; } + } - } /* partial read */ - - /* Adjust the window. */ - w = ( c->device_stat.st_blksize + w ) % pattern->length; - - /* Intuition check: - * If the pattern length evenly divides the block size - * then ( w == 0 ) always. + /* + * 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 ); + } - /* Decrement the bytes remaining in this pass. */ - z -= r; - - /* Increment the total progress counters. */ + z -= (u64) r; c->pass_done += r; c->round_done += r; @@ -665,193 +741,178 @@ int nwipe_static_verify( NWIPE_METHOD_SIGNATURE, nwipe_pattern_t* pattern ) } /* while bytes remaining */ - /* Release the buffers. */ free( b ); free( d ); - /* We're done. */ 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 ) { - /** - * Writes a static pattern to the device. - */ - - /* The result holder. */ int r; - - /* The IO size. */ size_t blocksize; - - /* The result buffer for calls to lseek. */ + size_t io_blocksize; + size_t bufsize; off64_t offset; - - /* The output buffer. */ char* b; - - /* A pointer into the output buffer. */ char* p; - - /* The output buffer window offset. */ - int w = 0; - - /* The number of bytes remaining in the pass. */ + int w = 0; /* window offset into pattern */ u64 z = c->device_size; - /* Number of writes to do before a fdatasync. */ int syncRate = nwipe_options.sync; - - /* Counter to track when to do a fdatasync. */ int i = 0; if( pattern == NULL ) { - /* Caught insanity. */ nwipe_log( NWIPE_LOG_SANITY, "__FUNCTION__: Null pattern pointer." ); return -1; } if( pattern->length <= 0 ) { - /* Caught insanity. */ nwipe_log( NWIPE_LOG_SANITY, "__FUNCTION__: The pattern length member is %i.", pattern->length ); return -1; } - /* Create the output buffer. */ - b = malloc( c->device_stat.st_blksize + pattern->length * 2 ); + io_blocksize = nwipe_effective_io_blocksize( c ); - /* Check the memory allocation. */ + /* + * 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 ) - { - nwipe_perror( errno, __FUNCTION__, "malloc" ); - nwipe_log( NWIPE_LOG_FATAL, "Unable to allocate memory for the pattern buffer." ); return -1; - } - for( p = b; p < b + c->device_stat.st_blksize + pattern->length; p += pattern->length ) + for( p = b; p < b + bufsize; p += pattern->length ) { - /* Fill the output buffer with the pattern. */ memcpy( p, pattern->s, pattern->length ); } - /// - /* Reset the file pointer. */ - offset = lseek( c->device_fd, 0, SEEK_SET ); - /* Reset the pass byte counter. */ + /* 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 ) { - /* This is system insanity. */ nwipe_log( NWIPE_LOG_SANITY, "__FUNCTION__: lseek() returned a bogus offset on '%s'.", c->device_name ); + free( b ); return -1; } while( z > 0 ) { - if( c->device_stat.st_blksize <= z ) + if( z >= (u64) io_blocksize ) { - blocksize = c->device_stat.st_blksize; + blocksize = io_blocksize; } else { - /* This is a seatbelt for buggy drivers and programming errors because */ - /* the device size should always be an even multiple of its blocksize. */ - blocksize = 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 ); + 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 ); + } } - /* Fill the output buffer with the random pattern. */ - /* Write the next block out to the device. */ - r = write( c->device_fd, &b[w], blocksize ); - - /* Check the result for a fatal error. */ + /* + * 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 ) ) // How much of the device has been erased? + if( c->bytes_erased < ( c->device_size - z ) ) { c->bytes_erased = c->device_size - z; } + free( b ); return -1; } - /* Check for a partial write. */ - if( r != blocksize ) + if( r != (int) blocksize ) { - /* TODO: Handle a partial write. */ + int s = (int) blocksize - r; - /* The number of bytes that were not written. */ - int s = blocksize - r; - - /* Increment the error count. */ c->pass_errors += s; nwipe_log( NWIPE_LOG_WARNING, "Partial write on '%s', %i bytes short.", c->device_name, s ); - /* Bump the file pointer to the next block. */ 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 ) ) // How much of the device has been erased? + if( c->bytes_erased < ( c->device_size - z ) ) { c->bytes_erased = c->device_size - z; } + free( b ); return -1; } + } - } /* partial write */ - - /* Adjust the window. */ - w = ( c->device_stat.st_blksize + w ) % pattern->length; - - /* Intuition check: - * - * If the pattern length evenly divides the block size - * then ( w == 0 ) always. + /* + * 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 ); + } - /* Decrement the bytes remaining in this pass. */ - z -= r; - - /* Increment the total progress counterr. */ + z -= (u64) r; c->pass_done += r; c->round_done += r; - /* Perodic Sync */ + /* Periodic sync if requested. */ if( syncRate > 0 ) { i++; if( i >= syncRate ) { - /* Tell our parent that we are syncing the device. */ c->sync_status = 1; - - /* Sync the device. */ r = fdatasync( c->device_fd ); - - /* Tell our parent that we have finished syncing the device. */ c->sync_status = 0; if( r != 0 ) @@ -861,7 +922,7 @@ int nwipe_static_pass( NWIPE_METHOD_SIGNATURE, nwipe_pattern_t* pattern ) 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 ) ) // How much of the device has been erased? + if( c->bytes_erased < ( c->device_size - z ) ) { c->bytes_erased = c->device_size - z; } @@ -874,20 +935,16 @@ int nwipe_static_pass( NWIPE_METHOD_SIGNATURE, nwipe_pattern_t* pattern ) pthread_testcancel(); - if( c->bytes_erased < ( c->device_size - z ) ) // How much of the device has been erased? + if( c->bytes_erased < ( c->device_size - z ) ) { c->bytes_erased = c->device_size - z; } } /* /remaining bytes */ - /* Tell our parent that we are syncing the device. */ + /* Final sync at end of pass. */ c->sync_status = 1; - - /* Sync the device. */ r = fdatasync( c->device_fd ); - - /* Tell our parent that we have finished syncing the device. */ c->sync_status = 0; if( r != 0 ) @@ -895,17 +952,16 @@ int nwipe_static_pass( NWIPE_METHOD_SIGNATURE, nwipe_pattern_t* pattern ) 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 ) ) // How much of the device has been erased? + if( c->bytes_erased < ( c->device_size - z - blocksize ) ) { c->bytes_erased = c->device_size - z - blocksize; } + free( b ); return -1; } - /* Release the output buffer. */ free( b ); - /* We're done. */ return 0; } /* nwipe_static_pass */