summaryrefslogtreecommitdiffstats
path: root/src/hpa_dco.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/hpa_dco.c
parentInitial commit. (diff)
downloadnwipe-upstream/0.36.tar.xz
nwipe-upstream/0.36.zip
Adding upstream version 0.36.upstream/0.36
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/hpa_dco.c')
-rw-r--r--src/hpa_dco.c858
1 files changed, 858 insertions, 0 deletions
diff --git a/src/hpa_dco.c b/src/hpa_dco.c
new file mode 100644
index 0000000..160d454
--- /dev/null
+++ b/src/hpa_dco.c
@@ -0,0 +1,858 @@
+/*
+ * hpa_dco.c: functions that handle the host protected area (HPA) and
+ * device configuration overlay (DCO)
+ *
+ * Copyright PartialVolume <https://github.com/PartialVolume>.
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdarg.h>
+#include <string.h>
+#include <scsi/sg.h>
+#include <scsi/scsi_ioctl.h>
+#include "nwipe.h"
+#include "context.h"
+#include "version.h"
+#include "method.h"
+#include "logging.h"
+#include "options.h"
+#include "hpa_dco.h"
+#include "miscellaneous.h"
+
+/* This function makes use of both the hdparm program to determine HPA/DCO status and we also access
+ * the device configuration overlay identify data structure via the sg driver with ioctl calls.
+ * I would prefer to write these functions without any reliance on hdparm however for the time being
+ * we will utilize both methods. However, I don't like doing it like this as a change in formatted
+ * output of hdparm could potentially break HPA/DCO detection requiring a fix. Time permitting I may
+ * come back to this and fully implement it without any reliance on hdparm.
+ */
+
+int hpa_dco_status( nwipe_context_t* ptr )
+{
+ nwipe_context_t* c;
+ c = ptr;
+
+ int r; // A result buffer.
+ int set_return_value;
+ int exit_status;
+ int hpa_line_found;
+ int dco_line_found;
+
+ FILE* fp;
+ char path_hdparm_cmd1_get_hpa[] = "hdparm --verbose -N";
+ char path_hdparm_cmd2_get_hpa[] = "/sbin/hdparm --verbose -N";
+ char path_hdparm_cmd3_get_hpa[] = "/usr/bin/hdparm --verbose -N";
+
+ char path_hdparm_cmd4_get_dco[] = "hdparm --verbose --dco-identify";
+ char path_hdparm_cmd5_get_dco[] = "/sbin/hdparm --verbose --dco-identify";
+ char path_hdparm_cmd6_get_dco[] = "/usr/bin/hdparm --verbose --dco-identify";
+
+ char pipe_std_err[] = "2>&1";
+
+ char result[512];
+
+ u64 nwipe_dco_real_max_sectors;
+
+ char* p;
+
+ /* Use the longest of the 'path_hdparm_cmd.....' strings above to
+ *determine size in the strings below
+ */
+ char hdparm_cmd_get_hpa[sizeof( path_hdparm_cmd3_get_hpa ) + sizeof( c->device_name ) + sizeof( pipe_std_err )];
+ char hdparm_cmd_get_dco[sizeof( path_hdparm_cmd6_get_dco ) + sizeof( c->device_name ) + sizeof( pipe_std_err )];
+
+ /* Initialise return value */
+ set_return_value = 0;
+
+ /* Construct the command including path to the binary if required, I do it like this to cope
+ * with distros that don't setup their paths in a standard way or maybe don't even define a
+ * path. By doing this we avoid the 'No such file or directory' message you would otherwise
+ * get on some distros. -> debian SID
+ */
+
+ 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 );
+ }
+ else
+ {
+ snprintf( hdparm_cmd_get_hpa,
+ sizeof( hdparm_cmd_get_hpa ),
+ "%s %s %s\n",
+ path_hdparm_cmd3_get_hpa,
+ c->device_name,
+ pipe_std_err );
+ snprintf( hdparm_cmd_get_dco,
+ sizeof( hdparm_cmd_get_dco ),
+ "%s %s %s\n",
+ path_hdparm_cmd6_get_dco,
+ c->device_name,
+ pipe_std_err );
+ }
+ }
+ else
+ {
+ snprintf( hdparm_cmd_get_hpa,
+ sizeof( hdparm_cmd_get_hpa ),
+ "%s %s %s\n",
+ path_hdparm_cmd2_get_hpa,
+ c->device_name,
+ pipe_std_err );
+ snprintf( hdparm_cmd_get_dco,
+ sizeof( hdparm_cmd_get_dco ),
+ "%s %s %s\n",
+ path_hdparm_cmd5_get_dco,
+ c->device_name,
+ pipe_std_err );
+ }
+ }
+ else
+ {
+ snprintf( hdparm_cmd_get_hpa,
+ sizeof( hdparm_cmd_get_hpa ),
+ "%s %s %s\n",
+ path_hdparm_cmd1_get_hpa,
+ c->device_name,
+ pipe_std_err );
+ snprintf( hdparm_cmd_get_dco,
+ sizeof( hdparm_cmd_get_dco ),
+ "%s %s %s\n",
+ path_hdparm_cmd4_get_dco,
+ c->device_name,
+ pipe_std_err );
+ }
+
+ /* Initialise the results buffer, so we don't some how inadvertently process a past result */
+ memset( result, 0, sizeof( result ) );
+
+ if( hdparm_cmd_get_hpa[0] != 0 )
+ {
+
+ fp = popen( hdparm_cmd_get_hpa, "r" );
+
+ if( fp == NULL )
+ {
+ nwipe_log( NWIPE_LOG_WARNING, "hpa_dco_status: Failed to create stream to %s", hdparm_cmd_get_hpa );
+
+ set_return_value = 1;
+ }
+
+ if( fp != NULL )
+ {
+ hpa_line_found = 0; //* init */
+
+ /* Read the output a line at a time - output it. */
+ while( fgets( result, sizeof( result ) - 1, fp ) != NULL )
+ {
+ if( nwipe_options.verbose )
+ {
+ nwipe_log( NWIPE_LOG_DEBUG, "%s \n%s", hdparm_cmd_get_hpa, result );
+ }
+
+ /* Change the output of hdparm to lower case and search using lower case strings, to try
+ * to avoid minor changes in case in hdparm's output from breaking HPA/DCO detection
+ */
+ strlower( result ); // convert the result to lower case
+
+ /* Scan the hdparm results for HPA is disabled
+ */
+ if( strstr( result, "sg_io: bad/missing sense data" ) != 0 )
+ {
+ c->HPA_status = HPA_UNKNOWN;
+ nwipe_log( NWIPE_LOG_ERROR, "SG_IO bad/missing sense data %s", hdparm_cmd_get_hpa );
+ break;
+ }
+ else
+ {
+ if( strstr( result, "hpa is disabled" ) != 0 )
+ {
+ c->HPA_status = HPA_DISABLED;
+
+ nwipe_log( NWIPE_LOG_DEBUG,
+ "hdparm says the host protected area is disabled on %s but this information may or "
+ "may not be correct, as occurs when you get a SG_IO error and 0/1 sectors and it "
+ "says HPA is enabled. Further checks are conducted below..",
+ c->device_name );
+ hpa_line_found = 1;
+ break;
+ }
+ else
+ {
+ if( strstr( result, "hpa is enabled" ) != 0 )
+ {
+ c->HPA_status = HPA_ENABLED;
+ nwipe_log( NWIPE_LOG_DEBUG,
+ "hdparm says the host protected area is enabled on %s but this information may "
+ "or may not be correct, as occurs when you get a SG_IO error and 0/1 sectors "
+ "and it says HPA is enabled. Further checks are conducted below..",
+ c->device_name );
+ hpa_line_found = 1;
+ break;
+ }
+ else
+ {
+ if( strstr( result, "accessible max address disabled" ) != 0 )
+ {
+ c->HPA_status = HPA_DISABLED;
+ nwipe_log( NWIPE_LOG_DEBUG,
+ "hdparm says the accessible max address disabled on %s"
+ "this means that there are no hidden sectors, "
+ "",
+ c->device_name );
+ hpa_line_found = 1;
+ break;
+ }
+ else
+ {
+ if( strstr( result, "accessible max address enabled" ) != 0 )
+ {
+ c->HPA_status = HPA_ENABLED;
+ nwipe_log( NWIPE_LOG_DEBUG,
+ "hdparm says the accessible max address enabled on %s"
+ "this means that there are hidden sectors",
+ c->device_name );
+ hpa_line_found = 1;
+ break;
+ }
+ else
+ {
+ if( strstr( result, "invalid" ) != 0 )
+ {
+ nwipe_log(
+ NWIPE_LOG_WARNING,
+ "hdparm reports invalid output, sector information may be invalid, buggy "
+ "drive firmware on %s?",
+ c->device_name );
+ // We'll assume the HPA values are in the string as we may be able to extract
+ // something meaningful
+ hpa_line_found = 1;
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /* if the HPA line was found then process the line,
+ * extracting the 'hpa set' and 'hpa real' values.
+ */
+ if( hpa_line_found == 1 )
+ {
+ /* Extract the 'HPA set' value, the first value in the line and convert
+ * to binary and save in context */
+
+ nwipe_log( NWIPE_LOG_INFO, "HPA: %s on %s", result, c->device_name );
+
+ c->HPA_reported_set = str_ascii_number_to_ll( result );
+
+ /* Check whether the number was too large or no number found & log */
+ if( c->HPA_reported_set == -1 )
+ {
+ nwipe_log( NWIPE_LOG_INFO, "HPA_set_value: HPA set value too large on %s", c->device_name );
+ c->HPA_reported_set = 0;
+ }
+ else
+ {
+ if( c->HPA_reported_set == -2 )
+ {
+ nwipe_log( NWIPE_LOG_INFO, "HPA_set_value: No HPA set value found %s", c->device_name );
+ c->HPA_reported_set = 0;
+ }
+ }
+
+ /* Extract the 'HPA real' value, the second value in the line and convert
+ * to binary and save in context, this is a little more difficult as sometimes
+ * a odd value is returned so instead of nnnnn/nnnnn you get nnnnnn/1(nnnnnn).
+ * So first we scan for a open bracket '(' then if there is no '(' we then start the
+ * search immediately after the '/'.
+ */
+ if( ( p = strstr( result, "(" ) ) )
+ {
+ c->HPA_reported_real = str_ascii_number_to_ll( p + 1 );
+ }
+ else
+ {
+ if( ( p = strstr( result, "/" ) ) )
+ {
+ c->HPA_reported_real = str_ascii_number_to_ll( p + 1 );
+ }
+ }
+
+ /* Check whether the number was too large or no number found & log */
+ if( c->HPA_reported_real == -1 )
+ {
+ nwipe_log( NWIPE_LOG_INFO, "HPA_set_value: HPA real value too large on %s", c->device_name );
+ c->HPA_reported_real = 0;
+ }
+ else
+ {
+ if( c->HPA_reported_real == -2 )
+ {
+ nwipe_log( NWIPE_LOG_INFO, "HPA_set_value: No HPA real value found %s", c->device_name );
+ c->HPA_reported_real = 0;
+ }
+ }
+
+ nwipe_log( NWIPE_LOG_INFO,
+ "HPA values %lli / %lli on %s",
+ c->HPA_reported_set,
+ c->HPA_reported_real,
+ c->device_name );
+ }
+ else
+ {
+ c->HPA_status = HPA_UNKNOWN;
+ nwipe_log( NWIPE_LOG_WARNING,
+ "[UNKNOWN] We can't find the HPA line, has hdparm ouput unknown/changed? %s",
+ c->device_name );
+ }
+
+ /* close */
+ r = pclose( fp );
+ if( r > 0 )
+ {
+ exit_status = WEXITSTATUS( r );
+ if( nwipe_options.verbose )
+ {
+ nwipe_log( NWIPE_LOG_WARNING,
+ "hpa_dco_status(): hdparm failed, \"%s\" exit status = %u",
+ hdparm_cmd_get_hpa,
+ exit_status );
+ }
+
+ if( exit_status == 127 )
+ {
+ nwipe_log( NWIPE_LOG_WARNING, "Command not found. Installing hdparm is mandatory !" );
+ set_return_value = 2;
+ if( nwipe_options.nousb )
+ {
+ return set_return_value;
+ }
+ }
+ }
+ }
+ }
+
+ /* Initialise the results buffer again, so we don't
+ * some how inadvertently process a past result */
+ memset( result, 0, sizeof( result ) );
+
+ /* -----------------------------------------------
+ * Run the dco identify command and determine the
+ * real max sectors, store it in the drive context
+ * for comparison against the hpa reported drive
+ * size values.
+ */
+
+ dco_line_found = 0;
+
+ if( hdparm_cmd_get_dco[0] != 0 )
+ {
+
+ fp = popen( hdparm_cmd_get_dco, "r" );
+
+ if( fp == NULL )
+ {
+ nwipe_log( NWIPE_LOG_WARNING, "hpa_dco_status: Failed to create stream to %s", hdparm_cmd_get_dco );
+
+ set_return_value = 1;
+ }
+
+ if( fp != NULL )
+ {
+ /* Read the output a line at a time - output it. */
+ while( fgets( result, sizeof( result ) - 1, fp ) != NULL )
+ {
+ /* Change the output of hdparm to lower case and search using lower case strings, to try
+ * to avoid minor changes in case in hdparm's output from breaking HPA/DCO detection */
+ strlower( result );
+
+ if( nwipe_options.verbose )
+ {
+ nwipe_log( NWIPE_LOG_DEBUG, "%s \n%s", hdparm_cmd_get_dco, result );
+ }
+
+ if( strstr( result, "real max sectors" ) != 0 )
+ {
+ /* extract the real max sectors, convert to binary and store in drive context */
+ dco_line_found = 1;
+ break;
+ }
+ }
+ /* DCO line found, now process it */
+ if( dco_line_found == 1 )
+ {
+ c->DCO_reported_real_max_sectors = str_ascii_number_to_ll( result );
+ nwipe_log( NWIPE_LOG_INFO,
+ "hdparm:DCO Real max sectors reported as %lli on %s",
+ c->DCO_reported_real_max_sectors,
+ c->device_name );
+
+ /* Validate the real max sectors to detect extreme or impossible
+ * values, so the size must be greater than zero but less than
+ * 200TB (429496729600 sectors). As its 2023 and the largest drive
+ * available is 20TB I wonder if somebody in the future will be looking
+ * at this and thinking, yep we need to increase that value... and I'm
+ * wondering what year that will be. This validation is necessary all
+ * because of a bug in hdparm v9.60 (and maybe other versions) which
+ * produced wildly inaccurate values, often negative.
+ */
+ if( c->DCO_reported_real_max_sectors > 0 && c->DCO_reported_real_max_sectors < 429496729600 )
+ {
+ nwipe_log( NWIPE_LOG_INFO,
+ "NWipe: DCO Real max sectors reported as %lli on %s",
+ c->DCO_reported_real_max_sectors,
+ c->device_name );
+ }
+ else
+ {
+ /* Call nwipe's own low level function to retrieve the drive configuration
+ * overlay and retrieve the real max sectors. We may remove reliance on hdparm
+ * if nwipes own low level drive access code works well.
+ */
+ c->DCO_reported_real_max_sectors = nwipe_read_dco_real_max_sectors( c->device_name );
+
+ /* Check our real max sectors function is returning sensible data too */
+ if( c->DCO_reported_real_max_sectors > 0 && c->DCO_reported_real_max_sectors < 429496729600 )
+ {
+ nwipe_log( NWIPE_LOG_INFO,
+ "NWipe: DCO Real max sectors reported as %lli on %s",
+ c->DCO_reported_real_max_sectors,
+ c->device_name );
+ }
+ else
+ {
+ c->DCO_reported_real_max_sectors = 0;
+ nwipe_log( NWIPE_LOG_INFO, "DCO Real max sectors not found" );
+ }
+ }
+ }
+ else
+ {
+ c->DCO_reported_real_max_sectors = 0;
+ nwipe_log( NWIPE_LOG_INFO, "DCO Real max sectors not found" );
+ }
+
+ nwipe_log(
+ NWIPE_LOG_INFO,
+ "libata: apparent max sectors reported as %lli with sector size as %i/%i (logical/physical) on %s",
+ c->device_size_in_sectors,
+ c->device_sector_size, // logical
+ c->device_phys_sector_size, // physical
+ c->device_name );
+
+ /* close */
+ r = pclose( fp );
+ if( r > 0 )
+ {
+ exit_status = WEXITSTATUS( r );
+ if( nwipe_options.verbose )
+ {
+ nwipe_log( NWIPE_LOG_WARNING,
+ "hpa_dco_status(): hdparm failed, \"%s\" exit status = %u",
+ hdparm_cmd_get_dco,
+ exit_status );
+ }
+
+ if( exit_status == 127 )
+ {
+ nwipe_log( NWIPE_LOG_WARNING, "Command not found. Installing hdparm is mandatory !" );
+ set_return_value = 2;
+ if( nwipe_options.nousb )
+ {
+ return set_return_value;
+ }
+ }
+ }
+ }
+ }
+
+ /* Compare the results of hdparm -N (HPA set / HPA real)
+ * and hdparm --dco-identidy (real max sectors). All three
+ * values may be different or perhaps 'HPA set' and 'HPA real' are
+ * different and 'HPA real' matches 'real max sectors'.
+ *
+ * A perfect HPA disabled result would be where all three
+ * values are the same. It can then be considered that the
+ * HPA is disabled.
+ *
+ * If 'HPA set' and 'HPA real' are different then it
+ * can be considered that HPA is enabled, assuming 'HPA set'
+ * and 'HPA real' are not 0/1 which occurs when a SG_IO error
+ * occurs. That also is checked for as it often indicates a
+ * poor USB device that does not have ATA pass through support.
+ *
+ * However we also need to consider that more recent drives
+ * no longer support HPA/DCO such as the Seagate ST10000NM0016,
+ * ST4000NM0033 and ST1000DM003. If you try to issue those drives
+ * with the ATA command code 0xB1 (device configuration overlay)
+ * you will get a generic illegal request in the returned sense data.
+ *
+ * One other thing to note, we use HPA enabled/disabled to mean
+ * hidden area detected or not detected, this could be caused by
+ * either the dco-setmax being issued or Np, either way an area
+ * of the disc can be hidden. From the user interface we just call
+ * it a HPA/DCO hidden area detected (or not) which is more
+ * meaningful than just saying HDA enabled or disabled and a user
+ * not familiar with the term HPA or DCO not understanding why a
+ * HDA being detected could be significant.
+ */
+
+ /* Determine, based on the values of 'HPA set', 'HPA real and
+ * 'real max sectors' whether we set the HPA flag as HPA_DISABLED,
+ * HPA_ENABLED, HPA_UNKNOWN or HPA_NOT_APPLICABLE. The HPA flag
+ * will be displayed in the GUI and on the certificate and is
+ * used to determine whether to reset the HPA.
+ *
+ */
+
+ /* WARNING temp assignments WARNING
+ * s=28,r=28,rm=0
+ *
+ */
+#if 0
+ c->HPA_reported_set = 10;
+ c->HPA_reported_real = 28;
+ c->DCO_reported_real_max_sectors = 0;
+
+ c->HPA_reported_set = 28;
+ c->HPA_reported_real = 28;
+ c->DCO_reported_real_max_sectors = 0;
+
+ c->HPA_reported_set = 1000;
+ c->HPA_reported_real = 2048;
+ c->DCO_reported_real_max_sectors = 2048;
+#endif
+
+ /* If any of the HPA or DCO values are larger than the apparent size then HPA is enabled. */
+ if( /*c->HPA_reported_set > c->device_size_in_sectors || */ c->HPA_reported_real > c->device_size_in_512byte_sectors
+ || c->DCO_reported_real_max_sectors > c->device_size_in_512byte_sectors )
+ {
+ c->HPA_status = HPA_ENABLED;
+ nwipe_log( NWIPE_LOG_WARNING, " *********************************" );
+ nwipe_log( NWIPE_LOG_WARNING, " *** HIDDEN SECTORS DETECTED ! *** on %s", c->device_name );
+ nwipe_log( NWIPE_LOG_WARNING, " *********************************" );
+ }
+ else
+ {
+ /* This occurs when a SG_IO error occurs with USB devices that don't support ATA pass
+ * through */
+ if( c->HPA_reported_set == 0 && c->HPA_reported_real == 0 && c->DCO_reported_real_max_sectors <= 1 )
+ {
+ c->HPA_status = HPA_UNKNOWN;
+ if( c->device_bus == NWIPE_DEVICE_USB )
+ {
+ nwipe_log( NWIPE_LOG_WARNING,
+ "HIDDEN SECTORS INDETERMINATE! on %s, Some USB adapters & memory sticks don't support "
+ "ATA pass through",
+ c->device_name );
+ }
+ }
+ else
+ {
+ c->HPA_status = HPA_DISABLED;
+ nwipe_log( NWIPE_LOG_INFO, "No hidden sectors on %s", c->device_name );
+ }
+ }
+
+ c->DCO_reported_real_max_size = c->DCO_reported_real_max_sectors * c->device_sector_size;
+
+ nwipe_dco_real_max_sectors = nwipe_read_dco_real_max_sectors( c->device_name );
+
+ /* Analyse all the variations to produce the final real max bytes which takes into
+ * account drives that don't support DCO or HPA. This result is used in the PDF
+ * creation functions.
+ */
+
+ if( c->device_type == NWIPE_DEVICE_NVME || c->device_type == NWIPE_DEVICE_VIRT
+ || c->HPA_status == HPA_NOT_APPLICABLE )
+ {
+ c->Calculated_real_max_size_in_bytes = c->device_size;
+ }
+ else
+ {
+ /* If the DCO is reporting a real max sectors > the apparent size
+ * as reported by libata then that is what we will use as the real disc size
+ */
+ if( c->DCO_reported_real_max_size > c->device_size_in_512byte_sectors )
+ {
+ c->Calculated_real_max_size_in_bytes = c->DCO_reported_real_max_sectors * c->device_sector_size;
+ }
+ else
+ {
+ /* If HPA is enabled and DCO real max sectors did not exist, then we have to assume - c->HPA_reported_real
+ * is the value we need, however if that is zero, then c->HPA_reported_set and if that is zero then
+ * c->device_size as reported by libata
+ */
+ if( c->HPA_reported_real > c->device_size_in_512byte_sectors )
+ {
+ c->Calculated_real_max_size_in_bytes = c->HPA_reported_real * c->device_sector_size;
+ }
+ else
+ {
+ if( c->HPA_reported_set > c->device_size_in_512byte_sectors )
+ {
+ c->Calculated_real_max_size_in_bytes = c->HPA_reported_set * c->device_sector_size;
+ }
+ else
+ {
+ c->Calculated_real_max_size_in_bytes = c->device_size;
+ }
+ }
+ }
+ }
+
+ /* -------------------------------------------------------------------
+ * create two variables for later use by the PDF creation function
+ * based on real max sectors and calculated real max size in bytes.
+ *
+ * DCO_reported_real_max_size = real max sectors * sector size = bytes
+ * DCO_reported_real_max_size_text = human readable string, i.e 1TB etc.
+ */
+
+ Determine_C_B_nomenclature(
+ c->DCO_reported_real_max_size, c->DCO_reported_real_max_size_text, NWIPE_DEVICE_SIZE_TXT_LENGTH );
+ Determine_C_B_nomenclature(
+ c->Calculated_real_max_size_in_bytes, c->Calculated_real_max_size_in_bytes_text, NWIPE_DEVICE_SIZE_TXT_LENGTH );
+
+ /* ----------------------------------------------------------------------------------
+ * Determine the size of the HPA if it's enabled and store the results in the context
+ */
+
+ if( c->HPA_status == HPA_ENABLED )
+ {
+ if( c->Calculated_real_max_size_in_bytes != c->device_size )
+ {
+ c->HPA_sectors =
+ ( (u64) ( c->Calculated_real_max_size_in_bytes - c->device_size ) / c->device_sector_size );
+ }
+ else
+ {
+ c->HPA_sectors = 0;
+ }
+
+ /* Convert the size to a human readable format and save in context */
+ Determine_C_B_nomenclature( c->HPA_sectors, c->HPA_size_text, NWIPE_DEVICE_SIZE_TXT_LENGTH );
+ }
+ else
+ {
+ /* HPA not enabled so initialise these values */
+ c->HPA_sectors = 0;
+ c->HPA_size_text[0] = 0;
+ }
+
+ nwipe_log( NWIPE_LOG_DEBUG,
+ "c->Calculated_real_max_size_in_bytes=%lli, c->device_size=%lli, c->device_sector_size=%lli, "
+ "c->DCO_reported_real_max_size=%lli, c->DCO_reported_real_max_sectors=%lli, c->HPA_sectors=%lli, "
+ "c->HPA_reported_set=%lli, c->HPA_reported_real=%lli, c->device_type=%i, "
+ "libata:c->device_size_in_sectors=%lli ",
+ "libata:c->device_size_in_512byte_sectors=%lli ",
+ c->Calculated_real_max_size_in_bytes,
+ c->device_size,
+ c->device_sector_size,
+ c->DCO_reported_real_max_size,
+ c->DCO_reported_real_max_sectors,
+ c->HPA_sectors,
+ c->HPA_reported_set,
+ c->HPA_reported_real,
+ c->device_type,
+ c->device_size_in_sectors,
+ c->device_size_in_512byte_sectors );
+
+ return set_return_value;
+}
+
+u64 nwipe_read_dco_real_max_sectors( char* device )
+{
+ /* This function sends a device configuration overlay identify command 0xB1 (dco-identify)
+ * to the drive and extracts the real max sectors. The value is incremented by 1 and
+ * then returned. We rely upon this function to determine real max sectors as there
+ * is a bug in hdparm 9.60, including possibly earlier or later versions but which is
+ * fixed in 9.65, that returns a incorrect (negative) value
+ * for some drives that are possibly over a certain size.
+ */
+
+ /* TODO Add checks in case of failure, especially with recent drives that may not
+ * support drive configuration overlay commands.
+ */
+
+#define LBA_SIZE 512
+#define CMD_LEN 16
+#define BLOCK_MAX 65535
+#define LBA_MAX ( 1 << 30 )
+#define SENSE_BUFFER_SIZE 32
+
+ u64 nwipe_real_max_sectors;
+
+ /* This command issues command 0xb1 (dco-identify) 15th byte */
+ unsigned char cmd_blk[CMD_LEN] = { 0x85, 0x08, 0x0e, 0x00, 0xc2, 0, 0x01, 0, 0, 0, 0, 0, 0, 0x40, 0xb1, 0 };
+
+ sg_io_hdr_t io_hdr;
+ unsigned char buffer[LBA_SIZE]; // Received data block
+ unsigned char sense_buffer[SENSE_BUFFER_SIZE]; // Sense data
+
+ /* three characters represent one byte of sense data, i.e
+ * two characters and a space "01 AE 67"
+ */
+ char sense_buffer_hex[( SENSE_BUFFER_SIZE * 3 ) + 1];
+
+ int i, i2; // index
+ int fd; // file descripter
+
+ if( ( fd = open( device, O_RDWR ) ) < 0 )
+ {
+ /* Unable to open device */
+ return -1;
+ }
+
+ /******************************************
+ * Initialise the sg header for reading the
+ * device configuration overlay identify data
+ */
+ memset( &io_hdr, 0, sizeof( sg_io_hdr_t ) );
+ io_hdr.interface_id = 'S';
+ io_hdr.cmd_len = sizeof( cmd_blk );
+ io_hdr.mx_sb_len = sizeof( sense_buffer );
+ io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
+ io_hdr.dxfer_len = LBA_SIZE;
+ io_hdr.dxferp = buffer;
+ io_hdr.cmdp = cmd_blk;
+ io_hdr.sbp = sense_buffer;
+ io_hdr.timeout = 20000;
+
+ if( ioctl( fd, SG_IO, &io_hdr ) < 0 )
+ {
+ nwipe_log( NWIPE_LOG_ERROR, "IOCTL command failed retrieving DCO" );
+ i2 = 0;
+ for( i = 0, i2 = 0; i < SENSE_BUFFER_SIZE; i++, i2 += 3 )
+ {
+ /* IOCTL returned an error */
+ snprintf( &sense_buffer_hex[i2], sizeof( sense_buffer_hex ), "%02x ", sense_buffer[i] );
+ }
+ sense_buffer_hex[i2] = 0; // terminate string
+ nwipe_log( NWIPE_LOG_DEBUG, "Sense buffer from failed DCO identify cmd:%s", sense_buffer_hex );
+ return -2;
+ }
+
+ /* Close the device */
+ close( fd );
+
+ /***************************************************************
+ * Extract the real max sectors from the returned 512 byte block.
+ * Assuming the first word/byte is 0. We extract the bytes & switch
+ * the endian. Words 3-6(bytes 6-13) contain the max sector address
+ */
+ nwipe_real_max_sectors = (u64) ( (u64) buffer[13] << 56 ) | ( (u64) buffer[12] << 48 ) | ( (u64) buffer[11] << 40 )
+ | ( (u64) buffer[10] << 32 ) | ( (u64) buffer[9] << 24 ) | ( (u64) buffer[8] << 16 ) | ( (u64) buffer[7] << 8 )
+ | buffer[6];
+
+ /* Don't really understand this but hdparm adds 1 to
+ * the real max sectors too, counting zero as sector?
+ * but only increment if it's already greater than zero
+ */
+ if( nwipe_real_max_sectors > 0 )
+ {
+ nwipe_real_max_sectors++;
+ }
+
+ nwipe_log(
+ NWIPE_LOG_INFO, "func:nwipe_read_dco_real_max_sectors(), DCO real max sectors = %lli", nwipe_real_max_sectors );
+
+ return nwipe_real_max_sectors;
+}
+
+int ascii2binary_array( char* input, unsigned char* output_bin, int bin_size )
+{
+ /* Converts ascii sense data output by hdparm to binary.
+ * Scans a character string that contains hexadecimal ascii data, ignores spaces
+ * and extracts and converts the hexadecimal ascii data to binary and places in a array.
+ * Typically for dco_identify sense data the bin size will be 512 bytes but for error
+ * sense data this would be 32 bytes.
+ */
+ int idx_in; // Index into ascii input string
+ int idx_out; // Index into the binary output array
+ int byte_count; // Counts which 4 bit value we are working on
+ char upper4bits;
+ char lower4bits;
+
+ byte_count = 0;
+ idx_in = 0;
+ idx_out = 0;
+ while( input[idx_in] != 0 )
+ {
+ if( input[idx_in] >= '0' && input[idx_in] <= '9' )
+ {
+ if( byte_count == 0 )
+ {
+ upper4bits = input[idx_in] - 0x30;
+ byte_count++;
+ }
+ else
+ {
+ lower4bits = input[idx_in] - 0x30;
+ output_bin[idx_out++] = ( upper4bits << 4 ) | ( lower4bits );
+ byte_count = 0;
+
+ if( idx_out >= bin_size )
+ {
+ return 0; // output array full.
+ }
+ }
+ }
+ else
+ {
+ if( input[idx_in] >= 'a' && input[idx_in] <= 'f' )
+ {
+ if( byte_count == 0 )
+ {
+ upper4bits = input[idx_in] - 0x57;
+ byte_count++;
+ }
+ else
+ {
+ lower4bits = input[idx_in] - 0x57;
+ output_bin[idx_out++] = ( upper4bits << 4 ) | ( lower4bits );
+ byte_count = 0;
+
+ if( idx_out >= bin_size )
+ {
+ return 0; // output array full.
+ }
+ }
+ }
+ }
+ idx_in++; // next byte in the input string
+ }
+ return 0;
+}