summaryrefslogtreecommitdiffstats
path: root/src/nwipe.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 22:13:02 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 22:13:02 +0000
commite244c93d21bcacf1a0eedefdcc3018a362274796 (patch)
tree857e0d7bcf20546b38fff97a8702c2c1240bc23b /src/nwipe.c
parentInitial commit. (diff)
downloadnwipe-e244c93d21bcacf1a0eedefdcc3018a362274796.tar.xz
nwipe-e244c93d21bcacf1a0eedefdcc3018a362274796.zip
Adding upstream version 0.36.upstream/0.36
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/nwipe.c')
-rw-r--r--src/nwipe.c1134
1 files changed, 1134 insertions, 0 deletions
diff --git a/src/nwipe.c b/src/nwipe.c
new file mode 100644
index 0000000..9dc8110
--- /dev/null
+++ b/src/nwipe.c
@@ -0,0 +1,1134 @@
+/*
+ * nwipe.c: Darik's Wipe.
+ *
+ * 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.
+ *
+ */
+#ifndef _DEFAULT_SOURCE
+#define _DEFAULT_SOURCE
+#endif
+
+#ifndef _POSIX_SOURCE
+#define _POSIX_SOURCE
+#endif
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <signal.h>
+#include <pthread.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "nwipe.h"
+#include "context.h"
+#include "method.h"
+#include "prng.h"
+#include "options.h"
+#include "device.h"
+#include "logging.h"
+#include "gui.h"
+#include "temperature.h"
+#include "miscellaneous.h"
+
+#include <sys/ioctl.h> /* FIXME: Twice Included */
+#include <sys/shm.h>
+#include <wait.h>
+
+#include <parted/parted.h>
+#include <parted/debug.h>
+#include "conf.h"
+#include "version.h"
+#include "hpa_dco.h"
+#include "conf.h"
+#include <libconfig.h>
+
+int terminate_signal;
+int user_abort;
+int global_wipe_status;
+
+/* helper function for sorting */
+int devnamecmp( const void* a, const void* b )
+{
+ // nwipe_log( NWIPE_LOG_DEBUG, "a: %s, b: %s", ( *( nwipe_context_t** ) a)->device_name, ( *( nwipe_context_t** )
+ // b)->device_name );
+
+ int ldiff = strlen( ( *(nwipe_context_t**) a )->device_name ) - strlen( ( *(nwipe_context_t**) b )->device_name );
+ if( ldiff != 0 )
+ {
+ return ldiff;
+ }
+ int ret = strcmp( ( *(nwipe_context_t**) a )->device_name, ( *(nwipe_context_t**) b )->device_name );
+ return ( ret );
+}
+
+int main( int argc, char** argv )
+{
+ int nwipe_optind; // The result of nwipe_options().
+ int nwipe_enumerated; // The number of contexts that have been enumerated.
+ int nwipe_error = 0; // An error counter.
+ int nwipe_selected = 0; // The number of contexts that have been selected.
+ int any_threads_still_running; // used in wipe thread cancellation wait loop
+ int thread_timeout_counter; // timeout thread cancellation after THREAD_CANCELLATION_TIMEOUT seconds
+ pthread_t nwipe_gui_thread = 0; // The thread ID of the GUI thread.
+ pthread_t nwipe_temperature_thread = 0; // The thread ID of the temperature update thread
+ pthread_t nwipe_sigint_thread; // The thread ID of the sigint handler.
+
+ char modprobe_command[] = "modprobe %s";
+ char modprobe_command2[] = "/sbin/modprobe %s";
+ char modprobe_command3[] = "/usr/sbin/modprobe %s";
+ char module_shortform[50];
+ char final_cmd_modprobe[sizeof( modprobe_command ) + sizeof( module_shortform )];
+
+ /* The entropy source file handle. */
+ int nwipe_entropy;
+
+ /* The generic index variables. */
+ int i;
+ int j;
+
+ /* The generic result buffer. */
+ int r;
+
+ /* Initialise the termintaion signal, 1=terminate nwipe */
+ terminate_signal = 0;
+
+ /* Initialise the user abort signal, 1=User aborted with CNTRL-C,SIGTERM, SIGQUIT, SIGINT etc.. */
+ user_abort = 0;
+
+ /* nwipes return status value, set prior to exit at the end of nwipe, as no other exit points allowed */
+ int return_status = 0;
+
+ /* Initialise, flag indicating whether a wipe has actually started or not 0=no, 1=yes */
+ global_wipe_status = 0;
+
+ /* Initialise flags that indicate whether a fatal or non fatal error occurred on ANY drive */
+ int fatal_errors_flag = 0;
+ int non_fatal_errors_flag = 0;
+
+ /* Two arrays are used, containing pointers to the the typedef for each disk */
+ /* The first array (c1) points to all devices, the second points to only */
+ /* the disks selected for wiping. */
+
+ /* The array of pointers to enumerated contexts. */
+ /* Initialised and populated in device scan. */
+ nwipe_context_t** c1 = 0;
+
+ if( geteuid() != 0 )
+ {
+ printf( "nwipe must run with root permissions, which is not the case.\nAborting\n" );
+ exit( 99 );
+ }
+
+ int wipe_threads_started = 0;
+
+ /** NOTE ** NOTE ** NOTE ** NOTE ** NOTE ** NOTE ** NOTE ** NOTE ** NOTE **
+ * Important Note: if you want nwipe_log messages to go into the logfile
+ * any 'nwipe_log()' commands must appear after the options are parsed here,
+ * else they will appear in the console but not in the logfile, that is,
+ * assuming you specified a log file on the command line as an nwipe option.
+ */
+
+ /*****************************
+ * Parse command line options.
+ */
+
+ /* Initialise the libconfig code that handles nwipe.conf */
+ nwipe_conf_init();
+
+ nwipe_optind = nwipe_options_parse( argc, argv );
+
+ /* Log nwipes version */
+ nwipe_log( NWIPE_LOG_INFO, "%s", banner );
+
+ /* Log OS info */
+ nwipe_log_OSinfo();
+
+ /* 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 -'
+ */
+ if( system( "which hdparm > /dev/null 2>&1" ) )
+ {
+ if( system( "which /sbin/hdparm > /dev/null 2>&1" ) )
+ {
+ if( system( "which /usr/bin/hdparm > /dev/null 2>&1" ) )
+ {
+ nwipe_log( NWIPE_LOG_WARNING, "hdparm command not found." );
+ nwipe_log( NWIPE_LOG_WARNING,
+ "Required by nwipe for HPA/DCO detection & correction and ATA secure erase." );
+ nwipe_log( NWIPE_LOG_WARNING, "** Please install hdparm **\n" );
+ cleanup();
+ exit( 1 );
+ }
+ }
+ }
+
+ /* Check if the given path for PDF reports is a writeable directory */
+ if( strcmp( nwipe_options.PDFreportpath, "noPDF" ) != 0 )
+ {
+ if( access( nwipe_options.PDFreportpath, W_OK ) != 0 )
+ {
+ nwipe_log( NWIPE_LOG_ERROR, "PDFreportpath %s is not a writeable directory.", nwipe_options.PDFreportpath );
+ cleanup();
+ exit( 2 );
+ }
+ }
+
+ if( nwipe_optind == argc )
+ {
+ /* File names were not given by the user. Scan for devices. */
+ nwipe_enumerated = nwipe_device_scan( &c1 );
+
+ if( terminate_signal == 1 )
+ {
+ cleanup();
+ exit( 1 );
+ }
+
+ if( nwipe_enumerated == 0 )
+ {
+ nwipe_log( NWIPE_LOG_INFO,
+ "Storage devices not found. Nwipe should be run as root or sudo/su, i.e sudo nwipe etc" );
+ cleanup();
+ return -1;
+ }
+ else
+ {
+ nwipe_log( NWIPE_LOG_INFO, "Automatically enumerated %i devices.", nwipe_enumerated );
+ }
+ }
+ else
+ {
+ argv += nwipe_optind;
+ argc -= nwipe_optind;
+
+ nwipe_enumerated = nwipe_device_get( &c1, argv, argc );
+ if( nwipe_enumerated == 0 )
+ {
+ nwipe_log( NWIPE_LOG_ERROR, "Devices not found. Check you're not excluding drives unnecessarily," );
+ nwipe_log( NWIPE_LOG_ERROR, "and you are running nwipe as sudo or as root." );
+ printf( "Devices not found, check you're not excluding drives unnecessarily \n and you are running nwipe "
+ "as sudo or as root." );
+ cleanup();
+ exit( 1 );
+ }
+ }
+
+ /* sort list of devices here */
+ qsort( (void*) c1, (size_t) nwipe_enumerated, sizeof( nwipe_context_t* ), devnamecmp );
+
+ if( terminate_signal == 1 )
+ {
+ cleanup();
+ exit( 1 );
+ }
+
+ /* Log the System information */
+ nwipe_log_sysinfo();
+
+ /* The array of pointers to contexts that will actually be wiped. */
+ nwipe_context_t** c2 = (nwipe_context_t**) malloc( nwipe_enumerated * sizeof( nwipe_context_t* ) );
+ if( c2 == NULL )
+ {
+ nwipe_log( NWIPE_LOG_ERROR, "memory allocation for c2 failed" );
+ cleanup();
+ exit( 1 );
+ }
+
+ /* Open the entropy source. */
+ nwipe_entropy = open( NWIPE_KNOB_ENTROPY, O_RDONLY );
+
+ /* Check the result. */
+ if( nwipe_entropy < 0 )
+ {
+ nwipe_perror( errno, __FUNCTION__, "open" );
+ nwipe_log( NWIPE_LOG_FATAL, "Unable to open entropy source %s.", NWIPE_KNOB_ENTROPY );
+ cleanup();
+ free( c2 );
+ return errno;
+ }
+
+ nwipe_log( NWIPE_LOG_NOTICE, "Opened entropy source '%s'.", NWIPE_KNOB_ENTROPY );
+
+ /* Block relevant signals in main thread. Any other threads that are */
+ /* created after this will also block those signals. */
+ sigset_t sigset;
+ sigemptyset( &sigset );
+ sigaddset( &sigset, SIGHUP );
+ sigaddset( &sigset, SIGTERM );
+ sigaddset( &sigset, SIGQUIT );
+ sigaddset( &sigset, SIGINT );
+ sigaddset( &sigset, SIGUSR1 );
+ pthread_sigmask( SIG_SETMASK, &sigset, NULL );
+
+ /* Create a signal handler thread. This thread will catch all */
+ /* signals and decide what to do with them. This will only */
+ /* catch nondirected signals. (I.e., if a thread causes a SIGFPE */
+ /* then that thread will get that signal. */
+
+ /* Pass a pointer to a struct containing all data to the signal handler. */
+ nwipe_misc_thread_data_t nwipe_misc_thread_data;
+ nwipe_thread_data_ptr_t nwipe_thread_data_ptr;
+
+ nwipe_thread_data_ptr.c = c2;
+ nwipe_misc_thread_data.nwipe_enumerated = nwipe_enumerated;
+ nwipe_misc_thread_data.nwipe_selected = 0;
+ if( !nwipe_options.nogui )
+ nwipe_misc_thread_data.gui_thread = &nwipe_gui_thread;
+ nwipe_thread_data_ptr.nwipe_misc_thread_data = &nwipe_misc_thread_data;
+
+ if( !nwipe_options.nosignals )
+ {
+ pthread_attr_t pthread_attr;
+ pthread_attr_init( &pthread_attr );
+ pthread_attr_setdetachstate( &pthread_attr, PTHREAD_CREATE_DETACHED );
+
+ pthread_create( &nwipe_sigint_thread, &pthread_attr, signal_hand, &nwipe_thread_data_ptr );
+ }
+
+ /* Makesure the drivetemp module is loaded, else drives hwmon entries won't appear in /sys/class/hwmon */
+ final_cmd_modprobe[0] = 0;
+
+ /* The kernel module we are going to load */
+ strcpy( module_shortform, "drivetemp" );
+
+ /* Determine whether we can access modprobe, required if the PATH environment is not setup ! (Debian sid 'su' as
+ * opposed to 'su -' */
+
+ if( system( "which modprobe > /dev/null 2>&1" ) )
+ {
+ if( system( "which /sbin/modprobe > /dev/null 2>&1" ) )
+ {
+ if( system( "which /usr/sbin/modprobe > /dev/null 2>&1" ) )
+ {
+ nwipe_log( NWIPE_LOG_WARNING, "modprobe command not found. Install kmod package (modprobe)) !" );
+ nwipe_log( NWIPE_LOG_WARNING, "Most temperature monitoring may be unavailable as module drivetemp" );
+ nwipe_log( NWIPE_LOG_WARNING, "could not be loaded. drivetemp is not available on kernels < v5.5" );
+ }
+ else
+ {
+ sprintf( final_cmd_modprobe, modprobe_command3, module_shortform );
+ }
+ }
+ else
+ {
+ sprintf( final_cmd_modprobe, modprobe_command2, module_shortform );
+ }
+ }
+ else
+ {
+ sprintf( final_cmd_modprobe, modprobe_command, module_shortform );
+ }
+
+ /* load the drivetemp module */
+ if( system( final_cmd_modprobe ) != 0 )
+ {
+ nwipe_log( NWIPE_LOG_WARNING, "hwmon: Unable to load module drivetemp, temperatures may be unavailable." );
+ nwipe_log( NWIPE_LOG_WARNING, "hwmon: It's possible the drivetemp software isn't modular but built-in" );
+ nwipe_log( NWIPE_LOG_WARNING, "hwmon: to the kernel, as is the case with ShredOS.x86_64 in which case" );
+ nwipe_log( NWIPE_LOG_WARNING, "hwmon: the temperatures will actually be available despite this issue." );
+ }
+ else
+ {
+ nwipe_log( NWIPE_LOG_NOTICE, "hwmon: Module drivetemp loaded, drive temperatures available" );
+ }
+
+ /* A context struct for each device has already been created. */
+ /* Now set specific nwipe options */
+ for( i = 0; i < nwipe_enumerated; i++ )
+ {
+
+ /* Set the entropy source. */
+ c1[i]->entropy_fd = nwipe_entropy;
+
+ if( nwipe_options.autonuke )
+ {
+ /* When the autonuke option is set, select all disks. */
+ // TODO - partitions
+ // if( c1[i].device_part == 0 ) { c1[i].select = NWIPE_SELECT_TRUE; }
+ // else { c1[i].select = NWIPE_SELECT_TRUE_PARENT; }
+ c1[i]->select = NWIPE_SELECT_TRUE;
+ }
+ else
+ {
+ /* The user must manually select devices. */
+ c1[i]->select = NWIPE_SELECT_FALSE;
+ }
+
+ /* Initialise temperature variables for device */
+ nwipe_init_temperature( c1[i] );
+ if( nwipe_options.verbose )
+ {
+ nwipe_log( NWIPE_LOG_NOTICE, "hwmon: Device %s hwmon path = %s", c1[i]->device_name, c1[i]->temp1_path );
+ }
+
+ // nwipe_update_temperature( c1[i] );
+
+ /* Log the temperature crtical, highest, lowest and lowest critical temperature
+ * limits to nwipes log file using the INFO catagory
+ */
+
+ nwipe_log_drives_temperature_limits( c1[i] );
+ }
+
+ /* Check for initialization errors. */
+ if( nwipe_error )
+ {
+ nwipe_log( NWIPE_LOG_ERROR, "Initialization error %i\n", nwipe_error );
+ cleanup();
+ return -1;
+ }
+
+ /* Set up the data structures to pass the temperature thread the data it needs */
+ nwipe_thread_data_ptr_t nwipe_temperature_thread_data;
+ nwipe_temperature_thread_data.c = c1;
+ nwipe_temperature_thread_data.nwipe_misc_thread_data = &nwipe_misc_thread_data;
+
+ /* Fork the temperature thread */
+ errno = pthread_create(
+ &nwipe_temperature_thread, NULL, nwipe_update_temperature_thread, &nwipe_temperature_thread_data );
+
+ /* Start the ncurses interface. */
+ if( !nwipe_options.nogui )
+ nwipe_gui_init();
+
+ if( nwipe_options.autonuke == 1 )
+ {
+ /* Print the options window. */
+ if( !nwipe_options.nogui )
+ nwipe_gui_options();
+ }
+ else
+ {
+ /* Get device selections from the user. */
+ if( nwipe_options.nogui )
+ {
+ printf( "--nogui option must be used with autonuke option\n" );
+ cleanup();
+ exit( 1 );
+ }
+ else
+ {
+ if( nwipe_options.PDF_preview_details == 1 )
+ {
+ nwipe_gui_preview_org_customer( SHOWING_PRIOR_TO_DRIVE_SELECTION );
+ }
+
+ nwipe_gui_select( nwipe_enumerated, c1 );
+ }
+ }
+
+ /* Initialise some of the variables in the drive contexts
+ */
+ for( i = 0; i < nwipe_enumerated; i++ )
+ {
+ /* Set the PRNG implementation, which must always come after the function nwipe_gui_select ! */
+ c1[i]->prng = nwipe_options.prng;
+ c1[i]->prng_seed.length = 0;
+ c1[i]->prng_seed.s = 0;
+ c1[i]->prng_state = 0;
+
+ /* Count the number of selected contexts. */
+ if( c1[i]->select == NWIPE_SELECT_TRUE )
+ {
+ nwipe_selected += 1;
+ }
+
+ /* Initialise the wipe result value */
+ c1[i]->result = 0;
+
+ /* Initialise the variable that tracks how much of the drive has been erased */
+ c1[i]->bytes_erased = 0;
+ }
+
+ /* Pass the number selected to the struct for other threads */
+ nwipe_misc_thread_data.nwipe_selected = nwipe_selected;
+
+ /* Populate the array of selected contexts. */
+ for( i = 0, j = 0; i < nwipe_enumerated; i++ )
+ {
+ if( c1[i]->select == NWIPE_SELECT_TRUE )
+ {
+ /* Copy the context. */
+ c2[j++] = c1[i];
+ }
+ }
+
+ /* TODO: free c1 and c2 memory. */
+ if( user_abort == 0 )
+ {
+ /* Log the wipe options that have been selected immediately prior to the start of the wipe */
+ nwipe_options_log();
+
+ /* The wipe has been initiated */
+ global_wipe_status = 1;
+
+ for( i = 0; i < nwipe_selected; i++ )
+ {
+ /* A result buffer for the BLKGETSIZE64 ioctl. */
+ u64 size64;
+
+ /* Initialise the spinner character index */
+ c2[i]->spinner_idx = 0;
+
+ /* Initialise the start and end time of the wipe */
+ c2[i]->start_time = 0;
+ c2[i]->end_time = 0;
+
+ /* 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 );
+
+ /* 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;
+ }
+
+ /* Stat the file. */
+ if( fstat( c2[i]->device_fd, &c2[i]->device_stat ) != 0 )
+ {
+ nwipe_perror( errno, __FUNCTION__, "fstat" );
+ nwipe_log( NWIPE_LOG_ERROR, "Unable to stat file '%s'.", c2[i]->device_name );
+ nwipe_error++;
+ continue;
+ }
+
+ /* Check that the file is a block device. */
+ if( !S_ISBLK( c2[i]->device_stat.st_mode ) )
+ {
+ nwipe_log( NWIPE_LOG_ERROR, "'%s' is not a block device.", c2[i]->device_name );
+ nwipe_error++;
+ continue;
+ }
+
+ /* TODO: Lock the file for exclusive access. */
+ /*
+ if( flock( c2[i]->device_fd, LOCK_EX | LOCK_NB ) != 0 )
+ {
+ nwipe_perror( errno, __FUNCTION__, "flock" );
+ nwipe_log( NWIPE_LOG_ERROR, "Unable to lock the '%s' file.", c2[i]->device_name );
+ nwipe_error++;
+ continue;
+ }
+ */
+
+ /* Print serial number of device if it exists. */
+ if( strlen( (const char*) c2[i]->device_serial_no ) )
+ {
+ nwipe_log( NWIPE_LOG_NOTICE, "%s has serial number %s", c2[i]->device_name, c2[i]->device_serial_no );
+ }
+
+ /* Do sector size and block size checking. I don't think this does anything useful as logical/Physical
+ * sector sizes are obtained by libparted in check.c */
+ if( ioctl( c2[i]->device_fd, BLKSSZGET, &c2[i]->device_sector_size ) == 0 )
+ {
+
+ if( ioctl( c2[i]->device_fd, BLKBSZGET, &c2[i]->device_block_size ) != 0 )
+ {
+ nwipe_log( NWIPE_LOG_WARNING, "Device '%s' failed BLKBSZGET ioctl.", c2[i]->device_name );
+ c2[i]->device_block_size = 0;
+ }
+ }
+ else
+ {
+ nwipe_log( NWIPE_LOG_WARNING, "Device '%s' failed BLKSSZGET ioctl.", c2[i]->device_name );
+ c2[i]->device_sector_size = 0;
+ c2[i]->device_block_size = 0;
+ }
+
+ /* The st_size field is zero for block devices. */
+ /* ioctl( c2[i]->device_fd, BLKGETSIZE64, &c2[i]->device_size ); */
+
+ /* Seek to the end of the device to determine its size. */
+ c2[i]->device_size = lseek( c2[i]->device_fd, 0, SEEK_END );
+
+ /* Also ask the driver for the device size. */
+ /* if( ioctl( c2[i]->device_fd, BLKGETSIZE64, &size64 ) ) */
+ if( ioctl( c2[i]->device_fd, _IOR( 0x12, 114, size_t ), &size64 ) )
+ {
+ /* The ioctl failed. */
+ fprintf( stderr, "Error: BLKGETSIZE64 failed on '%s'.\n", c2[i]->device_name );
+ nwipe_log( NWIPE_LOG_ERROR, "BLKGETSIZE64 failed on '%s'.\n", c2[i]->device_name );
+ nwipe_error++;
+ }
+ c2[i]->device_size = size64;
+
+ /* Check whether the two size values agree. */
+ if( c2[i]->device_size != size64 )
+ {
+ /* This could be caused by the linux last-odd-block problem. */
+ fprintf( stderr, "Error: Last-odd-block detected on '%s'.\n", c2[i]->device_name );
+ nwipe_log( NWIPE_LOG_ERROR, "Last-odd-block detected on '%s'.", c2[i]->device_name );
+ nwipe_error++;
+ }
+
+ if( c2[i]->device_size == (long long) -1 )
+ {
+ /* We cannot determine the size of this device. */
+ nwipe_perror( errno, __FUNCTION__, "lseek" );
+ nwipe_log( NWIPE_LOG_ERROR, "Unable to determine the size of '%s'.", c2[i]->device_name );
+ nwipe_error++;
+ }
+ else
+ {
+ /* Reset the file pointer. */
+ r = lseek( c2[i]->device_fd, 0, SEEK_SET );
+
+ if( r == (off64_t) -1 )
+ {
+ nwipe_perror( errno, __FUNCTION__, "lseek" );
+ nwipe_log( NWIPE_LOG_ERROR, "Unable to reset the '%s' file offset.", c2[i]->device_name );
+ nwipe_error++;
+ }
+ }
+
+ if( c2[i]->device_size == 0 )
+ {
+ nwipe_log( NWIPE_LOG_ERROR,
+ "%s, sect/blk/dev %i/%i/%llu",
+ c2[i]->device_name,
+ c2[i]->device_sector_size,
+ c2[i]->device_block_size,
+ c2[i]->device_size );
+ nwipe_error++;
+ continue;
+ }
+ else
+ {
+ nwipe_log( NWIPE_LOG_NOTICE,
+ "%s, sect/blk/dev %i/%i/%llu",
+ c2[i]->device_name,
+ c2[i]->device_sector_size,
+ c2[i]->device_block_size,
+ c2[i]->device_size );
+ }
+
+ /* Fork a child process. */
+ errno = pthread_create( &c2[i]->thread, NULL, nwipe_options.method, (void*) c2[i] );
+ if( errno )
+ {
+ nwipe_perror( errno, __FUNCTION__, "pthread_create" );
+ if( !nwipe_options.nogui )
+ nwipe_gui_free();
+ return errno;
+ }
+ else
+ {
+ wipe_threads_started = 1;
+ }
+ }
+ }
+
+ /* Change the terminal mode to non-blocking input. */
+ nodelay( stdscr, 0 );
+
+ /* Set getch to delay in order to slow screen updates. */
+ halfdelay( NWIPE_KNOB_SLEEP * 10 );
+
+ /* Set up data structs to pass the GUI thread the data it needs. */
+ nwipe_thread_data_ptr_t nwipe_gui_data;
+ if( !nwipe_options.nogui )
+ {
+ nwipe_gui_data.c = c2;
+ nwipe_gui_data.nwipe_misc_thread_data = &nwipe_misc_thread_data;
+ /* Fork the GUI thread. */
+ errno = pthread_create( &nwipe_gui_thread, NULL, nwipe_gui_status, &nwipe_gui_data );
+ }
+
+ /* Wait for all the wiping threads to finish, but don't wait if we receive the terminate signal */
+
+ /* set getch delay to 2/10th second. */
+ halfdelay( 10 );
+
+ i = 0;
+ while( i < nwipe_selected && terminate_signal == 0 )
+ {
+ if( i == nwipe_selected )
+ {
+ break;
+ }
+
+ if( c2[i]->wipe_status != 0 )
+ {
+ i = 0;
+ }
+ else
+ {
+ i++;
+ continue;
+ }
+ sleep( 1 ); /* DO NOT REMOVE ! Stops the routine hogging CPU cycles */
+ }
+
+ if( terminate_signal != 1 )
+ {
+ if( !nwipe_options.nowait && !nwipe_options.autopoweroff )
+ {
+ do
+ {
+ sleep( 1 );
+
+ } while( terminate_signal != 1 );
+ }
+ }
+ if( nwipe_options.verbose )
+ {
+ nwipe_log( NWIPE_LOG_INFO, "Exit in progress" );
+ }
+ /* Send a REQUEST for the wipe threads to be cancelled */
+ for( i = 0; i < nwipe_selected; i++ )
+ {
+
+ if( c2[i]->thread )
+ {
+ if( nwipe_options.verbose )
+ {
+ nwipe_log( NWIPE_LOG_INFO, "Requesting wipe thread cancellation for %s", c2[i]->device_name );
+ }
+ pthread_cancel( c2[i]->thread );
+ }
+ }
+
+ /* Kill the GUI thread */
+ if( nwipe_gui_thread )
+ {
+ if( nwipe_options.verbose )
+ {
+ nwipe_log( NWIPE_LOG_INFO, "Cancelling the GUI thread." );
+ }
+
+ /* We don't want to use pthread_cancel as our GUI thread is aware of the control-c
+ * signal and will exit itself we just join the GUI thread and wait for confirmation
+ */
+ r = pthread_join( nwipe_gui_thread, NULL );
+ if( r != 0 )
+ {
+ nwipe_log( NWIPE_LOG_WARNING, "main()>pthread_join():Error when waiting for GUI thread to cancel." );
+ }
+ else
+ {
+ if( nwipe_options.verbose )
+ {
+ nwipe_log( NWIPE_LOG_INFO, "GUI compute_stats thread has been cancelled" );
+ }
+ }
+ }
+
+ /* Release the gui. */
+ if( !nwipe_options.nogui )
+ {
+ nwipe_gui_free();
+ }
+
+ /* Now join the wipe threads and wait until they have terminated */
+ any_threads_still_running = 1;
+ thread_timeout_counter = THREAD_CANCELLATION_TIMEOUT;
+ while( any_threads_still_running )
+ {
+ /* quit waiting if we've tried 'thread_timeout_counter' times */
+ if( thread_timeout_counter == 0 )
+ {
+ break;
+ }
+
+ any_threads_still_running = 0;
+ for( i = 0; i < nwipe_selected; i++ )
+ {
+ if( c2[i]->thread )
+ {
+ printf( "\nWaiting for wipe thread to cancel for %s\n", c2[i]->device_name );
+
+ /* Joins the thread and waits for completion before continuing */
+ r = pthread_join( c2[i]->thread, NULL );
+ if( r != 0 )
+ {
+ nwipe_log( NWIPE_LOG_ERROR,
+ "Error joining the wipe thread when waiting for thread to cancel.",
+ c2[i]->device_name );
+
+ if( r == EDEADLK )
+ {
+ nwipe_log( NWIPE_LOG_ERROR,
+ "Error joining the wipe thread: EDEADLK: Deadlock detected.",
+ c2[i]->device_name );
+ }
+ else
+ {
+ if( r == EINVAL )
+ {
+ nwipe_log( NWIPE_LOG_ERROR,
+ "Error joining the wipe thread: %s EINVAL: thread is not joinable.",
+ c2[i]->device_name );
+ }
+ else
+ {
+ if( r == ESRCH )
+ {
+ nwipe_log( NWIPE_LOG_ERROR,
+ "Error joining the wipe thread: %s ESRCH: no matching thread found",
+ c2[i]->device_name );
+ }
+ }
+ }
+
+ any_threads_still_running = 1;
+ }
+ else
+ {
+ c2[i]->thread = 0; /* Zero the thread so we know it's been cancelled */
+
+ if( nwipe_options.verbose )
+ {
+ nwipe_log( NWIPE_LOG_INFO, "Wipe thread for device %s has terminated", c2[i]->device_name );
+ }
+
+ /* Close the device file descriptor. */
+ close( c2[i]->device_fd );
+ }
+ }
+ }
+ thread_timeout_counter--;
+ sleep( 1 );
+ }
+
+ /* Now all the wipe threads have finished, we can issue a terminate_signal = 1
+ * which will cause the temperature update thread to terminate, this is necessary
+ * because in gui mode the terminate_signal is set when the user presses a key to
+ * exit on completion of all the wipes, however in non gui mode that code isn't
+ * active (being in the gui section) so here we need to set the terminate signal
+ * specifically for a completed wipes/s just for non gui mode.
+ */
+ terminate_signal = 1;
+
+ /* Kill the temperature update thread */
+ if( nwipe_temperature_thread )
+ {
+ if( nwipe_options.verbose )
+ {
+ nwipe_log( NWIPE_LOG_INFO, "Cancelling the temperature thread." );
+ }
+
+ /* We don't want to use pthread_cancel as our temperature thread is aware of the control-c
+ * signal and will exit itself we just join the temperature thread and wait for confirmation
+ */
+ r = pthread_join( nwipe_temperature_thread, NULL );
+ if( r != 0 )
+ {
+ nwipe_log( NWIPE_LOG_WARNING,
+ "main()>pthread_join():Error when waiting for temperature thread to cancel." );
+ }
+ else
+ {
+ if( nwipe_options.verbose )
+ {
+ nwipe_log( NWIPE_LOG_INFO, "temperature thread has been cancelled" );
+ }
+ }
+ }
+
+ if( nwipe_options.verbose )
+ {
+ for( i = 0; i < nwipe_selected; i++ )
+ {
+ nwipe_log( NWIPE_LOG_DEBUG,
+ "Status: %s, result=%d, pass_errors=%llu, verify_errors=%llu, fsync_errors=%llu",
+ c2[i]->device_name,
+ c2[i]->result,
+ c2[i]->pass_errors,
+ c2[i]->verify_errors,
+ c2[i]->fsyncdata_errors );
+ }
+ }
+
+ /* if no wipe threads started then zero each selected drive result flag,
+ * as we don't need to report fatal/non fatal errors if no wipes were ever started ! */
+ if( wipe_threads_started == 0 )
+ {
+ for( i = 0; i < nwipe_selected; i++ )
+ {
+ c2[i]->result = 0;
+ }
+ }
+ else
+ {
+ for( i = 0; i < nwipe_selected; i++ )
+ {
+ /* Check for errors. */
+ if( c2[i]->result != 0 || c2[i]->pass_errors != 0 || c2[i]->verify_errors != 0
+ || c2[i]->fsyncdata_errors != 0 )
+ {
+ /* If the run_method ever returns anything other than zero then makesure there is at least one pass
+ * error This is so that the log summary tables correctly show a failure when one occurs as it only
+ * shows pass, verification and fdatasync errors. */
+ if( c2[i]->result != 0 )
+ {
+ if( c2[i]->pass_errors == 0 )
+ {
+ c2[i]->pass_errors = 1;
+ }
+ }
+
+ nwipe_log( NWIPE_LOG_FATAL,
+ "Nwipe exited with errors on device = %s, see log for specific error\n",
+ c2[i]->device_name );
+ nwipe_log( NWIPE_LOG_DEBUG,
+ "Status: %s, result=%d, pass_errors=%llu, verify_errors=%llu, fsync_errors=%llu",
+ c2[i]->device_name,
+ c2[i]->result,
+ c2[i]->pass_errors,
+ c2[i]->verify_errors,
+ c2[i]->fsyncdata_errors );
+ non_fatal_errors_flag = 1;
+ return_status = 1;
+ }
+ }
+ }
+
+ /* Generate and send the drive status summary to the log */
+ nwipe_log_summary( c2, nwipe_selected );
+
+ /* Print a one line status message for the user */
+ if( return_status == 0 || return_status == 1 )
+ {
+ if( user_abort == 1 )
+ {
+ if( global_wipe_status == 1 )
+ {
+ nwipe_log( NWIPE_LOG_INFO,
+ "Nwipe was aborted by the user. Check the summary table for the drive status." );
+ }
+ else
+ {
+ nwipe_log( NWIPE_LOG_INFO, "Nwipe was aborted by the user prior to the wipe starting." );
+ }
+ }
+ else
+ {
+ if( fatal_errors_flag == 1 || non_fatal_errors_flag == 1 )
+ {
+ nwipe_log( NWIPE_LOG_INFO,
+ "Nwipe exited with errors, check the log & summary table for individual drive status." );
+ }
+ else
+ {
+ nwipe_log( NWIPE_LOG_INFO, "Nwipe successfully completed. See summary table for details." );
+ }
+ }
+ }
+
+ cleanup();
+
+ check_for_autopoweroff();
+
+ /* Exit. */
+ return return_status;
+}
+
+void* signal_hand( void* ptr )
+{
+ int sig;
+ int hours;
+ int minutes;
+ int seconds;
+
+ hours = 0;
+ minutes = 0;
+ seconds = 0;
+
+ // Define signals that this handler should react to
+ sigset_t sigset;
+ sigemptyset( &sigset );
+ sigaddset( &sigset, SIGHUP );
+ sigaddset( &sigset, SIGTERM );
+ sigaddset( &sigset, SIGQUIT );
+ sigaddset( &sigset, SIGINT );
+ sigaddset( &sigset, SIGUSR1 );
+
+ int i;
+ char eta[9];
+
+ /* Set up the structs we will use for the data required. */
+ nwipe_thread_data_ptr_t* nwipe_thread_data_ptr;
+ nwipe_context_t** c;
+ nwipe_misc_thread_data_t* nwipe_misc_thread_data;
+
+ /* Retrieve from the pointer passed to the function. */
+ nwipe_thread_data_ptr = (nwipe_thread_data_ptr_t*) ptr;
+ c = nwipe_thread_data_ptr->c;
+ nwipe_misc_thread_data = nwipe_thread_data_ptr->nwipe_misc_thread_data;
+
+ while( 1 )
+ {
+ /* wait for a signal to arrive */
+ sigwait( &sigset, &sig );
+
+ switch( sig )
+ {
+
+ // Log current status. All values are automatically updated by the GUI
+ case SIGUSR1:
+ compute_stats( ptr );
+
+ for( i = 0; i < nwipe_misc_thread_data->nwipe_selected; i++ )
+ {
+
+ if( c[i]->thread )
+ {
+ char* status = "";
+ switch( c[i]->pass_type )
+ {
+ case NWIPE_PASS_FINAL_BLANK:
+ status = "[blanking]";
+ break;
+
+ case NWIPE_PASS_FINAL_OPS2:
+ status = "[OPS-II final]";
+ break;
+
+ case NWIPE_PASS_WRITE:
+ status = "[writing]";
+ break;
+
+ case NWIPE_PASS_VERIFY:
+ status = "[verifying]";
+ break;
+
+ case NWIPE_PASS_NONE:
+ break;
+ }
+ if( c[i]->sync_status )
+ {
+ status = "[syncing]";
+ }
+
+ convert_seconds_to_hours_minutes_seconds( c[i]->eta, &hours, &minutes, &seconds );
+
+ nwipe_log( NWIPE_LOG_INFO,
+ "%s: %05.2f%%, round %i of %i, pass %i of %i, eta %02i:%02i:%02i, %s",
+ c[i]->device_name,
+ c[i]->round_percent,
+ c[i]->round_working,
+ c[i]->round_count,
+ c[i]->pass_working,
+ c[i]->pass_count,
+ hours,
+ minutes,
+ seconds,
+ status );
+ }
+ else
+ {
+ if( c[i]->result == 0 )
+ {
+ nwipe_log( NWIPE_LOG_INFO, "%s: Success", c[i]->device_name );
+ }
+ else if( c[i]->signal )
+ {
+ nwipe_log(
+ NWIPE_LOG_INFO, "%s: >>> FAILURE! <<<: signal %i", c[i]->device_name, c[i]->signal );
+ }
+ else
+ {
+ nwipe_log(
+ NWIPE_LOG_INFO, "%s: >>> FAILURE! <<<: code %i", c[i]->device_name, c[i]->result );
+ }
+ }
+ }
+
+ break;
+
+ case SIGHUP:
+ case SIGINT:
+ case SIGQUIT:
+ case SIGTERM:
+ /* Set termination flag for main() which will do housekeeping prior to exit */
+ terminate_signal = 1;
+
+ /* Set the user abort flag */
+ user_abort = 1;
+
+ /* Return control to the main thread, returning the signal received */
+ return ( (void*) 0 );
+
+ break;
+ }
+ }
+
+ return ( 0 );
+}
+
+int cleanup()
+{
+ int i;
+ extern int log_elements_displayed; // initialised and found in logging.c
+ extern int log_elements_allocated; // initialised and found in logging.c
+ extern char** log_lines;
+ extern config_t nwipe_cfg;
+
+ /* Print the logs held in memory to the console */
+ for( i = log_elements_displayed; i < log_elements_allocated; i++ )
+ {
+ printf( "%s\n", log_lines[i] );
+ }
+ fflush( stdout );
+
+ /* Deallocate memory used by logging */
+ if( log_elements_allocated != 0 )
+ {
+ for( i = 0; i < log_elements_allocated; i++ )
+ {
+ free( log_lines[i] );
+ }
+ log_elements_allocated = 0; // zeroed just in case cleanup is called twice.
+ free( log_lines );
+ }
+
+ /* Deallocate libconfig resources */
+ config_destroy( &nwipe_cfg );
+
+ /* TODO: Any other cleanup required ? */
+
+ return 0;
+}
+void check_for_autopoweroff( void )
+{
+ char cmd[] = "shutdown -Ph +1 \"System going down in one minute\"";
+ FILE* fp;
+ int r; // A result buffer.
+
+ /* User request auto power down ? */
+ if( nwipe_options.autopoweroff == 1 )
+ {
+ fp = popen( cmd, "r" );
+ if( fp == NULL )
+ {
+ nwipe_log( NWIPE_LOG_INFO, "Failed to autopoweroff to %s", cmd );
+ return;
+ }
+ r = pclose( fp );
+ }
+}