/* $Id$ Copyright (c) 2005-2012 Ross Smith II (http://smithii.com). All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of version 2 of the GNU General Public License as published by the Free Software Foundation. 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. */ /* todo/wishlist handle bad sectors without failing scramble the MFT/FAT tables first catch ctrl-C to report where to start over Implement HAVE_CRYPTOGRAPHIC using TrueCrypt's RNG */ #include #include #include #include // time() #include // _getpid() #include #undef HAVE_CRYPTOGRAPHIC //#define DUMMY_WRITE 1 #define BYTES_PER_ELEMENT (3) #define SECTORS_PER_READ (64) #define BUFFER_SIZE_CHAR (512) #define BUFFER_SIZE_WCHAR ((BUFFER_SIZE_CHAR) / sizeof(wchar_t)) #define USE_KILOBYTE 1 // \todo convert separate wipe arrays to one array int dod_bytes[] = {0x00, 0xff, -1}; int dod_elements = sizeof(dod_bytes) / sizeof(dod_bytes[0]); // source: BCWipe-1.6-5/bcwipe/wipe.h int dod7_bytes[] = {0x35, 0xca, 0x97, 0x68, 0xac, 0x53, -1}; int dod7_elements = sizeof(dod7_bytes) / sizeof(dod7_bytes[0]); int bci_bytes[] = {0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0xaa}; int bci_elements = sizeof(bci_bytes) / sizeof(bci_bytes[0]); int doe_bytes[] = {-1, -1, 0x00}; int doe_elements = sizeof(doe_bytes) / sizeof(doe_bytes[0]); int schneier_bytes[] = {0xff, 0x00, -1, -1, -1, -1, -1}; int schneier_elements = sizeof(schneier_bytes) / sizeof(schneier_bytes[0]); // source: http://www.cs.auckland.ac.nz/~pgut001/pubs/secure_del.html int gutmann_bytes[][BYTES_PER_ELEMENT] = { {-1, -1, -1}, // 1 {-1, -1, -1}, // 2 {-1, -1, -1}, // 3 {-1, -1, -1}, // 4 {0x55, 0x55, 0x55}, // 5 {0xAA, 0xAA, 0xAA}, // 6 {0x92, 0x49, 0x24}, // 7 {0x49, 0x24, 0x92}, // 8 {0x24, 0x92, 0x49}, // 9 {0x00, 0x00, 0x00}, // 10 {0x11, 0x11, 0x11}, // 11 {0x22, 0x22, 0x22}, // 12 {0x33, 0x33, 0x33}, // 13 {0x44, 0x44, 0x44}, // 14 {0x55, 0x55, 0x55}, // 15 {0x66, 0x66, 0x66}, // 16 {0x77, 0x77, 0x77}, // 17 {0x88, 0x88, 0x88}, // 18 {0x99, 0x99, 0x99}, // 19 {0xAA, 0xAA, 0xAA}, // 20 {0xBB, 0xBB, 0xBB}, // 21 {0xCC, 0xCC, 0xCC}, // 22 {0xDD, 0xDD, 0xDD}, // 23 {0xEE, 0xEE, 0xEE}, // 24 {0xFF, 0xFF, 0xFF}, // 25 {0x92, 0x49, 0x24}, // 26 {0x49, 0x24, 0x92}, // 27 {0x24, 0x92, 0x49}, // 28 {0x6D, 0xB6, 0xDB}, // 29 {0xB6, 0xDB, 0x6D}, // 30 {0xDB, 0x6D, 0xB6}, // 31 {-1, -1, -1}, // 32 {-1, -1, -1}, // 33 {-1, -1, -1}, // 34 {-1, -1, -1} // 35 }; int gutmann_elements = sizeof(gutmann_bytes) / sizeof(gutmann_bytes[0]); // 1 2 3 4 5 6 7 // 12345678901234567890123456789012345678901234567890123456789012345678901234567890 #define HEADER " This All All All \n" \ "Pass No. of Pass Passes Passes Passes Est. %s/\n" \ " No. Passes Byte Complete Complete Elapsed Remain. Start Finish Second\n" \ "---- ------ ---- -------- -------- -------- -------- -------- -------- --------\n" // 1234 123456 0xff 100.000% 100.000% 00:00:00 00:00:00 00:00:00 00:00:00 12345.67 #define FORMAT_STRING "%4d %6d %4s %7.3f%% %7.3f%%%9s%9s %8s %8s%9.2f\r" typedef enum { EXIT_NONE, EXIT_POWEROFF, EXIT_SHUTDOWN, EXIT_HIBERNATE, EXIT_LOGOFF, EXIT_REBOOT, EXIT_STANDBY } ExitMode; char *exit_mode[] = { "none", "poweroff", "shutdown", "hibernate", "logoff", "reboot", "standby" }; int exit_modes = sizeof(exit_mode) / sizeof(exit_mode[0]); typedef enum { WIPEMODE_NORMAL, WIPEMODE_DOD, WIPEMODE_DOD7, WIPEMODE_GUTMANN, WIPEMODE_DOE, WIPEMODE_SCHNEIER, WIPEMODE_BCI } WipeMode; char *wipe_methods[] = { "standard wiping method (1 pass per iteration)", "US DoD 5220.22-M wiping method (3 passes per iteration)", "US DoD 5200.28-STD wiping method (7 passes per iteration)", "Peter Gutmann's wiping method (35 passes per iteration)", "US DoE wiping method (3 passes per iteration)", "Bruce Schneier's wiping method (7 passes per iteration)", "German BCI/VSITR wiping method (7 passes per iteration)" }; typedef enum { RANDOM_NONE, RANDOM_PSEUDO, RANDOM_WINDOWS, #ifdef HAVE_CRYPTOGRAPHIC RANDOM_CRYPTOGRAPHIC #endif } RandomMode; struct _opt { bool list; unsigned int passes; WipeMode mode; bool yes; RandomMode random; ExitMode restart; bool force; unsigned int quiet; unsigned int sectors; ULONGLONG start; ULONGLONG end; bool read; unsigned int help; bool kilobyte; unsigned int refresh; bool ignore; }; typedef struct _opt t_opt; static t_opt opt = { false, /* list */ 0, /* passes */ WIPEMODE_NORMAL, /* normal, dod, dod7, gutmann */ false, /* yes */ RANDOM_NONE, /* pseudo, windows, cryptographic */ EXIT_NONE, /* none, poweroff, shutdown, hibernate, logoff, reboot, standby */ false, /* force */ 0, /* quiet */ SECTORS_PER_READ, /* sectors */ 0ULL, /* start */ 0ULL, /* end */ false, /* read */ 0, /* help */ false, /* kilobyte */ 1, /* refresh */ false, /* ignore */ }; /* per http://www.scit.wlv.ac.uk/cgi-bin/mansec?3C+basename */ static char* basename(char* s) { char* rv; if (!s || !*s) return "."; rv = s + strlen(s) - 1; do { if (*rv == '/' || *rv == '\\') return rv + 1; --rv; } while (rv >= s); return s; } static void Warning(char *str) { LPVOID lpMsgBuf; DWORD err = GetLastError(); FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language (LPTSTR) &lpMsgBuf, 0, NULL); fprintf(stderr, "\n%s: %s (0x%x)\n", str, lpMsgBuf, err); } static void FatalError(char *str) { DWORD err = GetLastError(); Warning(str); exit(err ? err : 1); } static BOOL add_seconds(SYSTEMTIME *st, DWORD seconds, SYSTEMTIME *rv) { FILETIME ft; SystemTimeToFileTime(st, &ft); ULARGE_INTEGER uli; uli.HighPart = ft.dwHighDateTime; uli.LowPart = ft.dwLowDateTime; uli.QuadPart += (seconds * 10000000ULL); ft.dwHighDateTime = uli.HighPart; ft.dwLowDateTime = uli.LowPart; return (BOOL) FileTimeToSystemTime(&ft, rv); }; static char *systemtime_to_hhmmss(SYSTEMTIME *st, char *rv, int bufsiz) { _snprintf(rv, bufsiz, "%02d:%02d:%02d", st->wHour, st->wMinute, st->wSecond); return rv; } static char *seconds_to_hhmmss(DWORD seconds, char *rv, int bufsiz) { DWORD hours = seconds / 3600; seconds -= hours * 3600; DWORD minutes = seconds / 60; seconds -= minutes * 60; if (hours > 99) { DWORD days = hours / 24; hours -= days * 24; if (days > 99) { _snprintf(rv, bufsiz, "%03dd %02dh", days, hours); return rv; } _snprintf(rv, bufsiz, "%02dd%02d%02d", days, hours, minutes); return rv; } _snprintf(rv, bufsiz, "%02d:%02d:%02d", hours, minutes, seconds); return rv; } struct _stats { char *device_name; DWORD bytes_per_sector; ULONGLONG tick_frequency; /* ticks to seconds divisor */ ULONGLONG start_ticks; SYSTEMTIME lpStartTime; char start_time[20]; ULONGLONG wiping_ticks; ULONGLONG all_start_ticks; ULONGLONG all_wiping_ticks; }; typedef struct _stats t_stats; static ULONGLONG get_ticks(t_stats *stats) { typedef enum {STATE_UNINITIALIZED, STATE_USE_FREQUENCY, STATE_USE_TICKCOUNT} t_state; static t_state state = STATE_UNINITIALIZED; static DWORD last_ticks; static ULONGLONG overflow_ticks = 0; if (state == STATE_UNINITIALIZED) { LARGE_INTEGER frequency; QueryPerformanceFrequency(&frequency); if (frequency.QuadPart >= 1000) { state = STATE_USE_FREQUENCY; stats->tick_frequency = frequency.QuadPart; } else { state = STATE_USE_TICKCOUNT; stats->tick_frequency = 1000; last_ticks = GetTickCount(); } } if (state == STATE_USE_FREQUENCY) { LARGE_INTEGER now; QueryPerformanceCounter(&now); return now.QuadPart; } DWORD ticks = GetTickCount(); if (ticks < last_ticks) { overflow_ticks += 0x100000000; } return overflow_ticks + (ULONGLONG) ticks; } static void print_stats(unsigned int pass, char *s_byte, ULONGLONG sector, t_stats *stats) { ULONGLONG starting_sector = opt.start; ULONGLONG ending_sector = opt.end; ULONGLONG done_sectors = (ending_sector * ((ULONGLONG) pass - 1)) + sector; ULONGLONG total_sectors = ending_sector * opt.passes; double all_pct = (double) (LONGLONG) done_sectors / (double) (LONGLONG) total_sectors * 100.0; ULONGLONG remaining_ticks = 0; ULONGLONG elapsed_ticks = get_ticks(stats) - stats->start_ticks; if (done_sectors) { remaining_ticks = (total_sectors - done_sectors) * elapsed_ticks / done_sectors; } static double kilo = USE_KILOBYTE ? 1024 : 1000; double mb_sec = 0; if (stats->wiping_ticks) { ULONGLONG bytes = done_sectors * stats->bytes_per_sector; double megabytes = (double) (LONGLONG) bytes / (kilo * kilo); double seconds = (double) (LONGLONG) stats->wiping_ticks / (double) (LONGLONG) stats->tick_frequency; //printf("\nsector=%20I64d done_sectors=%20I64d bytes=%20I64d megabytes=%20.10f seconds=%20.10f\n", sector, done_sectors, bytes, megabytes, seconds); if (seconds > 0) { mb_sec = megabytes / seconds; } } sector -= starting_sector; ending_sector -= starting_sector; double this_pct = (double) (LONGLONG) sector / (double) (LONGLONG) ending_sector * 100.0; if (sector >= ending_sector) { this_pct = 100.0; all_pct = (double) (pass) * 100.0 / (double) opt.passes; if (pass >= opt.passes) { this_pct = 100.0; all_pct = 100.0; remaining_ticks = 0; } } DWORD remaining_seconds = (DWORD) (remaining_ticks / stats->tick_frequency); char remaining_time[255]; seconds_to_hhmmss(remaining_seconds, remaining_time, sizeof(remaining_time)); DWORD elapsed_seconds = (DWORD) (elapsed_ticks / stats->tick_frequency); char elapsed_time[255]; seconds_to_hhmmss(elapsed_seconds, elapsed_time, sizeof(elapsed_time)); SYSTEMTIME lpEndTime; add_seconds(&stats->lpStartTime, elapsed_seconds + remaining_seconds, &lpEndTime); char finish_time[255]; systemtime_to_hhmmss(&lpEndTime, finish_time, sizeof(finish_time)); //char buf[255]; //_snprintf(buf, sizeof(buf), "%.3f%% - %s - %s - %s", all_pct, remaining_time, stats->device_name, progname); //SetConsoleTitle(buf); if (opt.quiet == 1) { //_snprintf(buf, sizeof(buf), "%s - %.3f%% complete - %s remaining\r", stats->device_name, all_pct, remaining_time, progname); //printf("%s\r", buf); fflush(stdout); return; } printf(FORMAT_STRING, pass, opt.passes, s_byte, this_pct, all_pct, elapsed_time, remaining_time, stats->start_time, finish_time, mb_sec); fflush(stdout); } // static int FakeDosNameForDevice (char *lpszDiskFile, char *lpszDosDevice, char *lpszCFDevice, BOOL bNameOnly) { if (strncmp(lpszDiskFile, "\\\\", 2) == 0) { strcpy(lpszCFDevice, lpszDiskFile); return 1; } BOOL bDosLinkCreated = TRUE; _snprintf(lpszDosDevice, MAX_PATH, "dskwipe%lu", GetCurrentProcessId()); if (bNameOnly == FALSE) bDosLinkCreated = DefineDosDevice (DDD_RAW_TARGET_PATH, lpszDosDevice, lpszDiskFile); if (bDosLinkCreated == FALSE) { return 1; } else { _snprintf(lpszCFDevice, MAX_PATH, "\\\\.\\%s", lpszDosDevice); } return 0; } static int RemoveFakeDosName (char *lpszDiskFile, char *lpszDosDevice) { BOOL bDosLinkRemoved = DefineDosDevice (DDD_RAW_TARGET_PATH | DDD_EXACT_MATCH_ON_REMOVE | DDD_REMOVE_DEFINITION, lpszDosDevice, lpszDiskFile); if (bDosLinkRemoved == FALSE) { return 1; } return 0; } static void GetSizeString (LONGLONG size, wchar_t *str) { static wchar_t *b, *kb, *mb, *gb, *tb, *pb; if (b == NULL) { if (USE_KILOBYTE) { kb = L"KiB"; mb = L"MiB"; gb = L"GiB"; tb = L"TiB"; pb = L"PiB"; } else { kb = L"KB"; mb = L"MB"; gb = L"GB"; tb = L"TB"; pb = L"PB"; } b = L"bytes"; } DWORD kilo = USE_KILOBYTE ? 1024 : 1000; LONGLONG kiloI64 = kilo; double kilod = kilo; if (size > kiloI64 * kilo * kilo * kilo * kilo * 99) swprintf (str, BUFFER_SIZE_WCHAR, L"%I64d %s", size/ kilo / kilo /kilo/kilo/kilo, pb); else if (size > kiloI64*kilo*kilo*kilo*kilo) swprintf (str, BUFFER_SIZE_WCHAR, L"%.1f %s", (double)(size/kilod/kilo/kilo/kilo/kilo), pb); else if (size > kiloI64*kilo*kilo*kilo*99) swprintf (str, BUFFER_SIZE_WCHAR, L"%I64d %s", size/kilo/kilo/kilo/kilo, tb); else if (size > kiloI64*kilo*kilo*kilo) swprintf (str, BUFFER_SIZE_WCHAR, L"%.1f %s", (double)(size/kilod/kilo/kilo/kilo), tb); else if (size > kiloI64*kilo*kilo*99) swprintf (str, BUFFER_SIZE_WCHAR, L"%I64d %s", size/kilo/kilo/kilo, gb); else if (size > kiloI64*kilo*kilo) swprintf (str, BUFFER_SIZE_WCHAR, L"%.1f %s", (double)(size/kilod/kilo/kilo), gb); else if (size > kiloI64*kilo*99) swprintf (str, BUFFER_SIZE_WCHAR, L"%I64d %s", size/kilo/kilo, mb); else if (size > kiloI64*kilo) swprintf (str, BUFFER_SIZE_WCHAR, L"%.1f %s", (double)(size/kilod/kilo), mb); else if (size > kiloI64) swprintf (str, BUFFER_SIZE_WCHAR, L"%I64d %s", size/kilo, kb); else swprintf (str, BUFFER_SIZE_WCHAR, L"%I64d %s", size, b); } static void list_device(char *format_str, char *szTmp, int n) { int nDosLinkCreated; HANDLE dev; DWORD dwResult; BOOL bResult; PARTITION_INFORMATION diskInfo; DISK_GEOMETRY driveInfo; char szDosDevice[MAX_PATH], szCFDevice[MAX_PATH]; static LONGLONG deviceSize = 0; wchar_t size[BUFFER_SIZE_CHAR] = {0}, partTypeStr[1024] = {0}, *partType = partTypeStr; BOOL drivePresent = FALSE; BOOL removable = FALSE; drivePresent = TRUE; nDosLinkCreated = FakeDosNameForDevice (szTmp, szDosDevice, szCFDevice, FALSE); dev = CreateFile (szCFDevice, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE , NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL); bResult = DeviceIoControl (dev, IOCTL_DISK_GET_PARTITION_INFO, NULL, 0, &diskInfo, sizeof (diskInfo), &dwResult, NULL); // Test if device is removable if (/* n == 0 && */ DeviceIoControl (dev, IOCTL_DISK_GET_DRIVE_GEOMETRY, NULL, 0, &driveInfo, sizeof (driveInfo), &dwResult, NULL)) removable = driveInfo.MediaType == RemovableMedia; RemoveFakeDosName(szTmp, szDosDevice); CloseHandle(dev); if (!bResult) return; // System creates a virtual partition1 for some storage devices without // partition table. We try to detect this case by comparing sizes of // partition0 and partition1. If they match, no partition of the device // is displayed to the user to avoid confusion. Drive letter assigned by // system to partition1 is displayed as subitem of partition0 if (n == 0) { deviceSize = diskInfo.PartitionLength.QuadPart; } if (n > 0 && diskInfo.PartitionLength.QuadPart == deviceSize) { return; } switch(diskInfo.PartitionType) { case PARTITION_ENTRY_UNUSED: partType = L""; break; case PARTITION_XINT13_EXTENDED: case PARTITION_EXTENDED: partType = L"Extended"; break; case PARTITION_HUGE: wsprintfW (partTypeStr, L"%s (0x%02X)", L"Unformatted", diskInfo.PartitionType); partType = partTypeStr; break; case PARTITION_FAT_12: partType = L"FAT12"; break; case PARTITION_FAT_16: partType = L"FAT16"; break; case PARTITION_FAT32: case PARTITION_FAT32_XINT13: partType = L"FAT32"; break; case 0x08: partType = L"DELL (spanning)"; break; case 0x12: partType = L"Config/diagnostics"; break; case 0x11: case 0x14: case 0x16: case 0x1b: case 0x1c: case 0x1e: partType = L"Hidden FAT"; break; case PARTITION_IFS: partType = L"NTFS"; break; case 0x17: partType = L"Hidden NTFS"; break; case 0x3c: partType = L"PMagic recovery"; break; case 0x3d: partType = L"Hidden NetWare"; break; case 0x41: partType = L"Linux/MINIX"; break; case 0x42: partType = L"SFS/LDM/Linux Swap"; break; case 0x51: case 0x64: case 0x65: case 0x66: case 0x67: case 0x68: case 0x69: partType = L"Novell"; break; case 0x55: partType = L"EZ-Drive"; break; case PARTITION_OS2BOOTMGR: partType = L"OS/2 BM"; break; case PARTITION_XENIX_1: case PARTITION_XENIX_2: partType = L"Xenix"; break; case PARTITION_UNIX: partType = L"UNIX"; break; case 0x74: partType = L"Scramdisk"; break; case 0x78: partType = L"XOSL FS"; break; case 0x80: case 0x81: partType = L"MINIX"; break; case 0x82: partType = L"Linux Swap"; break; case 0x43: case 0x83: partType = L"Linux"; break; case 0xc2: case 0x93: partType = L"Hidden Linux"; break; case 0x86: case 0x87: partType = L"NTFS volume set"; break; case 0x9f: partType = L"BSD/OS"; break; case 0xa0: case 0xa1: partType = L"Hibernation"; break; case 0xa5: partType = L"BSD"; break; case 0xa8: partType = L"Mac OS-X"; break; case 0xa9: partType = L"NetBSD"; break; case 0xab: partType = L"Mac OS-X Boot"; break; case 0xb8: partType = L"BSDI BSD/386 swap"; break; case 0xc3: partType = L"Hidden Linux swap"; break; case 0xfb: partType = L"VMware"; break; case 0xfc: partType = L"VMware swap"; break; case 0xfd: partType = L"Linux RAID"; break; case 0xfe: partType = L"WinNT hidden"; break; default: wsprintfW(partTypeStr, L"0x%02X", diskInfo.PartitionType); partType = partTypeStr; break; } GetSizeString(diskInfo.PartitionLength.QuadPart, size); char *s_type = removable ? "Removable" : "Fixed"; printf(format_str, szTmp, size, s_type, partType); } // void print_ticks(char *fmt, ULONGLONG ticks, ULONGLONG tick_frequency) { char wiping_time[255]; DWORD seconds = (DWORD) (ticks / tick_frequency); seconds_to_hhmmss(seconds, wiping_time, sizeof(wiping_time)); printf(fmt, wiping_time); } static void list_devices() { printf( "Device Name Size Type Partition Type\n" "------------------------------ --------- --------- --------------------\n" // 123456789012345678901234567890 123456789 123456789 12345678901234567890 // \Device\Harddisk30\Partition03 1234.1 GB Removable SFS/LDM/Linux Swap ); char *format_str = "%-30s %9S %-9s %-20S\n"; char szTmp[MAX_PATH]; int i; for (i = 0; i < 64; i++) { _snprintf(szTmp, sizeof(szTmp), "\\\\.\\PhysicalDrive%d", i); list_device(format_str, szTmp, 0); } for (i = 0; i < 64; i++) { for (int n = 0; n <= 32; n++) { _snprintf(szTmp, sizeof(szTmp), "\\Device\\Harddisk%d\\Partition%d", i, n); list_device(format_str, szTmp, n); } } for (i = 0; i < 8; i++) { _snprintf(szTmp, sizeof(szTmp), "\\Device\\Floppy%d", i); list_device(format_str, szTmp, 0); } list_device(format_str, "\\Device\\Ramdisk", 0); for (i = 0; i < 26; i++) { _snprintf(szTmp, sizeof(szTmp), "\\\\.\\%c:", 'A' + i); list_device(format_str, szTmp, 0); } } void print_device_info(char *device_name) { char szCFDevice[MAX_PATH]; char szDosDevice[MAX_PATH]; int nDosLinkCreated = FakeDosNameForDevice(device_name, szDosDevice, szCFDevice, FALSE); char err[256]; printf("Device: %s\n", device_name); SetErrorMode(SEM_NOOPENFILEERRORBOX); HANDLE hnd = CreateFile( szCFDevice, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL); if (hnd == INVALID_HANDLE_VALUE) { _snprintf(err, sizeof(err), "Cannot open '%s'", device_name); FatalError(err); } DISK_GEOMETRY driveInfo; PARTITION_INFORMATION diskInfo; DWORD dwResult; BOOL bResult; dwResult = 0; bResult = DeviceIoControl( hnd, IOCTL_DISK_GET_DRIVE_GEOMETRY, NULL, 0, &driveInfo, sizeof(driveInfo), &dwResult, NULL); if (!bResult) { _snprintf(err, sizeof(err), "Cannot query '%s'", device_name); FatalError(err); } CloseHandle(hnd); ULONGLONG last_sector = driveInfo.Cylinders.QuadPart * driveInfo.TracksPerCylinder * driveInfo.SectorsPerTrack; ULONGLONG total_sectors = last_sector + 1; ULONGLONG total_bytes = total_sectors * driveInfo.BytesPerSector; wchar_t size[BUFFER_SIZE_CHAR]; GetSizeString(total_bytes, size); printf("Cylinders: %I64d\n", driveInfo.Cylinders.QuadPart); printf("Tracks/cylinder: %d\n", driveInfo.TracksPerCylinder); printf("Sectors/track: %d\n", driveInfo.SectorsPerTrack); printf("Bytes/sector: %d\n", driveInfo.BytesPerSector); printf("Total Sectors: %I64d\n", total_sectors); printf("Total Bytes: %I64d\n", total_bytes); printf("Size: %S\n", size); } int main(int argc, char * argv[]) { list_devices(); return 0; }