Merge pull request #700 from Knogle/auto-selection-for-prng-and-benchmark-no-gui-option

prng, gui: add PRNG benchmarking, auto-selection and CLI support
This commit is contained in:
PartialVolume
2026-01-03 21:51:49 +00:00
committed by GitHub
6 changed files with 359 additions and 9 deletions

View File

@@ -209,15 +209,6 @@ static void nwipe_gui_draw_acs_prefix( WINDOW* w, int y, int x )
mvwaddch( w, y, x + 3, ' ' );
}
typedef struct
{
const nwipe_prng_t* prng;
double mbps;
double seconds;
unsigned long long bytes;
int rc;
} nwipe_prng_bench_result_t;
static double nwipe_gui_monotonic_seconds( void )
{
struct timespec ts;

View File

@@ -96,6 +96,25 @@ int devnamecmp( const void* a, const void* b )
return ( ret );
}
static int nwipe_prng_bench_cmp_desc( const void* a, const void* b )
{
const nwipe_prng_bench_result_t* A = (const nwipe_prng_bench_result_t*) a;
const nwipe_prng_bench_result_t* B = (const nwipe_prng_bench_result_t*) b;
/* successful results first */
if( A->rc != 0 && B->rc == 0 )
return 1;
if( A->rc == 0 && B->rc != 0 )
return -1;
/* then sort by MB/s */
if( A->mbps < B->mbps )
return 1;
if( A->mbps > B->mbps )
return -1;
return 0;
}
#define NWIPE_PDF_DIR_MODE ( S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH )
/* -> 0755: rwx for owner, r-x for group and others */
@@ -339,6 +358,106 @@ int main( int argc, char** argv )
/* Log OS info */
nwipe_log_OSinfo();
/* ------------------------------------------------------------
* PRNG benchmark / auto-select (runs before device scan)
* ------------------------------------------------------------ */
if( nwipe_options.prng_benchmark_only || nwipe_options.prng_auto )
{
/* tune defaults */
const size_t io_block = 4 * 1024 * 1024; /* 4 MiB RAM buffer blocks */
double seconds = nwipe_options.prng_bench_seconds;
/* If user requested auto-select and didn't override seconds, keep it short */
if( nwipe_options.prng_auto )
{
if( seconds <= 0.0 )
seconds = 0.25;
/* Optional: if you consider "1.0" to be the default and want auto shorter:
* if( seconds == 1.0 ) seconds = 0.25;
*/
}
else
{
if( seconds <= 0.0 )
seconds = 1.0;
}
nwipe_prng_bench_result_t results[16];
memset( results, 0, sizeof( results ) );
if( nwipe_options.prng_benchmark_only )
{
int n = nwipe_prng_benchmark_all(
seconds, io_block, results, (int) ( sizeof( results ) / sizeof( results[0] ) ) );
if( n <= 0 )
{
nwipe_log( NWIPE_LOG_ERROR, "PRNG benchmark failed (no results)." );
printf( "PRNG benchmark failed (no results).\n" );
cleanup();
exit( 3 );
}
qsort( results, (size_t) n, sizeof( results[0] ), nwipe_prng_bench_cmp_desc );
/* Print to console + log */
printf( "\nPRNG Benchmark (RAM-only) ~%.2fs each, block=%zu MiB\n", seconds, io_block / ( 1024 * 1024 ) );
printf( "---------------------------------------------------\n" );
nwipe_log( NWIPE_LOG_INFO,
"PRNG Benchmark (RAM-only) ~%.2fs each, block=%zu MiB",
seconds,
io_block / ( 1024 * 1024 ) );
for( int i = 0; i < n; i++ )
{
if( results[i].rc == 0 )
{
printf( "%2d) %-40s %10.1f MB/s\n", i + 1, results[i].prng->label, results[i].mbps );
nwipe_log( NWIPE_LOG_INFO,
"PRNG bench %2d) %-40s %10.1f MB/s",
i + 1,
results[i].prng->label,
results[i].mbps );
}
else
{
printf( "%2d) %-40s (failed: rc=%d)\n", i + 1, results[i].prng->label, results[i].rc );
nwipe_log( NWIPE_LOG_WARNING,
"PRNG bench %2d) %-40s (failed: rc=%d)",
i + 1,
results[i].prng->label,
results[i].rc );
}
}
printf( "\n" );
cleanup();
exit( 0 );
}
/* --prng=auto path */
if( nwipe_options.prng_auto )
{
const nwipe_prng_t* best = nwipe_prng_select_fastest(
seconds, io_block, results, (int) ( sizeof( results ) / sizeof( results[0] ) ) );
if( best != NULL )
{
/* Apply selection */
nwipe_options.prng = (nwipe_prng_t*) best;
nwipe_log( NWIPE_LOG_INFO, "Auto-selected fastest PRNG: %s", best->label );
printf( "Auto-selected fastest PRNG: %s\n", best->label );
}
else
{
nwipe_log( NWIPE_LOG_WARNING,
"Auto PRNG selection: no working PRNG found, keeping configured default." );
printf( "Auto PRNG selection: no working PRNG found, keeping configured default.\n" );
}
}
}
/* Check that hdparm exists, we use hdparm for some HPA/DCO detection etc, if not
* exit nwipe. These checks are required if the PATH environment is not setup !
* Example: Debian sid 'su' as opposed to 'su -'

View File

@@ -91,6 +91,8 @@ int nwipe_options_parse( int argc, char** argv )
/* The Pseudo Random Number Generator. */
{ "prng", required_argument, 0, 'p' },
{ "prng-benchmark", no_argument, 0, 0 },
{ "prng-bench-seconds", required_argument, 0, 0 },
/* The number of times to run the method. */
{ "rounds", required_argument, 0, 'r' },
@@ -139,6 +141,10 @@ int nwipe_options_parse( int argc, char** argv )
nwipe_options.autonuke = 0;
nwipe_options.autopoweroff = 0;
nwipe_options.method = &nwipe_random;
nwipe_options.prng_auto = 0;
nwipe_options.prng_benchmark_only = 0;
nwipe_options.prng_bench_seconds = 1.0; /* default for interactive / manual */
/*
* Determines and sets the default PRNG based on AES-NI support and system architecture.
* It selects AES-CTR PRNG if AES-NI is supported, xoroshiro256 for 64-bit systems without AES-NI,
@@ -571,6 +577,21 @@ int nwipe_options_parse( int argc, char** argv )
break;
}
if( strcmp( nwipe_options_long[i].name, "prng-benchmark" ) == 0 )
{
nwipe_options.prng_benchmark_only = 1;
break;
}
if( strcmp( nwipe_options_long[i].name, "prng-bench-seconds" ) == 0 )
{
nwipe_options.prng_bench_seconds = atof( optarg );
if( nwipe_options.prng_bench_seconds < 0.05 )
nwipe_options.prng_bench_seconds = 0.05;
if( nwipe_options.prng_bench_seconds > 10.0 )
nwipe_options.prng_bench_seconds = 10.0;
break;
}
/* getopt_long should raise on invalid option, so we should never get here. */
exit( EINVAL );
@@ -737,6 +758,12 @@ int nwipe_options_parse( int argc, char** argv )
case 'p': /* PRNG option. */
if( strcmp( optarg, "auto" ) == 0 )
{
nwipe_options.prng_auto = 1;
/* keep current default as fallback until autoselect runs */
break;
}
if( strcmp( optarg, "mersenne" ) == 0 || strcmp( optarg, "twister" ) == 0 )
{
nwipe_options.prng = &nwipe_twister;
@@ -996,6 +1023,18 @@ void display_help()
puts( " If set to \"noPDF\" no PDF reports are written.\n" );
puts( " -p, --prng=METHOD PRNG option "
"(mersenne|twister|isaac|isaac64|add_lagg_fibonacci_prng|xoroshiro256_prng|aes_ctr_prng)\n" );
puts( " --prng=auto" );
puts( " Automatically benchmark all available PRNGs at startup and" );
puts( " select the fastest one for the current hardware." );
puts( "" );
puts( " --prng-benchmark" );
puts( " Run a RAM-only PRNG throughput benchmark and exit." );
puts( " Prints a sorted leaderboard (MB/s). No wipe is performed." );
puts( "" );
puts( " --prng-bench-seconds=N" );
puts( " Seconds per PRNG during benchmarking (default: 1.0)." );
puts( " For --prng=auto this is automatically reduced unless set." );
puts( " -q, --quiet Anonymize logs and the GUI by removing unique data, i.e." );
puts( " serial numbers, LU WWN Device ID, and SMBIOS/DMI data." );
puts( " XXXXXX = S/N exists, ????? = S/N not obtainable\n" );

View File

@@ -58,6 +58,9 @@ typedef struct
int nowait; // Do not wait for a final key before exiting.
int nosignals; // Do not allow signals to interrupt a wipe.
int nogui; // Do not show the GUI.
int prng_auto; /* 1 = auto-select fastest PRNG at startup */
int prng_benchmark_only; /* 1 = run PRNG benchmark and exit (nogui-friendly) */
double prng_bench_seconds; /* seconds per PRNG (default e.g. 0.25 for auto, 1.0 for manual) */
char* banner; // The product banner shown on the top line of the screen.
void* method; // A function pointer to the wipe method that will be used.
char logfile[FILENAME_MAX]; // The filename to log the output to.

View File

@@ -44,6 +44,15 @@ nwipe_prng_t nwipe_xoroshiro256_prng = { "XORoshiro-256", nwipe_xoroshiro256_prn
/* AES-CTR-NI PRNG Structure */
nwipe_prng_t nwipe_aes_ctr_prng = { "AES-CTR (Kernel)", nwipe_aes_ctr_prng_init, nwipe_aes_ctr_prng_read };
static const nwipe_prng_t* all_prngs[] = {
&nwipe_twister,
&nwipe_isaac,
&nwipe_isaac64,
&nwipe_add_lagg_fibonacci_prng,
&nwipe_xoroshiro256_prng,
&nwipe_aes_ctr_prng,
};
/* Print given number of bytes from unsigned integer number to a byte stream buffer starting with low-endian. */
static inline void u32_to_buffer( u8* restrict buffer, u32 val, const int len )
{
@@ -681,3 +690,173 @@ int nwipe_aes_ctr_prng_read( NWIPE_PRNG_READ_SIGNATURE )
}
return 0;
}
/* ----------------------------------------------------------------------
* PRNG benchmark / auto-selection core
* ---------------------------------------------------------------------- */
static double nwipe_prng_monotonic_seconds( void )
{
struct timespec ts;
clock_gettime( CLOCK_MONOTONIC, &ts );
return (double) ts.tv_sec + (double) ts.tv_nsec / 1000000000.0;
}
/* einfacher LCG zum Seed-Befüllen nur für Benchmark, kein Kryptokram */
static void nwipe_prng_make_seed( unsigned char* seed, size_t len )
{
unsigned long t = (unsigned long) time( NULL );
unsigned long x = ( t ^ 0xA5A5A5A5UL ) + (unsigned long) (uintptr_t) seed;
for( size_t i = 0; i < len; i++ )
{
x = x * 1664525UL + 1013904223UL;
seed[i] = (unsigned char) ( ( x >> 16 ) & 0xFF );
}
}
static void* nwipe_prng_alloc_aligned( size_t alignment, size_t size )
{
void* p = NULL;
if( posix_memalign( &p, alignment, size ) != 0 )
return NULL;
return p;
}
static void nwipe_prng_bench_one( const nwipe_prng_t* prng,
nwipe_prng_bench_result_t* out,
void* io_buf,
size_t io_block,
double seconds_per_prng )
{
void* state = NULL;
unsigned char seedbuf[4096];
nwipe_entropy_t seed;
seed.s = (u8*) seedbuf;
seed.length = sizeof( seedbuf );
nwipe_prng_make_seed( seedbuf, sizeof( seedbuf ) );
out->prng = prng;
out->mbps = 0.0;
out->seconds = 0.0;
out->bytes = 0;
out->rc = 0;
int rc = prng->init( &state, &seed );
if( rc != 0 )
{
out->rc = rc;
if( state )
free( state );
return;
}
const double t0 = nwipe_prng_monotonic_seconds();
double now = t0;
while( ( now - t0 ) < seconds_per_prng )
{
rc = prng->read( &state, io_buf, io_block );
if( rc != 0 )
{
out->rc = rc;
break;
}
out->bytes += (unsigned long long) io_block;
now = nwipe_prng_monotonic_seconds();
}
out->seconds = now - t0;
if( out->rc == 0 && out->seconds > 0.0 )
{
out->mbps = ( (double) out->bytes / ( 1024.0 * 1024.0 ) ) / out->seconds;
}
if( state )
free( state );
}
int nwipe_prng_benchmark_all( double seconds_per_prng,
size_t io_block_bytes,
nwipe_prng_bench_result_t* results,
size_t results_count )
{
if( results == NULL || results_count == 0 )
return 0;
/* Anzahl PRNGs begrenzen auf results_count */
size_t max = sizeof( all_prngs ) / sizeof( all_prngs[0] );
if( results_count < max )
max = results_count;
void* io_buf = nwipe_prng_alloc_aligned( 4096, io_block_bytes );
if( !io_buf )
{
nwipe_log( NWIPE_LOG_ERROR, "PRNG benchmark: unable to allocate %zu bytes buffer", io_block_bytes );
return -1;
}
for( size_t i = 0; i < max; i++ )
{
nwipe_prng_bench_result_t* r = &results[i];
memset( r, 0, sizeof( *r ) );
nwipe_prng_bench_one( all_prngs[i], r, io_buf, io_block_bytes, seconds_per_prng );
}
free( io_buf );
return (int) max;
}
const nwipe_prng_t* nwipe_prng_select_fastest( double seconds_per_prng,
size_t io_block_bytes,
nwipe_prng_bench_result_t* results,
size_t results_count )
{
int n = nwipe_prng_benchmark_all( seconds_per_prng, io_block_bytes, results, results_count );
if( n <= 0 )
return NULL;
printf( "Analysing PRNG performance:\n" );
fflush( stdout );
for( int i = 0; i < n; i++ )
{
if( results[i].prng == NULL )
continue;
if( results[i].rc == 0 )
printf( "%-22s -> %8.1f MB/s\n", results[i].prng->label, results[i].mbps );
else
printf( "%-22s -> (failed: rc=%d)\n", results[i].prng->label, results[i].rc );
fflush( stdout );
}
const nwipe_prng_t* best = NULL;
double best_mbps = 0.0;
for( int i = 0; i < n; i++ )
{
if( results[i].rc == 0 && results[i].mbps > best_mbps )
{
best_mbps = results[i].mbps;
best = results[i].prng;
}
}
if( best == NULL )
{
nwipe_log( NWIPE_LOG_WARNING, "Auto PRNG selection: no successful PRNG benchmark" );
printf( "Auto PRNG selection: no successful PRNG benchmark\n" );
fflush( stdout );
}
else
{
printf( "Selected PRNG: %s\n", best->label );
fflush( stdout );
}
return best;
}

View File

@@ -45,6 +45,25 @@ typedef struct
nwipe_prng_read_t read; // Read data from the prng.
} nwipe_prng_t;
typedef struct
{
const nwipe_prng_t* prng;
double mbps;
double seconds;
unsigned long long bytes;
int rc;
} nwipe_prng_bench_result_t;
int nwipe_prng_benchmark_all( double seconds_per_prng,
size_t io_block_bytes,
nwipe_prng_bench_result_t* results,
size_t results_count );
const nwipe_prng_t* nwipe_prng_select_fastest( double seconds_per_prng,
size_t io_block_bytes,
nwipe_prng_bench_result_t* results,
size_t results_count );
/* Mersenne Twister prototypes. */
int nwipe_twister_init( NWIPE_PRNG_INIT_SIGNATURE );
int nwipe_twister_read( NWIPE_PRNG_READ_SIGNATURE );