Added optional wiping of file tails and undelete data to ntfswipe (Bogdan)

Added more wiping options to ntfswipe (to avoid leaking of deleted data).
edge.strict_endians
Jean-Pierre André 2012-06-18 12:41:51 +02:00
parent 8f896f112a
commit 24c9f19065
3 changed files with 654 additions and 14 deletions

View File

@ -370,7 +370,7 @@ AC_CHECK_HEADERS([ctype.h fcntl.h libgen.h libintl.h limits.h locale.h \
endian.h byteswap.h sys/byteorder.h sys/disk.h sys/endian.h \
sys/param.h sys/ioctl.h sys/mkdev.h sys/mount.h sys/stat.h sys/types.h \
sys/vfs.h sys/statvfs.h sys/sysmacros.h linux/major.h linux/fd.h \
linux/hdreg.h machine/endian.h windows.h syslog.h pwd.h])
linux/hdreg.h machine/endian.h windows.h syslog.h pwd.h malloc.h])
# Checks for typedefs, structures, and compiler characteristics.
AC_HEADER_STDBOOL
@ -428,7 +428,7 @@ AC_CHECK_FUNCS([ \
atexit basename daemon dup2 fdatasync ffs getopt_long hasmntopt \
mbsinit memmove memset realpath regcomp setlocale setxattr \
strcasecmp strchr strdup strerror strnlen strsep strtol strtoul \
sysconf utime utimensat gettimeofday clock_gettime fork \
sysconf utime utimensat gettimeofday clock_gettime fork memcpy random snprintf \
])
AC_SYS_LARGEFILE

View File

@ -42,10 +42,17 @@
#endif
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#else
#ifdef HAVE_MALLOC_H
#include <malloc.h>
#endif
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_TIME_H
#include <time.h>
#endif
#include "ntfswipe.h"
#include "types.h"
@ -56,9 +63,77 @@
#include "mst.h"
/* #include "version.h" */
#include "logging.h"
#include "list.h"
#include "mft.h"
static const char *EXEC_NAME = "ntfswipe";
static struct options opts;
static unsigned long int npasses = 0;
struct filename {
char *parent_name;
struct ntfs_list_head list; /* Previous/Next links */
ntfschar *uname; /* Filename in unicode */
int uname_len; /* and its length */
/* Allocated size (multiple of cluster size) */
s64 size_alloc;
s64 size_data; /* Actual size of data */
long long parent_mref;
FILE_ATTR_FLAGS flags;
time_t date_c; /* Time created */
time_t date_a; /* altered */
time_t date_m; /* mft record changed */
time_t date_r; /* read */
char *name; /* Filename in current locale */
FILE_NAME_TYPE_FLAGS name_space;
char padding[7]; /* Unused: padding to 64 bit. */
};
struct data {
struct ntfs_list_head list; /* Previous/Next links */
char *name; /* Stream name in current locale */
ntfschar *uname; /* Unicode stream name */
int uname_len; /* and its length */
int resident; /* Stream is resident */
int compressed; /* Stream is compressed */
int encrypted; /* Stream is encrypted */
/* Allocated size (multiple of cluster size) */
s64 size_alloc;
s64 size_data; /* Actual size of data */
/* Initialised size, may be less than data size */
s64 size_init;
VCN size_vcn; /* Highest VCN in the data runs */
runlist_element *runlist; /* Decoded data runs */
int percent; /* Amount potentially recoverable */
void *data; /* If resident, a pointer to the data */
char padding[4]; /* Unused: padding to 64 bit. */
};
struct ufile {
s64 inode; /* MFT record number */
time_t date; /* Last modification date/time */
struct ntfs_list_head name; /* A list of filenames */
struct ntfs_list_head data; /* A list of data streams */
char *pref_name; /* Preferred filename */
char *pref_pname; /* parent filename */
s64 max_size; /* Largest size we find */
int attr_list; /* MFT record may be one of many */
int directory; /* MFT record represents a directory */
MFT_RECORD *mft; /* Raw MFT record */
char padding[4]; /* Unused: padding to 64 bit. */
};
#define NPAT 22
/* Taken from `shred' source */
static const unsigned int patterns[NPAT] = {
0x000, 0xFFF, /* 1-bit */
0x555, 0xAAA, /* 2-bit */
0x249, 0x492, 0x6DB, 0x924, 0xB6D, 0xDB6, /* 3-bit */
0x111, 0x222, 0x333, 0x444, 0x666, 0x777,
0x888, 0x999, 0xBBB, 0xCCC, 0xDDD, 0xEEE /* 4-bit */
};
/**
* version - Print version information about the program
@ -94,6 +169,7 @@ static void usage(void)
" -p --pagefile Wipe pagefile (swap space)\n"
" -t --tails Wipe file tails\n"
" -u --unused Wipe unused clusters\n"
" -s --undel Wipe undelete data\n"
"\n"
" -a --all Wipe all unused space\n"
"\n"
@ -186,7 +262,7 @@ static int parse_list(char *list, int **result)
*/
static int parse_options(int argc, char *argv[])
{
static const char *sopt = "-ab:c:dfh?ilmnpqtuvV";
static const char *sopt = "-ab:c:dfh?ilmnpqtuvVs";
static struct option lopt[] = {
{ "all", no_argument, NULL, 'a' },
{ "bytes", required_argument, NULL, 'b' },
@ -203,6 +279,7 @@ static int parse_options(int argc, char *argv[])
{ "quiet", no_argument, NULL, 'q' },
{ "tails", no_argument, NULL, 't' },
{ "unused", no_argument, NULL, 'u' },
{ "undel", no_argument, NULL, 's' },
{ "verbose", no_argument, NULL, 'v' },
{ "version", no_argument, NULL, 'V' },
{ NULL, 0, NULL, 0 }
@ -239,6 +316,7 @@ static int parse_options(int argc, char *argv[])
opts.pagefile++;
opts.tails++;
opts.unused++;
opts.undel++;
break;
case 'b':
if (!opts.bytes) {
@ -289,6 +367,9 @@ static int parse_options(int argc, char *argv[])
opts.quiet++;
ntfs_log_clear_levels(NTFS_LOG_LEVEL_QUIET);
break;
case 's':
opts.undel++;
break;
case 't':
opts.tails++;
break;
@ -359,7 +440,8 @@ static int parse_options(int argc, char *argv[])
}
if (!opts.directory && !opts.logfile && !opts.mft &&
!opts.pagefile && !opts.tails && !opts.unused) {
!opts.pagefile && !opts.tails && !opts.unused &&
!opts.undel) {
opts.info = 1;
}
}
@ -443,10 +525,12 @@ static s64 wipe_compressed_attribute(ntfs_volume *vol, int byte,
{
unsigned char *buf;
s64 size, offset, ret, wiped = 0;
le16 block_size_le;
u16 block_size;
VCN cur_vcn = 0;
runlist *rlc = na->rl;
runlist_element *rlc = na->rl;
s64 cu_mask = na->compression_block_clusters - 1;
runlist_element *restart = na->rl;
while (rlc->length) {
cur_vcn += rlc->length;
@ -457,10 +541,11 @@ static s64 wipe_compressed_attribute(ntfs_volume *vol, int byte,
}
if (rlc->lcn == LCN_HOLE) {
runlist *rlt;
runlist_element *rlt;
offset = cur_vcn - rlc->length;
if (offset == (offset & (~cu_mask))) {
restart = rlc + 1;
rlc++;
continue;
}
@ -469,9 +554,11 @@ static s64 wipe_compressed_attribute(ntfs_volume *vol, int byte,
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);
ret = ntfs_rl_pread(vol, restart,
offset - (restart->vcn
<< vol->cluster_size_bits),
2, &block_size_le);
block_size = le16_to_cpu(block_size_le);
if (ret != 2) {
ntfs_log_verbose("Internal error\n");
ntfs_log_error("ntfs_rl_pread failed");
@ -504,6 +591,8 @@ static s64 wipe_compressed_attribute(ntfs_volume *vol, int byte,
if ((act == act_info) || (!size)) {
wiped += size;
if (rlc->lcn == LCN_HOLE)
restart = rlc + 1;
rlc++;
continue;
}
@ -518,7 +607,9 @@ static s64 wipe_compressed_attribute(ntfs_volume *vol, int byte,
}
memset(buf, byte, size);
ret = ntfs_rl_pwrite(vol, na->rl, 0, offset, size, buf);
ret = ntfs_rl_pwrite(vol, restart,
restart->vcn << vol->cluster_size_bits,
offset, size, buf);
free(buf);
if (ret != size) {
ntfs_log_verbose("Internal error\n");
@ -530,6 +621,8 @@ static s64 wipe_compressed_attribute(ntfs_volume *vol, int byte,
}
wiped += ret;
next:
if (rlc->lcn == LCN_HOLE)
restart = rlc + 1;
rlc++;
}
@ -694,7 +787,7 @@ static s64 wipe_mft(ntfs_volume *vol, int byte, enum action act)
if (!vol || (byte < 0))
return -1;
rec = malloc(vol->mft_record_size);
rec = (MFT_RECORD*)malloc(vol->mft_record_size);
if (!rec) {
ntfs_log_error("malloc failed\n");
return -1;
@ -932,14 +1025,15 @@ free_bitmap:
static u32 get_indx_record_size(ntfs_attr *nar)
{
u32 indx_record_size;
le32 indx_record_size_le;
if (ntfs_attr_pread(nar, 8, 4, &indx_record_size) != 4) {
if (ntfs_attr_pread(nar, 8, 4, &indx_record_size_le) != 4) {
ntfs_log_verbose("Couldn't determine size of INDX record\n");
ntfs_log_error("ntfs_attr_pread failed");
return 0;
}
indx_record_size = le32_to_cpu(indx_record_size);
indx_record_size = le32_to_cpu(indx_record_size_le);
if (!indx_record_size) {
ntfs_log_verbose("Internal error\n");
ntfs_log_error("INDX record should be 0");
@ -1282,6 +1376,525 @@ error_exit:
return -1;
}
/**
* Part of ntfsprogs.
* Modified: removed logging, signal handling, removed data.
*
* free_file - Release the resources used by a file object
* \param file The unwanted file object
*
* This will free up the memory used by a file object and iterate through the
* object's children, freeing their resources too.
*
* \return none
*/
static void free_file (struct ufile *file)
{
struct ntfs_list_head *item = NULL, *tmp = NULL;
struct filename *f = NULL;
struct data *d = NULL;
if (file == NULL)
return;
ntfs_list_for_each_safe(item, tmp, &(file->name)) {
/* List of filenames */
f = ntfs_list_entry(item, struct filename, list);
if (f->name != NULL)
free(f->name);
if (f->parent_name != NULL) {
free(f->parent_name);
}
free(f);
}
ntfs_list_for_each_safe(item, tmp, &(file->data)) {
/* List of data streams */
d = ntfs_list_entry(item, struct data, list);
if (d->name != NULL)
free(d->name);
if (d->runlist != NULL)
free(d->runlist);
free(d);
}
free(file->mft);
free(file);
}
/**
* Fills the given buffer with one of predefined patterns.
* \param pat_no Pass number.
* \param buffer Buffer to be filled.
* \param buflen Length of the buffer.
*/
static void fill_buffer (
unsigned long int pat_no,
unsigned char * const buffer,
const size_t buflen,
int * const selected )
/*@requires notnull buffer @*/ /*@sets *buffer @*/
{
size_t i;
#if (!defined HAVE_MEMCPY) && (!defined HAVE_STRING_H)
size_t j;
#endif
unsigned int bits;
if ((buffer == NULL) || (buflen == 0))
return;
/* De-select all patterns once every npasses calls. */
if (pat_no % npasses == 0) {
for (i = 0; i < NPAT; i++) {
selected[i] = 0;
}
}
pat_no %= npasses;
/* double check for npasses >= NPAT + 3: */
for (i = 0; i < NPAT; i++) {
if (selected[i] == 0)
break;
}
if (i >= NPAT) {
for (i = 0; i < NPAT; i++) {
selected[i] = 0;
}
}
/* The first, last and middle passess will be using a random pattern */
if ((pat_no == 0) || (pat_no == npasses-1) || (pat_no == npasses/2)) {
#if (!defined __STRICT_ANSI__) && (defined HAVE_RANDOM)
bits = (unsigned int)(random() & 0xFFF);
#else
bits = (unsigned int)(rand() & 0xFFF);
#endif
} else {
/* For other passes, one of the fixed patterns is selected. */
do {
#if (!defined __STRICT_ANSI__) && (defined HAVE_RANDOM)
i = (size_t)(random() % NPAT);
#else
i = (size_t)(rand() % NPAT);
#endif
} while (selected[i] == 1);
bits = opts.bytes[i];
selected[i] = 1;
}
buffer[0] = (unsigned char) bits;
buffer[1] = (unsigned char) bits;
buffer[2] = (unsigned char) bits;
for (i = 3; i < buflen / 2; i *= 2) {
#ifdef HAVE_MEMCPY
memcpy(buffer + i, buffer, i);
#elif defined HAVE_STRING_H
strncpy((char *)(buffer + i), (char *)buffer, i);
#else
for (j = 0; j < i; j++) {
buffer[i+j] = buffer[j];
}
#endif
}
if (i < buflen) {
#ifdef HAVE_MEMCPY
memcpy(buffer + i, buffer, buflen - i);
#elif defined HAVE_STRING_H
strncpy((char *)(buffer + i), (char *)buffer, buflen - i);
#else
for (j=0; j<buflen - i; j++) {
buffer[i+j] = buffer[j];
}
#endif
}
}
/**
* Destroys the specified record's filenames and data.
*
* \param nv The filesystem.
* \param record The record (i-node number), which filenames & data
* to destroy.
* \return 0 in case of no errors, other values otherwise.
*/
static int destroy_record(ntfs_volume *nv, const s64 record,
unsigned char * const buf)
{
struct ufile *file = NULL;
runlist_element *rl = NULL;
ntfs_attr *mft = NULL;
ntfs_attr_search_ctx *ctx = NULL;
int ret_wfs = 0;
unsigned long int pass, i;
s64 j;
unsigned char * a_offset;
int selected[NPAT];
file = (struct ufile *) malloc(sizeof(struct ufile));
if (file == NULL) {
return -1;
}
NTFS_INIT_LIST_HEAD(&(file->name));
NTFS_INIT_LIST_HEAD(&(file->data));
file->inode = record;
file->mft = (MFT_RECORD*)malloc(nv->mft_record_size);
if (file->mft == NULL) {
free_file (file);
return -1;
}
mft = ntfs_attr_open(nv->mft_ni, AT_DATA, AT_UNNAMED, 0);
if (mft == NULL) {
free_file(file);
return -2;
}
/* Read the MFT reocrd of the i-node */
if (ntfs_attr_mst_pread(mft, nv->mft_record_size * record, 1LL,
nv->mft_record_size, file->mft) < 1) {
ntfs_attr_close(mft);
free_file(file);
return -3;
}
ntfs_attr_close(mft);
mft = NULL;
ctx = ntfs_attr_get_search_ctx(NULL, file->mft);
if (ctx == NULL) {
free_file(file);
return -4;
}
/* Wiping file names */
while (1 == 1) {
if (ntfs_attr_lookup(AT_FILE_NAME, NULL, 0, CASE_SENSITIVE,
0LL, NULL, 0, ctx) != 0) {
break; /* None / no more of that type */
}
if (ctx->attr == NULL)
break;
/* We know this will always be resident.
Find the offset of the data, including the MFT record. */
a_offset = ((unsigned char *) ctx->attr
+ le16_to_cpu(ctx->attr->value_offset));
for (pass = 0; pass < npasses; pass++) {
fill_buffer(pass, a_offset,
le32_to_cpu(ctx->attr->value_length),
selected);
if ( !opts.noaction ) {
if (ntfs_mft_records_write(nv,
MK_MREF(record, 0), 1LL,
ctx->mrec) != 0) {
ret_wfs = -5;
break;
}
/* Flush after each writing, if more than
1 overwriting needs to be done. Allow I/O
bufferring (efficiency), if just one
pass is needed. */
if (npasses > 1) {
nv->dev->d_ops->sync(nv->dev);
}
}
}
/* Wiping file name length */
for (pass = 0; pass < npasses; pass++) {
fill_buffer (pass, (unsigned char *)
&(ctx->attr->value_length), sizeof(u32),
selected);
if (!opts.noaction) {
if (ntfs_mft_records_write(nv,
MK_MREF(record, 0),
1LL, ctx->mrec) != 0) {
ret_wfs = -5;
break;
}
if (npasses > 1) {
nv->dev->d_ops->sync(nv->dev);
}
}
}
ctx->attr->value_length = cpu_to_le32(0);
if (!opts.noaction) {
if (ntfs_mft_records_write(nv, MK_MREF(record, 0),
1LL, ctx->mrec) != 0) {
ret_wfs = -5;
break;
}
}
}
ntfs_attr_reinit_search_ctx(ctx);
/* Wiping file data */
while (1 == 1) {
if (ntfs_attr_lookup(AT_DATA, NULL, 0, CASE_SENSITIVE, 0LL,
NULL, 0, ctx) != 0) {
break; /* None / no more of that type */
}
if (ctx->attr == NULL)
break;
if (ctx->attr->non_resident == 0) {
/* attribute is resident (part of MFT record) */
/* find the offset of the data, including the MFT record */
a_offset = ((unsigned char *) ctx->attr
+ le16_to_cpu(ctx->attr->value_offset));
/* Wiping the data itself */
for (pass = 0; pass < npasses; pass++) {
fill_buffer (pass, a_offset,
le32_to_cpu(ctx->attr->value_length),
selected);
if (!opts.noaction) {
if (ntfs_mft_records_write(nv,
MK_MREF(record, 0),
1LL, ctx->mrec) != 0) {
ret_wfs = -5;
break;
}
if (npasses > 1) {
nv->dev->d_ops->sync(nv->dev);
}
}
}
/* Wiping data length */
for (pass = 0; pass < npasses; pass++) {
fill_buffer(pass, (unsigned char *)
&(ctx->attr->value_length),
sizeof(u32), selected);
if (!opts.noaction) {
if (ntfs_mft_records_write(nv,
MK_MREF(record, 0),
1LL, ctx->mrec) != 0) {
ret_wfs = -5;
break;
}
if (npasses > 1) {
nv->dev->d_ops->sync(nv->dev);
}
}
}
ctx->attr->value_length = cpu_to_le32(0);
if ( !opts.noaction ) {
if (ntfs_mft_records_write(nv,
MK_MREF(record, 0),
1LL, ctx->mrec) != 0) {
ret_wfs = -5;
break;
}
}
} else {
/* Non-resident here */
rl = ntfs_mapping_pairs_decompress(nv,
ctx->attr, NULL);
if (rl == NULL) {
continue;
}
if (rl[0].length <= 0) {
continue;
}
for (i = 0; (rl[i].length > 0) && (ret_wfs == 0); i++) {
if (rl[i].lcn == -1) {
continue;
}
for (j = rl[i].lcn;
(j < rl[i].lcn + rl[i].length)
&& (ret_wfs == 0); j++) {
if (utils_cluster_in_use(nv, j) != 0)
continue;
for (pass = 0;
pass < npasses;
pass++) {
fill_buffer(pass, buf,
(size_t) nv->cluster_size,
selected);
if (!opts.noaction) {
if (ntfs_cluster_write(
nv, j, 1LL,
buf) < 1) {
ret_wfs = -5;
break;
}
if (npasses > 1) {
nv->dev->d_ops->sync
(nv->dev);
}
}
}
}
}
/* Wipe the data length here */
for (pass = 0; pass < npasses; pass++) {
fill_buffer(pass, (unsigned char *)
&(ctx->attr->lowest_vcn),
sizeof(VCN), selected);
fill_buffer(pass, (unsigned char *)
&(ctx->attr->highest_vcn),
sizeof(VCN), selected);
fill_buffer(pass, (unsigned char *)
&(ctx->attr->allocated_size),
sizeof(s64), selected);
fill_buffer(pass, (unsigned char *)
&(ctx->attr->data_size),
sizeof(s64), selected);
fill_buffer(pass, (unsigned char *)
&(ctx->attr->initialized_size),
sizeof(s64), selected);
fill_buffer(pass, (unsigned char *)
&(ctx->attr->compressed_size),
sizeof(s64), selected);
if ( !opts.noaction ) {
if (ntfs_mft_records_write(nv,
MK_MREF (record, 0),
1LL, ctx->mrec) != 0) {
ret_wfs = -5;
break;
}
if (npasses > 1) {
nv->dev->d_ops->sync(nv->dev);
}
}
}
ctx->attr->lowest_vcn = cpu_to_le64(0);
ctx->attr->highest_vcn = cpu_to_le64(0);
ctx->attr->allocated_size = cpu_to_le64(0);
ctx->attr->data_size = cpu_to_le64(0);
ctx->attr->initialized_size = cpu_to_le64(0);
ctx->attr->compressed_size = cpu_to_le64(0);
if (!opts.noaction) {
if (ntfs_mft_records_write(nv,
MK_MREF (record, 0),
1LL, ctx->mrec) != 0) {
ret_wfs = -5;
break;
}
}
} /* end of resident check */
} /* end of 'wiping file data' loop */
ntfs_attr_put_search_ctx(ctx);
free_file(file);
return ret_wfs;
}
/**
* Starts search for deleted inodes and undelete data on the given
* NTFS filesystem.
* \param FS The filesystem.
* \return 0 in case of no errors, other values otherwise.
*/
static int wipe_unrm(ntfs_volume *nv)
{
int ret_wfs = 0, ret;
ntfs_attr *bitmapattr = NULL;
s64 bmpsize, size, nr_mft_records, i, j, k;
unsigned char b;
unsigned char * buf = NULL;
#define MYBUF_SIZE 8192
unsigned char *mybuf;
#define MINIM(x, y) ( ((x)<(y))?(x):(y) )
mybuf = (unsigned char *) malloc(MYBUF_SIZE);
if (mybuf == NULL) {
return -1;
}
buf = (unsigned char *) malloc(nv->cluster_size);
if (buf == NULL) {
free (mybuf);
return -1;
}
bitmapattr = ntfs_attr_open(nv->mft_ni, AT_BITMAP, AT_UNNAMED, 0);
if (bitmapattr == NULL) {
free (buf);
free (mybuf);
return -2;
}
bmpsize = bitmapattr->initialized_size;
nr_mft_records = nv->mft_na->initialized_size
>> nv->mft_record_size_bits;
/* just like ntfsundelete; detects i-node numbers fine */
for (i = 0; (i < bmpsize) && (ret_wfs==0); i += MYBUF_SIZE) {
/* read a part of the file bitmap */
size = ntfs_attr_pread(bitmapattr, i,
MINIM((bmpsize - i), MYBUF_SIZE), mybuf);
if (size < 0)
break;
/* parse each byte of the just-read part of the bitmap */
for (j = 0; (j < size) && (ret_wfs==0); j++) {
b = mybuf[j];
/* parse each bit of the byte Bit 1 means 'in use'. */
for (k = 0; (k < CHAR_BIT) && (ret_wfs==0);
k++, b>>=1) {
/* (i+j)*8+k is the i-node bit number */
if (((i+j)*CHAR_BIT+k) >= nr_mft_records) {
goto done;
}
if ((b & 1) != 0) {
/* i-node is in use, skip it */
continue;
}
/* wiping the i-node here: */
ret = destroy_record (nv,
(i+j)*CHAR_BIT+k, buf);
if (ret != 0) {
ret_wfs = ret;
}
}
}
}
done:
ntfs_attr_close(bitmapattr);
free(buf);
free(mybuf);
ntfs_log_quiet("wipe_undelete\n");
return ret_wfs;
}
/**
* print_summary - Tell the user what we are about to do
*
@ -1311,6 +1924,8 @@ static void print_summary(void)
ntfs_log_quiet("\tthe logfile (journal)\n");
if (opts.pagefile)
ntfs_log_quiet("\tthe pagefile (swap space)\n");
if (opts.undel)
ntfs_log_quiet("\tundelete data\n");
ntfs_log_quiet("\n%s will overwrite these areas with: ", EXEC_NAME);
if (opts.bytes) {
@ -1377,6 +1992,18 @@ int main(int argc, char *argv[])
sleep(5);
}
for (i = 0; opts.bytes[i] >= 0; i++) {
npasses = i+1;
}
if (npasses == 0) {
npasses = opts.count;
}
#ifdef HAVE_TIME_H
srandom(time(NULL));
#else
/* use a pointer as a pseudorandom value */
srandom((int)vol + npasses);
#endif
ntfs_log_info("\n");
for (i = 0; i < opts.count; i++) {
int byte;
@ -1433,11 +2060,23 @@ int main(int argc, char *argv[])
total += wiped;
}
if (opts.undel) {
wiped = wipe_unrm(vol);
if (wiped != 0)
goto umount;
/*
else
total += wiped;
*/
}
if (act == act_info)
break;
}
ntfs_log_info("%lld bytes were wiped\n", (long long)total);
ntfs_log_info(
"%lld bytes were wiped (excluding undelete data)\n",
(long long)total);
}
result = 0;
umount:

View File

@ -47,6 +47,7 @@ struct options {
int pagefile; /* Wipe pagefile (swap space) */
int tails; /* Wipe file tails */
int unused; /* Wipe unused clusters */
int undel; /* Wipe undelete data */
};
#endif /* _NTFSWIPE_H_ */