From 24c9f19065bff353f2314e52d94cfc200a456773 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Pierre=20Andr=C3=A9?= Date: Mon, 18 Jun 2012 12:41:51 +0200 Subject: [PATCH] Added optional wiping of file tails and undelete data to ntfswipe (Bogdan) Added more wiping options to ntfswipe (to avoid leaking of deleted data). --- configure.ac | 4 +- ntfsprogs/ntfswipe.c | 663 ++++++++++++++++++++++++++++++++++++++++++- ntfsprogs/ntfswipe.h | 1 + 3 files changed, 654 insertions(+), 14 deletions(-) diff --git a/configure.ac b/configure.ac index e818ca9d..d887a4b2 100644 --- a/configure.ac +++ b/configure.ac @@ -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 diff --git a/ntfsprogs/ntfswipe.c b/ntfsprogs/ntfswipe.c index d4a30db3..4a26eca0 100644 --- a/ntfsprogs/ntfswipe.c +++ b/ntfsprogs/ntfswipe.c @@ -42,10 +42,17 @@ #endif #ifdef HAVE_STDLIB_H #include +#else +#ifdef HAVE_MALLOC_H +#include +#endif #endif #ifdef HAVE_UNISTD_H #include #endif +#ifdef HAVE_TIME_H +#include +#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; jname)); + 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: diff --git a/ntfsprogs/ntfswipe.h b/ntfsprogs/ntfswipe.h index d9de8017..d22c0854 100644 --- a/ntfsprogs/ntfswipe.h +++ b/ntfsprogs/ntfswipe.h @@ -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_ */