summaryrefslogtreecommitdiffstats
path: root/src/hpa_dco.c
blob: bbd3d1cdbccf216e807617dcf511a133c95f7d85 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
/*
 *  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

    /* Zero the data block prior to use */
    memset( buffer, 0, LBA_SIZE );

    /* 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;
}