/* * device.c: Device routines for nwipe. * * Copyright Darik Horn . * * Modifications to original dwipe Copyright Andy Beverley * * 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 #include #include #include #include "nwipe.h" #include "context.h" #include "device.h" #include "method.h" #include "options.h" #include "logging.h" #include #include // Drive specific defs #include #include #include #include #include #include "hpa_dco.h" #include "miscellaneous.h" #include #include int check_device( nwipe_context_t*** c, PedDevice* dev, int dcount ); char* trim( char* str ); extern int terminate_signal; int nwipe_device_scan( nwipe_context_t*** c ) { /** * Scans the filesystem for storage device names. * * @parameter device_names A reference to a null array pointer. * @modifies device_names Populates device_names with an array of nwipe_context_t * @returns The number of strings in the device_names array. * */ PedDevice* dev = NULL; ped_device_probe_all(); int dcount = 0; while( ( dev = ped_device_get_next( dev ) ) ) { /* to have some progress indication. can help if there are many/slow disks */ fprintf( stderr, "." ); if( check_device( c, dev, dcount ) ) dcount++; /* Don't bother scanning drives if the terminate signal is active ! as in the case of * the readlink program missing which is required if the --nousb option has been specified */ if( terminate_signal == 1 ) { break; } } /* Return the number of devices that were found. */ return dcount; } /* nwipe_device_scan */ int nwipe_device_get( nwipe_context_t*** c, char** devnamelist, int ndevnames ) { PedDevice* dev = NULL; int i; int dcount = 0; for( i = 0; i < ndevnames; i++ ) { /* to have some progress indication. can help if there are many/slow disks */ fprintf( stderr, "." ); dev = ped_device_get( devnamelist[i] ); if( !dev ) { nwipe_log( NWIPE_LOG_WARNING, "Device %s not found", devnamelist[i] ); continue; } if( check_device( c, dev, dcount ) ) dcount++; /* Don't bother scanning drives if the terminate signal is active ! as in the case of * the readlink program missing which is required if the --nousb option has been specified */ if( terminate_signal == 1 ) { break; } } /* Return the number of devices that were found. */ return dcount; } /* nwipe_device_get */ int check_device( nwipe_context_t*** c, PedDevice* dev, int dcount ) { /* Populate this struct, then assign it to overall array of structs. */ nwipe_context_t* next_device; int fd; int idx; int r; char tmp_serial[NWIPE_SERIALNUMBER_LENGTH + 1]; nwipe_device_t bus; int is_ssd; int check_HPA; // a flag that indicates whether we check for a HPA on this device bus = 0; /* Check whether this drive is on the excluded drive list ? */ idx = 0; while( idx < MAX_NUMBER_EXCLUDED_DRIVES ) { if( !strcmp( dev->path, nwipe_options.exclude[idx++] ) ) { nwipe_log( NWIPE_LOG_NOTICE, "Device %s excluded as per command line option -e", dev->path ); return 0; } } /* Check whether the user has specified using the --nousb option * that all USB devices should not be displayed or wiped whether * in GUI, --nogui or --autonuke modes */ if( nwipe_options.nousb ) { /* retrieve bus and drive serial number, HOWEVER we are only interested in the bus at this time */ r = nwipe_get_device_bus_type_and_serialno( dev->path, &bus, &is_ssd, tmp_serial ); /* See nwipe_get_device_bus_type_and_serialno() function for meaning of these codes */ if( r == 0 || ( r >= 3 && r <= 6 ) ) { if( bus == NWIPE_DEVICE_USB ) { nwipe_log( NWIPE_LOG_NOTICE, "Device %s ignored as per command line option --nousb", dev->path ); return 0; } } else { if( r == 2 ) { nwipe_log( NWIPE_LOG_NOTICE, "--nousb requires the 'readlink' program, please install readlink", dev->path ); terminate_signal = 1; return 0; } } } /* Try opening the device to see if it's valid. Close on completion. */ if( !ped_device_open( dev ) ) { nwipe_log( NWIPE_LOG_FATAL, "Unable to open device" ); return 0; } ped_device_close( dev ); /* New device, reallocate memory for additional struct pointer */ *c = realloc( *c, ( dcount + 1 ) * sizeof( nwipe_context_t* ) ); next_device = malloc( sizeof( nwipe_context_t ) ); /* Check the allocation. */ if( !next_device ) { nwipe_perror( errno, __FUNCTION__, "malloc" ); nwipe_log( NWIPE_LOG_FATAL, "Unable to create the array of enumeration contexts." ); return 0; } /* Zero the allocation. */ memset( next_device, 0, sizeof( nwipe_context_t ) ); /* Get device information */ next_device->device_model = dev->model; remove_ATA_prefix( next_device->device_model ); /* Some USB adapters have drive model endian swapped, pattern match and fix */ fix_endian_model_names( next_device->device_model ); /* full device name, i.e. /dev/sda */ next_device->device_name = dev->path; /* remove /dev/ from device, right justify and prefix name so string length is eight characters */ nwipe_strip_path( next_device->device_name_without_path, next_device->device_name ); /* To maintain column alignment in the gui we have to remove /dev/ from device names that * exceed eight characters including the /dev/ path. */ if( strlen( next_device->device_name ) > MAX_LENGTH_OF_DEVICE_STRING ) { strcpy( next_device->gui_device_name, next_device->device_name_without_path ); } else { strcpy( next_device->gui_device_name, next_device->device_name ); } next_device->device_size = dev->length * dev->sector_size; next_device->device_sector_size = dev->sector_size; // logical sector size next_device->device_block_size = dev->sector_size; // set as logical but could be a multiple of logical sector size next_device->device_phys_sector_size = dev->phys_sector_size; // physical sector size next_device->device_size_in_sectors = next_device->device_size / next_device->device_sector_size; next_device->device_size_in_512byte_sectors = next_device->device_size / 512; Determine_C_B_nomenclature( next_device->device_size, next_device->device_size_txt, NWIPE_DEVICE_SIZE_TXT_LENGTH ); next_device->device_size_text = next_device->device_size_txt; next_device->result = -2; /* Attempt to get serial number of device. */ next_device->device_serial_no[0] = 0; /* initialise the serial number */ if( ( fd = open( next_device->device_name = dev->path, O_RDONLY ) ) == ERR ) { nwipe_log( NWIPE_LOG_WARNING, "Unable to open device %s to obtain serial number", next_device->device_name ); } /* * We don't check the ioctl return status because there are plenty of situations where a serial number may not be * returned by ioctl such as USB drives, logical volumes, encryted volumes, so the log file would have multiple * benign ioctl errors reported which isn't necessarily a problem. */ ioctl( fd, HDIO_GET_IDENTITY, &next_device->identity ); close( fd ); for( idx = 0; idx < NWIPE_SERIALNUMBER_LENGTH; idx++ ) { if( isascii( next_device->identity.serial_no[idx] ) && !iscntrl( next_device->identity.serial_no[idx] ) ) { next_device->device_serial_no[idx] = next_device->identity.serial_no[idx]; } else { break; } } // Terminate the string. next_device->device_serial_no[idx] = 0; // Remove leading/trailing whitespace from serial number and left justify. trim( (char*) next_device->device_serial_no ); /* if we couldn't obtain serial number by using the above method .. try this */ r = nwipe_get_device_bus_type_and_serialno( next_device->device_name, &next_device->device_type, &next_device->device_is_ssd, tmp_serial ); /* If serial number & bus retrieved (0) OR unsupported USB bus identified (5) */ if( r == 0 || r == 5 ) { /* If the serial number hasn't already been populated */ if( next_device->device_serial_no[0] == 0 ) { strncpy( next_device->device_serial_no, tmp_serial, NWIPE_SERIALNUMBER_LENGTH ); } } /* Does the user want to anonymize serial numbers ? */ if( nwipe_options.quiet ) { if( next_device->device_serial_no[0] == 0 ) { strncpy( next_device->device_serial_no, "????????????????????", NWIPE_SERIALNUMBER_LENGTH + 1 ); } else { strncpy( next_device->device_serial_no, "XXXXXXXXXXXXXXXXXXXX", NWIPE_SERIALNUMBER_LENGTH + 1 ); } } /* strncpy would have copied the null terminator BUT just to be sure, just in case somebody changes the length * of those strings we should explicitly terminate the string */ next_device->device_serial_no[NWIPE_SERIALNUMBER_LENGTH] = 0; /* Initialise the variables that toggle the [size][temp c] with [HPA status] * Not currently used, but may be used in the future or for other purposes */ next_device->HPA_toggle_time = time( NULL ); next_device->HPA_display_toggle_state = 0; /* Initialise the HPA variables for this device */ next_device->HPA_reported_set = 0; next_device->HPA_reported_real = 0; next_device->DCO_reported_real_max_sectors = 0; next_device->HPA_status = HPA_NOT_APPLICABLE; /* All device strings should be 4 characters, prefix with space if under 4 characters * We also set a switch for certain devices to check for the host protected area (HPA) */ check_HPA = 0; // WARNING TEMP LINE WARNING // next_device->device_type = NWIPE_DEVICE_ATA; switch( next_device->device_type ) { case NWIPE_DEVICE_UNKNOWN: strcpy( next_device->device_type_str, " UNK" ); check_HPA = 1; break; case NWIPE_DEVICE_IDE: strcpy( next_device->device_type_str, " IDE" ); check_HPA = 1; break; case NWIPE_DEVICE_SCSI: strcpy( next_device->device_type_str, " SCSI" ); check_HPA = 1; break; case NWIPE_DEVICE_COMPAQ: strcpy( next_device->device_type_str, " CPQ" ); break; case NWIPE_DEVICE_USB: strcpy( next_device->device_type_str, " USB" ); check_HPA = 1; break; case NWIPE_DEVICE_IEEE1394: strcpy( next_device->device_type_str, "1394" ); break; case NWIPE_DEVICE_ATA: strcpy( next_device->device_type_str, " ATA" ); check_HPA = 1; break; case NWIPE_DEVICE_NVME: strcpy( next_device->device_type_str, "NVME" ); break; case NWIPE_DEVICE_VIRT: strcpy( next_device->device_type_str, "VIRT" ); break; case NWIPE_DEVICE_SAS: strcpy( next_device->device_type_str, " SAS" ); check_HPA = 1; break; case NWIPE_DEVICE_MMC: strcpy( next_device->device_type_str, " MMC" ); break; } if( next_device->device_is_ssd ) { strcpy( next_device->device_type_str + 4, "-SSD" ); } else { strcpy( next_device->device_type_str + 4, " " ); } if( strlen( (const char*) next_device->device_serial_no ) ) { snprintf( next_device->device_label, NWIPE_DEVICE_LABEL_LENGTH, "%s %s [%s] %s/%s", next_device->device_name, next_device->device_type_str, next_device->device_size_text, next_device->device_model, next_device->device_serial_no ); } else { snprintf( next_device->device_label, NWIPE_DEVICE_LABEL_LENGTH, "%s %s [%s] %s", next_device->device_name, next_device->device_type_str, next_device->device_size_text, next_device->device_model ); } nwipe_log( NWIPE_LOG_NOTICE, "Found %s, %s, %s, %s, S/N=%s", next_device->device_name, next_device->device_type_str, next_device->device_model, next_device->device_size_text, next_device->device_serial_no ); nwipe_log( NWIPE_LOG_INFO, "%s, sector(logical)/block(physical) sizes %i/%i", next_device->device_name, dev->sector_size, dev->phys_sector_size ); /****************************** * Check for hidden sector_size */ if( check_HPA == 1 ) { hpa_dco_status( next_device ); } /* print an empty line to separate the drives in the log */ nwipe_log( NWIPE_LOG_INFO, " " ); ( *c )[dcount] = next_device; return 1; } /* Remove leading/trailing whitespace from a string and left justify result */ char* trim( char* str ) { size_t len = 0; char* frontp = str; char* endp = NULL; if( str == NULL ) { return NULL; } if( str[0] == '\0' ) { return str; } len = strlen( str ); endp = str + len; /* * Move the front and back pointers to address the first non-whitespace * characters from each end. */ while( isspace( (unsigned char) *frontp ) ) { ++frontp; } if( endp != frontp ) { while( isspace( (unsigned char) *( --endp ) ) && endp != frontp ) { } } if( str + len - 1 != endp ) *( endp + 1 ) = '\0'; else if( frontp != str && endp == frontp ) *str = '\0'; /* * Shift the string so that it starts at str so that if it's dynamically * allocated, we can still free it on the returned pointer. Note the reuse * of endp to mean the front of the string buffer now. */ endp = str; if( frontp != str ) { while( *frontp ) { *endp++ = *frontp++; } *endp = '\0'; } return str; } int nwipe_get_device_bus_type_and_serialno( char* device, nwipe_device_t* bus, int* is_ssd, char* serialnumber ) { /* The caller provides a string that contains the device, i.e. /dev/sdc, also a pointer * to an integer (bus type), another pointer to an integer (is_ssd), and finally a 21 byte * character string which this function populates with the serial number (20 characters + null terminator). * * The function populates the bus integer and serial number strings for the given device. * Results for bus would typically be ATA or USB see nwipe_device_t in context.h * * Return Values: * 0 = Success * 1 = popen failed to create stream for readlink * 2 = readlink exit code not 0, see nwipe logs * 3 = popen failed to create stream for smartctl * 4 = smartctl command not found, install smartmontools * 5 = smartctl detected unsupported USB to IDE/SATA adapter * 6 = All other errors ! * */ FILE* fp; int r; // A result buffer. int idx_src; int idx_dest; int device_len; int set_return_value; int exit_status; int idx; int idx2; char readlink_command[] = "readlink /sys/block/%s"; char readlink_command2[] = "/usr/bin/readlink /sys/block/%s"; char readlink_command3[] = "/sbin/readlink /sys/block/%s"; char smartctl_command[] = "smartctl -i %s"; char smartctl_command2[] = "/sbin/smartctl -i %s"; char smartctl_command3[] = "/usr/bin/smartctl -i %s"; char device_shortform[50]; char result[512]; char final_cmd_readlink[sizeof( readlink_command ) + sizeof( device_shortform )]; char final_cmd_smartctl[sizeof( smartctl_command ) + 256]; char* pResult; char smartctl_labels_to_anonymize[][18] = { "serial number:", "lu wwn device id:", "logical unit id:", "" /* Don't remove this empty string !, important */ }; /* Initialise return value */ set_return_value = 0; *bus = 0; /* Scan device name and if device is for instance /dev/sdx then convert to sdx * If already sdx then just copy. */ idx_dest = 0; device_shortform[idx_dest] = 0; device_len = strlen( device ); idx_src = device_len; while( idx_src >= 0 ) { if( device[idx_src] == '/' || idx_src == 0 ) { idx_src++; /* Now scan forwards copying the short form device i.e sdc */ while( idx_src < device_len ) { device_shortform[idx_dest++] = device[idx_src++]; } break; } else { idx_src--; } } device_shortform[idx_dest] = 0; final_cmd_readlink[0] = 0; /* Determine whether we can access readlink, required if the PATH environment is not setup ! (Debian sid 'su' as * opposed to 'su -' */ if( system( "which readlink > /dev/null 2>&1" ) ) { if( system( "which /sbin/readlink > /dev/null 2>&1" ) ) { if( system( "which /usr/bin/readlink > /dev/null 2>&1" ) ) { nwipe_log( NWIPE_LOG_WARNING, "Command not found. Install readlink !" ); set_return_value = 2; /* Return immediately if --nousb specified. Readlink is a requirement for this option. */ if( nwipe_options.nousb ) { return set_return_value; } } else { sprintf( final_cmd_readlink, readlink_command3, device_shortform ); } } else { sprintf( final_cmd_readlink, readlink_command2, device_shortform ); } } else { sprintf( final_cmd_readlink, readlink_command, device_shortform ); } if( final_cmd_readlink[0] != 0 ) { fp = popen( final_cmd_readlink, "r" ); if( fp == NULL ) { nwipe_log( NWIPE_LOG_WARNING, "nwipe_get_device_bus_type_and_serialno: Failed to create stream to %s", readlink_command ); set_return_value = 1; } if( fp != NULL ) { /* Read the output a line at a time - output it. */ if( fgets( result, sizeof( result ) - 1, fp ) != NULL ) { if( nwipe_options.verbose ) { strip_CR_LF( result ); nwipe_log( NWIPE_LOG_DEBUG, "Readlink: %s", result ); } /* Scan the readlink results for bus types, i.e. USB or ATA * Example: readlink * /sys/block/sdd../devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.3/2-1.3:1.0/host6/target6:0:0/6:0:0:0/block/sdd */ if( strstr( result, "/usb" ) != 0 ) { *bus = NWIPE_DEVICE_USB; } else { if( strstr( result, "/ata" ) != 0 ) { *bus = NWIPE_DEVICE_ATA; } else { if( strstr( result, "/nvme/" ) != 0 ) { *bus = NWIPE_DEVICE_NVME; } else { if( strstr( result, "/virtual/" ) != 0 ) { *bus = NWIPE_DEVICE_VIRT; } else { if( strstr( result, "/mmcblk" ) != 0 ) { *bus = NWIPE_DEVICE_MMC; } } } } } } /* close */ r = pclose( fp ); if( r > 0 ) { exit_status = WEXITSTATUS( r ); if( nwipe_options.verbose ) { nwipe_log( NWIPE_LOG_WARNING, "nwipe_get_device_bus_type_and_serialno(): readlink failed, \"%s\" exit status = %u", final_cmd_readlink, exit_status ); } if( exit_status == 127 ) { nwipe_log( NWIPE_LOG_WARNING, "Command not found. Install Readlink recommended !" ); set_return_value = 2; if( nwipe_options.nousb ) { return set_return_value; } } } } } /* * Retrieve smartmontools drive information if USB bridge supports it, so we can retrieve the serial number of the * drive that's on the other side of the USB bridge.. */ final_cmd_smartctl[0] = 0; /* Determine whether we can access smartctl, required if the PATH environment is not setup ! (Debian sid 'su' as * opposed to 'su -' */ if( system( "which smartctl > /dev/null 2>&1" ) ) { if( system( "which /sbin/smartctl > /dev/null 2>&1" ) ) { if( system( "which /usr/bin/smartctl > /dev/null 2>&1" ) ) { nwipe_log( NWIPE_LOG_WARNING, "Command not found. Install smartmontools !" ); } else { sprintf( final_cmd_smartctl, smartctl_command3, device ); } } else { sprintf( final_cmd_smartctl, smartctl_command2, device ); } } else { sprintf( final_cmd_smartctl, smartctl_command, device ); } if( final_cmd_smartctl[0] != 0 ) { fp = popen( final_cmd_smartctl, "r" ); if( fp == NULL ) { nwipe_log( NWIPE_LOG_WARNING, "nwipe_get_device_bus_type_and_serialno(): Failed to create stream to %s", smartctl_command ); set_return_value = 3; } else { /* Read the output a line at a time - output it. */ while( fgets( result, sizeof( result ) - 1, fp ) != NULL ) { /* Convert the label, i.e everything before the ':' to lower case, it's required to * convert to lower case as smartctl seems to use inconsistent case when labeling * for serial number, i.e mostly it produces labels "Serial Number:" but occasionally * it produces a label "Serial number:" */ idx = 0; while( result[idx] != 0 && result[idx] != ':' ) { /* If upper case alpha character, change to lower case */ if( result[idx] >= 'A' && result[idx] <= 'Z' ) { result[idx] = tolower( result[idx] ); } idx++; } if( nwipe_options.verbose && result[0] != 0x0A ) { strip_CR_LF( result ); /* Remove serial number if -q option specified */ if( nwipe_options.quiet ) { /* initialise index into string array */ idx2 = 0; while( smartctl_labels_to_anonymize[idx2][0] != 0 ) { if( ( pResult = strstr( result, &smartctl_labels_to_anonymize[idx2][0] ) ) != 0 ) { /* set index to character after end of label string */ idx = strlen( &smartctl_labels_to_anonymize[idx2][0] ); /* Ignore spaces, overwrite other characters */ while( *( pResult + idx ) != 0x0A && *( pResult + idx ) != 0x0D && *( pResult + idx ) != 0 && idx <= sizeof( result ) - 1 ) { if( *( pResult + idx ) == ' ' ) { idx++; continue; } else { /* ignore if the serial number has been written over with '?' */ if( *( pResult + idx ) != '?' ) { *( pResult + idx ) = 'X'; } idx++; } } } idx2++; } } nwipe_log( NWIPE_LOG_INFO, "smartctl: %s", result ); } if( strstr( result, "serial number:" ) != 0 ) { /* strip any leading or trailing spaces and left justify, +15 is the length of "Serial Number:" */ trim( &result[15] ); strncpy( serialnumber, &result[15], NWIPE_SERIALNUMBER_LENGTH ); serialnumber[NWIPE_SERIALNUMBER_LENGTH] = 0; } if( *bus == 0 ) { if( strstr( result, "transport protocol:" ) != 0 ) { /* strip any leading or trailing spaces and left justify, +4 is the length of "bus type:" */ trim( &result[19] ); for( idx = 19; result[idx]; idx++ ) { result[idx] = tolower( result[idx] ); } if( strncmp( &result[19], "sas", 3 ) == 0 ) { *bus = NWIPE_DEVICE_SAS; } } if( strstr( result, "sata version is:" ) != 0 ) { /* strip any leading or trailing spaces and left justify, +4 is the length of "bus type:" */ trim( &result[16] ); for( idx = 16; result[idx]; idx++ ) { result[idx] = tolower( result[idx] ); } if( strncmp( &result[16], "sata", 4 ) == 0 ) { *bus = NWIPE_DEVICE_ATA; } } } if( strstr( result, "rotation rate:" ) != 0 ) { /* strip any leading or trailing spaces and left justify, +15 is the length of "Rotation Rate:" */ trim( &result[15] ); for( idx = 15; result[idx]; idx++ ) { result[idx] = tolower( result[idx] ); } if( strncmp( &result[15], "solid state device", 19 ) == 0 ) { *is_ssd = 1; } } } /* close */ r = pclose( fp ); if( r > 0 ) { exit_status = WEXITSTATUS( r ); if( nwipe_options.verbose && exit_status != 1 ) { nwipe_log( NWIPE_LOG_WARNING, "nwipe_get_device_bus_type_and_serialno(): smartctl failed, \"%s\" exit status = %u", final_cmd_smartctl, exit_status ); } set_return_value = 6; if( exit_status == 127 ) { nwipe_log( NWIPE_LOG_WARNING, "Command not found. Install Smartctl recommended !" ); set_return_value = 4; } if( exit_status == 1 ) { nwipe_log( NWIPE_LOG_WARNING, "Smartctl is unable to provide smart data for %s", device ); if( *bus == NWIPE_DEVICE_USB || *bus == NWIPE_DEVICE_MMC ) { strcpy( serialnumber, "(S/N: unknown)" ); set_return_value = 5; } } } } } return set_return_value; } void remove_ATA_prefix( char* str ) { /* Remove "ATA" prefix if present in the model no. string, left justifing string */ int idx_pre = 4; int idx_post = 0; if( !strncmp( str, "ATA ", 4 ) ) { while( str[idx_pre] != 0 ) { str[idx_post++] = str[idx_pre++]; } str[idx_post] = 0; } }