ntfs-3g/ntfsprogs/ntfswipe.c

1399 lines
33 KiB
C

/**
* ntfswipe - Part of the Linux-NTFS project.
*
* Copyright (c) 2002-2003 Richard Russon
* Copyright (c) 2004 Yura Pakhuchiy
*
* This utility will overwrite unused space on an NTFS volume.
*
* 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; either version 2 of the License, or
* (at your option) any later version.
*
* 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 (in the main directory of the Linux-NTFS
* distribution in the file COPYING); if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "config.h"
#include <stdio.h>
#include <errno.h>
#include <stdarg.h>
#include <getopt.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include "ntfswipe.h"
#include "types.h"
#include "volume.h"
#include "utils.h"
#include "debug.h"
#include "dir.h"
#include "mst.h"
static const char *EXEC_NAME = "ntfswipe";
static struct options opts;
GEN_PRINTF (Eprintf, stderr, NULL, FALSE)
GEN_PRINTF (Vprintf, stdout, &opts.verbose, TRUE)
GEN_PRINTF (Qprintf, stdout, &opts.quiet, FALSE)
/**
* version - Print version information about the program
*
* Print a copyright statement and a brief description of the program.
*
* Return: none
*/
static void version (void)
{
printf ("\n%s v%s - Overwrite the unused space on an NTFS Volume.\n\n",
EXEC_NAME, VERSION);
printf ("Copyright (c) 2002-2003 Richard Russon\n");
printf ("Copyright (c) 2004 Yura Pakhuchiy\n");
printf ("\n%s\n%s%s\n", ntfs_gpl, ntfs_bugs, ntfs_home);
}
/**
* usage - Print a list of the parameters to the program
*
* Print a list of the parameters and options for the program.
*
* Return: none
*/
static void usage (void)
{
printf ("\nUsage: %s [options] device\n"
" -i --info Show volume information (default)\n"
"\n"
" -d --directory Wipe directory indexes\n"
" -l --logfile Wipe the logfile (journal)\n"
" -m --mft Wipe mft space\n"
" -p --pagefile Wipe pagefile (swap space)\n"
" -t --tails Wipe file tails\n"
" -u --unused Wipe unused clusters\n"
"\n"
" -a --all Wipe all unused space\n"
"\n"
" -c num --count num Number of times to write (default = 1)\n"
" -b list --bytes list List of values to write (default = 0)\n"
"\n"
" -n --no-action Do not write to disk\n"
" -f --force Use less caution\n"
" -q --quiet Less output\n"
" -v --verbose More output\n"
" -V --version Version information\n"
" -h --help Print this help\n\n",
EXEC_NAME);
printf ("%s%s\n", ntfs_bugs, ntfs_home);
}
/**
* parse_list - Read a comma-separated list of numbers
* @list: The comma-separated list of numbers
* @result: Store the parsed list here (must be freed by caller)
*
* Read a comma-separated list of numbers and allocate an array of ints to store
* them in. The numbers can be in decimal, octal or hex.
*
* N.B. The caller must free the memory returned in @result.
* N.B. If the function fails, @result is not changed.
*
* Return: 0 Error, invalid string
* n Success, the count of numbers parsed
*/
static int parse_list (char *list, int **result)
{
char *ptr;
char *end;
int i;
int count;
int *mem = NULL;
if (!list || !result)
return 0;
for (count = 0, ptr = list; ptr; ptr = strchr (ptr+1, ','))
count++;
mem = malloc ((count+1) * sizeof (int));
if (!mem) {
Eprintf ("Couldn't allocate memory in parse_list().\n");
return 0;
}
memset (mem, 0xFF, (count+1) * sizeof (int));
for (ptr = list, i = 0; i < count; i++) {
end = NULL;
mem[i] = strtol (ptr, &end, 0);
if (!end || (end == ptr) || ((*end != ',') && (*end != 0))) {
Eprintf ("Invalid list '%s'\n", list);
free (mem);
return 0;
}
if ((mem[i] < 0) || (mem[i] > 255)) {
Eprintf ("Bytes must be in range 0-255.\n");
free (mem);
return 0;
}
ptr = end + 1;
}
Dprintf ("Parsing list '%s' - ", list);
for (i = 0; i <= count; i++)
Dprintf ("0x%02x ", mem[i]);
Dprintf ("\n");
*result = mem;
return count;
}
/**
* parse_options - Read and validate the programs command line
*
* Read the command line, verify the syntax and parse the options.
* This function is very long, but quite simple.
*
* Return: 1 Success
* 0 Error, one or more problems
*/
static int parse_options (int argc, char *argv[])
{
static const char *sopt = "-ab:c:dfh?ilmnpqtuvV";
static struct option lopt[] = {
{ "all", no_argument, NULL, 'a' },
{ "bytes", required_argument, NULL, 'b' },
{ "count", required_argument, NULL, 'c' },
{ "directory", no_argument, NULL, 'd' },
{ "force", no_argument, NULL, 'f' },
{ "help", no_argument, NULL, 'h' },
{ "info", no_argument, NULL, 'i' },
{ "logfile", no_argument, NULL, 'l' },
{ "mft", no_argument, NULL, 'm' },
{ "no-action", no_argument, NULL, 'n' },
//{ "no-wait", no_argument, NULL, 0 },
{ "pagefile", no_argument, NULL, 'p' },
{ "quiet", no_argument, NULL, 'q' },
{ "tails", no_argument, NULL, 't' },
{ "unused", no_argument, NULL, 'u' },
{ "verbose", no_argument, NULL, 'v' },
{ "version", no_argument, NULL, 'V' },
{ NULL, 0, NULL, 0 }
};
char c = -1;
char *end;
int err = 0;
int ver = 0;
int help = 0;
opterr = 0; /* We'll handle the errors, thank you. */
opts.count = 1;
while ((c = getopt_long (argc, argv, sopt, lopt, NULL)) != -1) {
switch (c) {
case 1: /* A non-option argument */
if (!opts.device) {
opts.device = argv[optind-1];
} else {
opts.device = NULL;
err++;
}
break;
case 'i':
opts.info++; /* and fall through */
case 'a':
opts.directory++;
opts.logfile++;
opts.mft++;
opts.pagefile++;
opts.tails++;
opts.unused++;
break;
case 'b':
if (!opts.bytes) {
if (!parse_list (optarg, &opts.bytes))
err++;
} else {
err++;
}
break;
case 'c':
if (opts.count == 1) {
end = NULL;
opts.count = strtol (optarg, &end, 0);
if (end && *end)
err++;
} else {
err++;
}
break;
case 'd':
opts.directory++;
break;
case 'f':
opts.force++;
break;
case 'h':
case '?':
help++;
break;
case 'l':
opts.logfile++;
break;
case 'm':
opts.mft++;
break;
case 'n':
opts.noaction++;
break;
case 'p':
opts.pagefile++;
break;
case 'q':
opts.quiet++;
break;
case 't':
opts.tails++;
break;
case 'u':
opts.unused++;
break;
case 'v':
opts.verbose++;
break;
case 'V':
ver++;
break;
default:
if ((optopt == 'b') || (optopt == 'c')) {
Eprintf ("Option '%s' requires an argument.\n", argv[optind-1]);
} else {
Eprintf ("Unknown option '%s'.\n", argv[optind-1]);
}
err++;
break;
}
}
if (help || ver) {
opts.quiet = 0;
} else {
if (opts.device == NULL) {
if (argc > 1)
Eprintf ("You must specify exactly one device.\n");
err++;
}
if (opts.quiet && opts.verbose) {
Eprintf ("You may not use --quiet and --verbose at the same time.\n");
err++;
}
/*
if (opts.info && (opts.unused || opts.tails || opts.mft || opts.directory)) {
Eprintf ("You may not use any other options with --info.\n");
err++;
}
*/
if ((opts.count < 1) || (opts.count > 100)) {
Eprintf ("The iteration count must be between 1 and 100.\n");
err++;
}
/* Create a default list */
if (!opts.bytes) {
opts.bytes = malloc (2 * sizeof (int));
if (opts.bytes) {
opts.bytes[0] = 0;
opts.bytes[1] = -1;
} else {
Eprintf ("Couldn't allocate memory for byte list.\n");
err++;
}
}
if (!opts.directory && !opts.logfile && !opts.mft &&
!opts.pagefile && !opts.tails && !opts.unused) {
opts.info = 1;
}
}
if (ver)
version();
if (help || err)
usage();
return (!err && !help && !ver);
}
/**
* wipe_unused - Wipe unused clusters
* @vol: An ntfs volume obtained from ntfs_mount
* @byte: Overwrite with this value
* @act: Wipe, test or info
*
* Read $Bitmap and wipe any clusters that are marked as not in use.
*
* Return: >0 Success, the atrribute was wiped
* 0 Nothing to wipe
* -1 Error, something went wrong
*/
static s64 wipe_unused (ntfs_volume *vol, int byte, enum action act)
{
s64 i;
s64 total = 0;
s64 result = 0;
u8 *buffer = NULL;
if (!vol || (byte < 0))
return -1;
if (act != act_info) {
buffer = malloc (vol->cluster_size);
if (!buffer) {
Eprintf ("malloc failed\n");
return -1;
}
memset (buffer, byte, vol->cluster_size);
}
for (i = 0; i < vol->nr_clusters; i++) {
if (utils_cluster_in_use (vol, i)) {
//Vprintf ("cluster %lld is in use\n", i);
continue;
}
if (act == act_wipe) {
//Vprintf ("cluster %lld is not in use\n", i);
result = ntfs_pwrite (vol->dev, vol->cluster_size * i, vol->cluster_size, buffer);
if (result != vol->cluster_size) {
Eprintf ("write failed\n");
goto free;
}
}
total += vol->cluster_size;
}
Qprintf ("wipe_unused 0x%02x, %lld bytes\n", byte, (long long)total);
free:
free (buffer);
return total;
}
/**
* wipe_compressed_attribute - Wipe compressed $DATA attribute
* @vol: An ntfs volume obtained from ntfs_mount
* @byte: Overwrite with this value
* @act: Wipe, test or info
* @na: Opened ntfs attribute
*
* Return: >0 Success, the atrribute was wiped
* 0 Nothing to wipe
* -1 Error, something went wrong
*/
static s64 wipe_compressed_attribute (ntfs_volume *vol, int byte,
enum action act, ntfs_attr *na)
{
unsigned char *buf;
s64 size, offset, ret, wiped = 0;
u16 block_size;
VCN cur_vcn = 0;
runlist *rlc = na->rl;
s64 cu_mask = na->compression_block_clusters - 1;
while (rlc->length) {
cur_vcn += rlc->length;
if ((cur_vcn & cu_mask) ||
(((rlc + 1)->length) && (rlc->lcn != LCN_HOLE))) {
rlc++;
continue;
}
if (rlc->lcn == LCN_HOLE) {
runlist *rlt;
offset = cur_vcn - rlc->length;
if (offset == (offset & (~cu_mask))) {
rlc++;
continue;
}
offset = (offset & (~cu_mask))
<< vol->cluster_size_bits;
rlt = rlc;
while ((rlt - 1)->lcn == LCN_HOLE) rlt--;
while (1) {
ret = ntfs_rl_pread (vol, na->rl,
offset, 2, &block_size);
block_size = le16_to_cpu (block_size);
if (ret != 2) {
Vprintf ("Internal error\n");
Eprintf ("ntfs_rl_pread failed");
return -1;
}
if (block_size == 0) {
offset += 2;
break;
}
block_size &= 0x0FFF;
block_size += 3;
offset += block_size;
if (offset >= (((rlt->vcn) <<
vol->cluster_size_bits) - 2))
goto next;
}
size = (rlt->vcn << vol->cluster_size_bits) - offset;
} else {
size = na->allocated_size - na->data_size;
offset = (cur_vcn << vol->cluster_size_bits) - size;
}
if (size < 0) {
Vprintf ("Internal error\n");
Eprintf ("bug or damaged fs: we want "
"allocate buffer size %lld bytes", size);
return -1;
}
if ((act == act_info) || (!size)) {
wiped += size;
rlc++;
continue;
}
buf = malloc (size);
if (!buf) {
Vprintf ("Not enough memory\n");
Eprintf ("Not enough memory to allocate "
"%lld bytes", size);
return -1;
}
memset (buf, byte, size);
ret = ntfs_rl_pwrite (vol, na->rl, offset, size, buf);
free (buf);
if (ret != size) {
Vprintf ("Internal error\n");
Eprintf ("ntfs_rl_pwrite failed, offset %llu, "
"size %lld, vcn %lld", offset, size, rlc->vcn);
return -1;
}
wiped += ret;
next:
rlc++;
}
return wiped;
}
/**
* wipe_attribute - Wipe not compressed $DATA attribute
* @vol: An ntfs volume obtained from ntfs_mount
* @byte: Overwrite with this value
* @act: Wipe, test or info
* @na: Opened ntfs attribute
*
* Return: >0 Success, the atrribute was wiped
* 0 Nothing to wipe
* -1 Error, something went wrong
*/
static s64 wipe_attribute (ntfs_volume *vol, int byte, enum action act,
ntfs_attr *na)
{
unsigned char *buf;
s64 wiped;
s64 size;
u64 offset = na->data_size;
if (!offset)
return 0;
if (NAttrEncrypted(na))
offset = (((offset - 1) >> 10) + 1) << 10;
size = (vol->cluster_size - offset) % vol->cluster_size;
if (act == act_info)
return size;
buf = malloc (size);
if (!buf) {
Vprintf ("Not enough memory\n");
Eprintf ("Not enough memory to allocate %lld bytes", size);
return -1;
}
memset (buf, byte, size);
wiped = ntfs_rl_pwrite (vol, na->rl, offset, size, buf);
if (wiped == -1) {
Vprintf ("Internal error\n");
Eprintf ("Couldn't wipe tail");
}
free (buf);
return wiped;
}
/**
* wipe_tails - Wipe the file tails
* @vol: An ntfs volume obtained from ntfs_mount
* @byte: Overwrite with this value
* @act: Wipe, test or info
*
* Disk space is allocated in clusters. If a file isn't an exact multiple of
* the cluster size, there is some slack space at the end. Wipe this space.
*
* Return: >0 Success, the clusters were wiped
* 0 Nothing to wipe
* -1 Error, something went wrong
*/
static s64 wipe_tails (ntfs_volume *vol, int byte, enum action act)
{
s64 total = 0;
s64 inode_num;
ntfs_inode *ni;
ntfs_attr *na;
if (!vol || (byte < 0))
return -1;
for (inode_num = 16; inode_num < vol->nr_mft_records; inode_num++) {
s64 wiped;
Vprintf ("Inode %lld - ", inode_num);
ni = ntfs_inode_open (vol, inode_num);
if (!ni) {
Vprintf ("Could not open inode\n");
continue;
}
if (ni->mrec->base_mft_record) {
Vprintf ("Not base mft record. Skipping\n");
goto close_inode;
}
na = ntfs_attr_open (ni, AT_DATA, AT_UNNAMED, 0);
if (!na) {
Vprintf ("Couldn't open $DATA attribute\n");
goto close_inode;
}
if (!NAttrNonResident(na)) {
Vprintf ("Resident $DATA atrribute. Skipping.\n");
goto close_attr;
}
if (ntfs_attr_map_whole_runlist(na)) {
Vprintf ("Internal error\n");
Eprintf ("Can't map runlist (inode %lld)\n", inode_num);
goto close_attr;
}
if (NAttrCompressed(na))
wiped = wipe_compressed_attribute (vol, byte, act, na);
else
wiped = wipe_attribute (vol, byte, act, na);
if (wiped == -1) {
Eprintf (" (inode %lld)\n", inode_num);
goto close_attr;
}
if (wiped) {
Vprintf ("Wiped %llu bytes\n", wiped);
total += wiped;
} else
Vprintf ("Nothing to wipe\n");
close_attr:
ntfs_attr_close (na);
close_inode:
ntfs_inode_close (ni);
}
Qprintf ("wipe_tails 0x%02x, %lld bytes\n", byte, total);
return total;
}
/**
* wipe_mft - Wipe the MFT slack space
* @vol: An ntfs volume obtained from ntfs_mount
* @byte: Overwrite with this value
* @act: Wipe, test or info
*
* MFT Records are 1024 bytes long, but some of this space isn't used. Wipe any
* unused space at the end of the record and wipe any unused records.
*
* Return: >0 Success, the clusters were wiped
* 0 Nothing to wipe
* -1 Error, something went wrong
*/
static s64 wipe_mft (ntfs_volume *vol, int byte, enum action act)
{
// by considering the individual attributes we might be able to
// wipe a few more bytes at the attr's tail.
s64 i;
s64 total = 0;
s64 result = 0;
int size = 0;
u8 *buffer = NULL;
if (!vol || (byte < 0))
return -1;
buffer = malloc (vol->mft_record_size);
if (!buffer) {
Eprintf ("malloc failed\n");
return -1;
}
for (i = 0; i < vol->nr_mft_records; i++) {
if (utils_mftrec_in_use (vol, i)) {
result = ntfs_attr_mst_pread (vol->mft_na, vol->mft_record_size * i,
1, vol->mft_record_size, buffer);
if (result != 1) {
Eprintf ("error attr mst read %lld\n",
(long long)i);
total = -1; // XXX just negate result?
goto free;
}
// We know that the end marker will only take 4 bytes
size = *((u32*) (buffer + 0x18)) - 4;
if (act == act_info) {
//printf ("mft %d\n", size);
total += size;
continue;
}
memset (buffer + size, byte, vol->mft_record_size - size);
result = ntfs_attr_mst_pwrite (vol->mft_na, vol->mft_record_size * i,
1, vol->mft_record_size, buffer);
if (result != 1) {
Eprintf ("error attr mst write %lld\n",
(long long)i);
total = -1;
goto free;
}
if ((vol->mft_record_size * (i+1)) <= vol->mftmirr_na->allocated_size)
{
// We have to reduce the update sequence number, or else...
u16 offset;
u16 usa;
offset = le16_to_cpu (*(buffer + 0x04));
usa = le16_to_cpu (*(buffer + offset));
*((u16*) (buffer + offset)) = cpu_to_le16 (usa - 1);
result = ntfs_attr_mst_pwrite (vol->mftmirr_na, vol->mft_record_size * i,
1, vol->mft_record_size, buffer);
if (result != 1) {
Eprintf ("error attr mst write %lld\n",
(long long)i);
total = -1;
goto free;
}
}
total += vol->mft_record_size;
} else {
if (act == act_info) {
total += vol->mft_record_size;
continue;
}
// Build the record from scratch
memset (buffer, 0, vol->mft_record_size);
// Common values
*((u32*) (buffer + 0x00)) = magic_FILE; // Magic
*((u16*) (buffer + 0x06)) = cpu_to_le16 (0x0003); // USA size
*((u16*) (buffer + 0x10)) = cpu_to_le16 (0x0001); // Seq num
*((u32*) (buffer + 0x1C)) = cpu_to_le32 (vol->mft_record_size); // FILE size
*((u16*) (buffer + 0x28)) = cpu_to_le16 (0x0001); // Attr ID
if (vol->major_ver == 3) {
// Only XP and 2K3
*((u16*) (buffer + 0x04)) = cpu_to_le16 (0x0030); // USA offset
*((u16*) (buffer + 0x14)) = cpu_to_le16 (0x0038); // Attr offset
*((u32*) (buffer + 0x18)) = cpu_to_le32 (0x00000040); // FILE usage
*((u32*) (buffer + 0x38)) = cpu_to_le32 (0xFFFFFFFF); // End marker
} else {
// Only NT and 2K
*((u16*) (buffer + 0x04)) = cpu_to_le16 (0x002A); // USA offset
*((u16*) (buffer + 0x14)) = cpu_to_le16 (0x0030); // Attr offset
*((u32*) (buffer + 0x18)) = cpu_to_le32 (0x00000038); // FILE usage
*((u32*) (buffer + 0x30)) = cpu_to_le32 (0xFFFFFFFF); // End marker
}
result = ntfs_attr_mst_pwrite (vol->mft_na, vol->mft_record_size * i,
1, vol->mft_record_size, buffer);
if (result != 1) {
Eprintf ("error attr mst write %lld\n",
(long long)i);
total = -1;
goto free;
}
total += vol->mft_record_size;
}
}
Qprintf ("wipe_mft 0x%02x, %lld bytes\n", byte, (long long)total);
free:
free (buffer);
return total;
}
/**
* wipe_index_allocation - Wipe $INDEX_ALLOCATION attribute
* @vol: An ntfs volume obtained from ntfs_mount
* @byte: Overwrite with this value
* @act: Wipe, test or info
* @naa: Opened ntfs $INDEX_ALLOCATION attribute
* @nab: Opened ntfs $BIMTAP attribute
* @indx_record_size: Size of INDX record
*
* Return: >0 Success, the clusters were wiped
* 0 Nothing to wipe
* -1 Error, something went wrong
*/
static s64 wipe_index_allocation (ntfs_volume *vol, int byte, enum action act,
ntfs_attr *naa, ntfs_attr *nab, u32 indx_record_size) {
s64 total = 0;
s64 wiped = 0;
s64 offset = 0;
s64 obyte = 0;
u64 wipe_offset;
s64 wipe_size;
u8 obit = 0;
u8 mask;
u8 *bitmap;
u8 *buf;
bitmap = malloc (nab->data_size);
if (!bitmap) {
Vprintf ("malloc failed\n");
Eprintf ("Couldn't allocate %lld bytes", nab->data_size);
return -1;
}
if (ntfs_attr_pread (nab, 0, nab->data_size, bitmap)
!= nab->data_size) {
Vprintf ("Internal error\n");
Eprintf ("Couldn't read $BITMAP");
total = -1;
goto free_bitmap;
}
buf = malloc (indx_record_size);
if (!buf) {
Vprintf ("malloc failed\n");
Eprintf ("Couldn't allocate %u bytes",
(unsigned int)indx_record_size);
total = -1;
goto free_bitmap;
}
while (offset < naa->allocated_size) {
mask = 1 << obit;
if (bitmap[obyte] & mask) {
INDEX_ALLOCATION *indx;
s64 ret = ntfs_rl_pread (vol, naa->rl,
offset, indx_record_size, buf);
if (ret != indx_record_size) {
Vprintf ("ntfs_rl_pread failed\n");
Eprintf ("Couldn't read INDX record");
total = -1;
goto free_buf;
}
indx = (INDEX_ALLOCATION *) buf;
if (ntfs_mst_post_read_fixup ((NTFS_RECORD *)buf,
indx_record_size))
Eprintf ("damaged fs: mst_post_read_fixup failed");
if ((le32_to_cpu(indx->index.allocated_size) + 0x18) !=
indx_record_size) {
Vprintf ("Internal error\n");
Eprintf ("INDX record should be %u bytes",
(unsigned int)indx_record_size);
total = -1;
goto free_buf;
}
wipe_offset = le32_to_cpu(indx->index.index_length) + 0x18;
wipe_size = indx_record_size - wipe_offset;
memset (buf + wipe_offset, byte, wipe_size);
if (ntfs_mst_pre_write_fixup ((NTFS_RECORD *)indx,
indx_record_size))
Eprintf ("damaged fs: mst_pre_write_protect failed");
if (opts.verbose > 1)
Vprintf ("+");
} else {
wipe_size = indx_record_size;
memset (buf, byte, wipe_size);
if (opts.verbose > 1)
Vprintf ("x");
}
wiped = ntfs_rl_pwrite (vol, naa->rl, offset, indx_record_size, buf);
if (wiped != indx_record_size) {
Vprintf ("ntfs_rl_pwrite failed\n");
Eprintf ("Couldn't wipe tail of INDX record");
total = -1;
goto free_buf;
}
total += wipe_size;
offset += indx_record_size;
obit++;
if (obit > 7) {
obit = 0;
obyte++;
}
}
if ((opts.verbose > 1) && (wiped != -1))
Vprintf ("\n\t");
free_buf:
free (buf);
free_bitmap:
free (bitmap);
return total;
}
/**
* get_indx_record_size - determine size of INDX record from $INDEX_ROOT
* @nar: Opened ntfs $INDEX_ROOT attribute
*
* Return: >0 Success, return INDX record size
* 0 Error, something went wrong
*/
static u32 get_indx_record_size (ntfs_attr *nar)
{
u32 indx_record_size;
if (ntfs_attr_pread (nar, 8, 4, &indx_record_size) != 4) {
Vprintf ("Couldn't determine size of INDX record\n");
Eprintf ("ntfs_attr_pread failed");
return 0;
}
indx_record_size = le32_to_cpu (indx_record_size);
if (!indx_record_size) {
Vprintf ("Internal error\n");
Eprintf ("INDX record should be 0");
}
return indx_record_size;
}
/**
* wipe_directory - Wipe the directory indexes
* @vol: An ntfs volume obtained from ntfs_mount
* @byte: Overwrite with this value
* @act: Wipe, test or info
*
* Directories are kept in sorted B+ Trees. Index blocks may not be full. Wipe
* the unused space at the ends of these blocks.
*
* Return: >0 Success, the clusters were wiped
* 0 Nothing to wipe
* -1 Error, something went wrong
*/
static s64 wipe_directory (ntfs_volume *vol, int byte, enum action act)
{
s64 total = 0;
s64 inode_num;
ntfs_inode *ni;
ntfs_attr *naa;
ntfs_attr *nab;
ntfs_attr *nar;
if (!vol || (byte < 0))
return -1;
for (inode_num = 5; inode_num < vol->nr_mft_records; inode_num++) {
u32 indx_record_size;
s64 wiped;
Vprintf ("Inode %lld - ", inode_num);
ni = ntfs_inode_open (vol, inode_num);
if (!ni) {
if (opts.verbose > 2)
Vprintf ("Could not open inode\n");
else
Vprintf ("\r");
continue;
}
if (ni->mrec->base_mft_record) {
if (opts.verbose > 2)
Vprintf ("Not base mft record. Skipping\n");
else
Vprintf ("\r");
goto close_inode;
}
naa = ntfs_attr_open (ni, AT_INDEX_ALLOCATION, I30, 4);
if (!naa) {
if (opts.verbose > 2)
Vprintf ("Couldn't open $INDEX_ALLOCATION\n");
else
Vprintf ("\r");
goto close_inode;
}
if (!NAttrNonResident(naa)) {
Vprintf ("Resident $INDEX_ALLOCATION\n");
Eprintf ("damaged fs: Resident $INDEX_ALLOCATION "
"(inode %lld)\n", inode_num);
goto close_attr_allocation;
}
if (ntfs_attr_map_whole_runlist(naa)) {
Vprintf ("Internal error\n");
Eprintf ("Can't map runlist for $INDEX_ALLOCATION "
"(inode %lld)\n", inode_num);
goto close_attr_allocation;
}
nab = ntfs_attr_open (ni, AT_BITMAP, I30, 4);
if (!nab) {
Vprintf ("Couldn't open $BITMAP\n");
Eprintf ("damaged fs: $INDEX_ALLOCATION is present, "
"but we can't open $BITMAP with same "
"name (inode %lld)\n", inode_num);
goto close_attr_allocation;
}
nar = ntfs_attr_open (ni, AT_INDEX_ROOT, I30, 4);
if (!nar) {
Vprintf ("Couldn't open $INDEX_ROOT\n");
Eprintf ("damaged fs: $INDEX_ALLOCATION is present, but "
"we can't open $INDEX_ROOT with same name"
" (inode %lld)\n", inode_num);
goto close_attr_bitmap;
}
if (NAttrNonResident(nar)) {
Vprintf ("Not resident $INDEX_ROOT\n");
Eprintf ("damaged fs: Not resident $INDEX_ROOT "
"(inode %lld)\n", inode_num);
goto close_attr_root;
}
indx_record_size = get_indx_record_size (nar);
if (!indx_record_size) {
Eprintf (" (inode %lld)\n", inode_num);
goto close_attr_root;
}
wiped = wipe_index_allocation (vol, byte, act,
naa, nab, indx_record_size);
if (wiped == -1) {
Eprintf (" (inode %lld)\n", inode_num);
goto close_attr_root;
}
if (wiped) {
Vprintf ("Wiped %llu bytes\n", wiped);
total += wiped;
} else
Vprintf ("Nothing to wipe\n");
close_attr_root:
ntfs_attr_close (nar);
close_attr_bitmap:
ntfs_attr_close (nab);
close_attr_allocation:
ntfs_attr_close (naa);
close_inode:
ntfs_inode_close (ni);
}
Qprintf ("wipe_directory 0x%02x, %lld bytes\n", byte, total);
return total;
}
/**
* wipe_logfile - Wipe the logfile (journal)
* @vol: An ntfs volume obtained from ntfs_mount
* @byte: Overwrite with this value
* @act: Wipe, test or info
*
* The logfile journals the metadata to give the volume fault-tolerance. If the
* volume is in a consistant state, then this information can be erased.
*
* Return: >0 Success, the clusters were wiped
* 0 Nothing to wipe
* -1 Error, something went wrong
*/
static s64 wipe_logfile (ntfs_volume *vol, int byte, enum action act)
{
const int NTFS_BUF_SIZE2 = 8192;
//FIXME(?): We might need to zero the LSN field of every single mft
//record as well. (But, first try without doing that and see what
//happens, since chkdsk might pickup the pieces and do it for us...)
ntfs_inode *ni;
ntfs_attr *na;
s64 len, pos, count;
char buf[NTFS_BUF_SIZE2];
int eo;
if (!vol || (byte < 0))
return -1;
//Qprintf ("wipe_logfile (not implemented) 0x%02x\n", byte);
if ((ni = ntfs_inode_open(vol, FILE_LogFile)) == NULL) {
Dprintf("Failed to open inode FILE_LogFile.\n");
return -1;
}
if ((na = ntfs_attr_open(ni, AT_DATA, AT_UNNAMED, 0)) == NULL) {
Dprintf("Failed to open $FILE_LogFile/$DATA\n");
goto error_exit;
}
/* The $DATA attribute of the $LogFile has to be non-resident. */
if (!NAttrNonResident(na)) {
Dprintf("$LogFile $DATA attribute is resident!?!\n");
errno = EIO;
goto io_error_exit;
}
/* Get length of $LogFile contents. */
len = na->data_size;
if (!len) {
Dprintf("$LogFile has zero length, no disk write needed.\n");
return 0;
}
/* Read $LogFile until its end. We do this as a check for correct
length thus making sure we are decompressing the mapping pairs
array correctly and hence writing below is safe as well. */
pos = 0;
while ((count = ntfs_attr_pread(na, pos, NTFS_BUF_SIZE2, buf)) > 0)
pos += count;
if (count == -1 || pos != len) {
Dprintf("Amount of $LogFile data read does not "
"correspond to expected length!");
if (count != -1)
errno = EIO;
goto io_error_exit;
}
/* Fill the buffer with @byte's. */
memset(buf, byte, NTFS_BUF_SIZE2);
/* Set the $DATA attribute. */
pos = 0;
while ((count = len - pos) > 0) {
if (count > NTFS_BUF_SIZE2)
count = NTFS_BUF_SIZE2;
if ((count = ntfs_attr_pwrite(na, pos, count, buf)) <= 0) {
Dprintf("Failed to set the $LogFile attribute value.");
if (count != -1)
errno = EIO;
goto io_error_exit;
}
pos += count;
}
ntfs_attr_close(na);
ntfs_inode_close(ni);
Qprintf ("wipe_logfile 0x%02x, %lld bytes\n", byte, pos);
return pos;
io_error_exit:
eo = errno;
ntfs_attr_close(na);
errno = eo;
error_exit:
eo = errno;
ntfs_inode_close(ni);
errno = eo;
return -1;
}
/**
* wipe_pagefile - Wipe the pagefile (swap space)
* @vol: An ntfs volume obtained from ntfs_mount
* @byte: Overwrite with this value
* @act: Wipe, test or info
*
* pagefile.sys is used by Windows as extra virtual memory (swap space).
* Windows recreates the file at bootup, so it can be wiped without harm.
*
* Return: >0 Success, the clusters were wiped
* 0 Nothing to wipe
* -1 Error, something went wrong
*/
static s64 wipe_pagefile (ntfs_volume *vol, int byte, enum action act)
{
// wipe completely, chkdsk doesn't do anything, booting writes header
const int NTFS_BUF_SIZE2 = 4096;
ntfs_inode *ni;
ntfs_attr *na;
s64 len, pos, count;
char buf[NTFS_BUF_SIZE2];
int eo;
if (!vol || (byte < 0))
return -1;
//Qprintf ("wipe_pagefile (not implemented) 0x%02x\n", byte);
ni = utils_pathname_to_inode (vol, NULL, "pagefile.sys");
if (!ni) {
Dprintf("Failed to open inode of pagefile.sys.\n");
return 0;
}
if ((na = ntfs_attr_open(ni, AT_DATA, AT_UNNAMED, 0)) == NULL) {
Dprintf("Failed to open pagefile.sys/$DATA\n");
goto error_exit;
}
/* The $DATA attribute of the pagefile.sys has to be non-resident. */
if (!NAttrNonResident(na)) {
Dprintf("pagefile.sys $DATA attribute is resident!?!\n");
errno = EIO;
goto io_error_exit;
}
/* Get length of pagfile.sys contents. */
len = na->data_size;
if (!len) {
Dprintf("pagefile.sys has zero length, no disk write needed.\n");
return 0;
}
memset(buf, byte, NTFS_BUF_SIZE2);
/* Set the $DATA attribute. */
pos = 0;
while ((count = len - pos) > 0) {
if (count > NTFS_BUF_SIZE2)
count = NTFS_BUF_SIZE2;
if ((count = ntfs_attr_pwrite(na, pos, count, buf)) <= 0) {
Dprintf("Failed to set the pagefile.sys attribute value.");
if (count != -1)
errno = EIO;
goto io_error_exit;
}
pos += count;
}
ntfs_attr_close(na);
ntfs_inode_close(ni);
Qprintf ("wipe_pagefile 0x%02x, %lld bytes\n", byte, pos);
return pos;
io_error_exit:
eo = errno;
ntfs_attr_close(na);
errno = eo;
error_exit:
eo = errno;
ntfs_inode_close(ni);
errno = eo;
return -1;
}
/**
* print_summary - Tell the user what we are about to do
*
* List the operations about to be performed. The output will be silenced by
* the --quiet option.
*
* Return: none
*/
static void print_summary (void)
{
int i;
if (opts.noaction)
Qprintf ("%s is in 'no-action' mode, it will NOT write to disk."
"\n\n", EXEC_NAME);
Qprintf ("%s is about to wipe:\n", EXEC_NAME);
if (opts.unused)
Qprintf ("\tunused disk space\n");
if (opts.tails)
Qprintf ("\tfile tails\n");
if (opts.mft)
Qprintf ("\tunused mft areas\n");
if (opts.directory)
Qprintf ("\tunused directory index space\n");
if (opts.logfile)
Qprintf ("\tthe logfile (journal)\n");
if (opts.pagefile)
Qprintf ("\tthe pagefile (swap space)\n");
Qprintf ("\n%s will overwrite these areas with: ", EXEC_NAME);
if (opts.bytes) {
for (i = 0; opts.bytes[i] >= 0; i++)
Qprintf ("0x%02x ", opts.bytes[i]);
}
Qprintf ("\n");
if (opts.count > 1)
Qprintf ("%s will repeat these operations %d times.\n", EXEC_NAME, opts.count);
Qprintf ("\n");
}
/**
* main - Begin here
*
* Start from here.
*
* Return: 0 Success, the program worked
* 1 Error, something went wrong
*/
int main (int argc, char *argv[])
{
ntfs_volume *vol;
int result = 1;
int flags = 0;
int i, j;
enum action act = act_info;
if (!parse_options (argc, argv))
return 1;
utils_set_locale();
if (!opts.info)
print_summary();
if (opts.info || opts.noaction)
flags = MS_RDONLY;
vol = utils_mount_volume (opts.device, flags, opts.force);
if (!vol)
goto free;
if ((vol->flags & VOLUME_IS_DIRTY) && (!opts.force))
goto umount;
if (opts.info) {
act = act_info;
opts.count = 1;
} else if (opts.noaction) {
act = act_test;
} else {
act = act_wipe;
}
/* Even if the output it quieted, you still get 5 seconds to abort. */
if ((act == act_wipe) && !opts.force) {
Qprintf ("\n%s will begin in 5 seconds, press CTRL-C to abort.\n", EXEC_NAME);
sleep (5);
}
printf ("\n");
for (i = 0; i < opts.count; i++) {
int byte;
s64 total = 0;
s64 wiped = 0;
for (j = 0; byte = opts.bytes[j], byte >= 0; j++) {
if (opts.directory) {
wiped = wipe_directory (vol, byte, act);
if (wiped < 0)
goto umount;
else
total += wiped;
}
if (opts.tails) {
wiped = wipe_tails (vol, byte, act);
if (wiped < 0)
goto umount;
else
total += wiped;
}
if (opts.logfile) {
wiped = wipe_logfile (vol, byte, act);
if (wiped < 0)
goto umount;
else
total += wiped;
}
if (opts.mft) {
wiped = wipe_mft (vol, byte, act);
if (wiped < 0)
goto umount;
else
total += wiped;
}
if (opts.pagefile) {
wiped = wipe_pagefile (vol, byte, act);
if (wiped < 0)
goto umount;
else
total += wiped;
}
if (opts.unused) {
wiped = wipe_unused (vol, byte, act);
if (wiped < 0)
goto umount;
else
total += wiped;
}
if (act == act_info)
break;
}
printf ("%lld bytes were wiped\n", (long long)total);
}
/*
* We need to reset the logfile so Windows can boot and so journal
* replay does not cause corruption.
*/
if (act != act_info) {
printf ("Resetting logfile.\n");
ntfs_logfile_reset(vol);
}
if (ntfs_volume_set_flags (vol, VOLUME_IS_DIRTY) < 0) {
Eprintf ("Couldn't mark volume dirty\n");
}
result = 0;
umount:
ntfs_umount (vol, FALSE);
free:
if (opts.bytes)
free (opts.bytes);
return result;
}