mirror of
https://git.code.sf.net/p/ntfs-3g/ntfs-3g.git
synced 2024-11-27 03:53:48 +08:00
2290 lines
55 KiB
C
2290 lines
55 KiB
C
/**
|
|
* ntfswipe - Part of the Linux-NTFS project.
|
|
*
|
|
* Copyright (c) 2005 Anton Altaparmakov
|
|
* Copyright (c) 2002-2005 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"
|
|
|
|
#ifdef HAVE_STDIO_H
|
|
#include <stdio.h>
|
|
#endif
|
|
#ifdef HAVE_ERRNO_H
|
|
#include <errno.h>
|
|
#endif
|
|
#ifdef HAVE_STDARG_H
|
|
#include <stdarg.h>
|
|
#endif
|
|
#ifdef HAVE_GETOPT_H
|
|
#include <getopt.h>
|
|
#endif
|
|
#ifdef HAVE_STRING_H
|
|
#include <string.h>
|
|
#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
|
|
#ifdef HAVE_LIMITS_H
|
|
#include <limits.h>
|
|
#endif
|
|
|
|
#include "ntfswipe.h"
|
|
#include "types.h"
|
|
#include "volume.h"
|
|
#include "utils.h"
|
|
#include "debug.h"
|
|
#include "dir.h"
|
|
#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
|
|
*
|
|
* Print a copyright statement and a brief description of the program.
|
|
*
|
|
* Return: none
|
|
*/
|
|
static void version(void)
|
|
{
|
|
ntfs_log_info("\n%s v%s (libntfs-3g) - Overwrite the unused space on an NTFS "
|
|
"Volume.\n\n", EXEC_NAME, VERSION);
|
|
ntfs_log_info("Copyright (c) 2002-2005 Richard Russon\n");
|
|
ntfs_log_info("Copyright (c) 2004 Yura Pakhuchiy\n");
|
|
ntfs_log_info("\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)
|
|
{
|
|
ntfs_log_info("\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"
|
|
" -U --unused-fast Wipe unused clusters (fast)\n"
|
|
" -s --undel Wipe undelete data\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);
|
|
ntfs_log_info("%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) {
|
|
ntfs_log_error("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))) {
|
|
ntfs_log_error("Invalid list '%s'\n", list);
|
|
free(mem);
|
|
return 0;
|
|
}
|
|
|
|
if ((mem[i] < 0) || (mem[i] > 255)) {
|
|
ntfs_log_error("Bytes must be in range 0-255.\n");
|
|
free(mem);
|
|
return 0;
|
|
}
|
|
|
|
ptr = end + 1;
|
|
}
|
|
|
|
ntfs_log_debug("Parsing list '%s' - ", list);
|
|
for (i = 0; i <= count; i++)
|
|
ntfs_log_debug("0x%02x ", mem[i]);
|
|
ntfs_log_debug("\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?ilmnpqtuUvVs";
|
|
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' },
|
|
{ "unused-fast",no_argument, NULL, 'U' },
|
|
{ "undel", no_argument, NULL, 's' },
|
|
{ "verbose", no_argument, NULL, 'v' },
|
|
{ "version", no_argument, NULL, 'V' },
|
|
{ NULL, 0, NULL, 0 }
|
|
};
|
|
|
|
int c = -1;
|
|
char *end;
|
|
int err = 0;
|
|
int ver = 0;
|
|
int help = 0;
|
|
int levels = 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++;
|
|
opts.undel++;
|
|
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':
|
|
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++;
|
|
ntfs_log_clear_levels(NTFS_LOG_LEVEL_QUIET);
|
|
break;
|
|
case 's':
|
|
opts.undel++;
|
|
break;
|
|
case 't':
|
|
opts.tails++;
|
|
break;
|
|
case 'u':
|
|
opts.unused++;
|
|
break;
|
|
case 'U':
|
|
opts.unused_fast++;
|
|
break;
|
|
case 'v':
|
|
opts.verbose++;
|
|
ntfs_log_set_levels(NTFS_LOG_LEVEL_VERBOSE);
|
|
break;
|
|
case 'V':
|
|
ver++;
|
|
break;
|
|
case '?':
|
|
if (strncmp (argv[optind-1], "--log-", 6) == 0) {
|
|
if (!ntfs_log_parse_option (argv[optind-1]))
|
|
err++;
|
|
break;
|
|
}
|
|
/* fall through */
|
|
default:
|
|
if ((optopt == 'b') || (optopt == 'c')) {
|
|
ntfs_log_error("Option '%s' requires an argument.\n", argv[optind-1]);
|
|
} else {
|
|
ntfs_log_error("Unknown option '%s'.\n", argv[optind-1]);
|
|
}
|
|
err++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Make sure we're in sync with the log levels */
|
|
levels = ntfs_log_get_levels();
|
|
if (levels & NTFS_LOG_LEVEL_VERBOSE)
|
|
opts.verbose++;
|
|
if (!(levels & NTFS_LOG_LEVEL_QUIET))
|
|
opts.quiet++;
|
|
|
|
if (help || ver) {
|
|
opts.quiet = 0;
|
|
} else {
|
|
if (opts.device == NULL) {
|
|
if (argc > 1)
|
|
ntfs_log_error("You must specify exactly one device.\n");
|
|
err++;
|
|
}
|
|
|
|
if (opts.quiet && opts.verbose) {
|
|
ntfs_log_error("You may not use --quiet and --verbose at the same time.\n");
|
|
err++;
|
|
}
|
|
|
|
/*
|
|
if (opts.info && (opts.unused || opts.tails || opts.mft || opts.directory)) {
|
|
ntfs_log_error("You may not use any other options with --info.\n");
|
|
err++;
|
|
}
|
|
*/
|
|
|
|
if ((opts.count < 1) || (opts.count > 100)) {
|
|
ntfs_log_error("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 {
|
|
ntfs_log_error("Couldn't allocate memory for byte list.\n");
|
|
err++;
|
|
}
|
|
}
|
|
|
|
if (!opts.directory && !opts.logfile && !opts.mft &&
|
|
!opts.pagefile && !opts.tails && !opts.unused &&
|
|
!opts.unused_fast && !opts.undel) {
|
|
opts.info = 1;
|
|
}
|
|
}
|
|
|
|
if (ver)
|
|
version();
|
|
if (help || err)
|
|
usage();
|
|
|
|
/* tri-state 0 : done, 1 : error, -1 : proceed */
|
|
return (err ? 1 : (help || ver ? 0 : -1));
|
|
}
|
|
|
|
/**
|
|
* 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 attribute 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) {
|
|
ntfs_log_error("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)) {
|
|
//ntfs_log_verbose("cluster %lld is in use\n", i);
|
|
continue;
|
|
}
|
|
|
|
if (act == act_wipe) {
|
|
//ntfs_log_verbose("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) {
|
|
ntfs_log_error("write failed\n");
|
|
goto free;
|
|
}
|
|
}
|
|
|
|
total += vol->cluster_size;
|
|
}
|
|
|
|
ntfs_log_quiet("wipe_unused 0x%02x, %lld bytes\n", byte, (long long)total);
|
|
free:
|
|
free(buffer);
|
|
return total;
|
|
}
|
|
|
|
/**
|
|
* wipe_unused_fast - Faster 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.
|
|
*
|
|
* - read/write on a block basis (64 clusters, arbitrary)
|
|
* - skip of fully used block
|
|
* - skip non-used block already wiped
|
|
*
|
|
* Return: >0 Success, the attribute was wiped
|
|
* 0 Nothing to wipe
|
|
* -1 Error, something went wrong
|
|
*/
|
|
static s64 wipe_unused_fast(ntfs_volume *vol, int byte, enum action act)
|
|
{
|
|
s64 i;
|
|
s64 total = 0;
|
|
s64 unused = 0;
|
|
s64 result;
|
|
u8 *buffer;
|
|
u8 *big_buffer;
|
|
u32 *u32_buffer;
|
|
u32 u32_bytes;
|
|
unsigned int blksize;
|
|
unsigned int j,k;
|
|
BOOL wipe_needed;
|
|
|
|
if (!vol || (byte < 0))
|
|
return -1;
|
|
|
|
big_buffer = (u8*)malloc(vol->cluster_size*64);
|
|
if (!big_buffer) {
|
|
ntfs_log_error("malloc failed\n");
|
|
return -1;
|
|
}
|
|
|
|
for (i = 0; i < vol->nr_clusters; i+=64) {
|
|
blksize = vol->nr_clusters - i;
|
|
if (blksize > 64)
|
|
blksize = 64;
|
|
/* if all clusters in this block are used, ignore the block */
|
|
result = 0;
|
|
for (j = 0; j < blksize; j++) {
|
|
if (utils_cluster_in_use(vol, i+j))
|
|
result++;
|
|
}
|
|
unused += (blksize - result) * vol->cluster_size;
|
|
|
|
if (result == blksize) {
|
|
continue;
|
|
}
|
|
/*
|
|
* if all unused clusters in this block are already wiped,
|
|
* ignore the block
|
|
*/
|
|
if (ntfs_pread(vol->dev, vol->cluster_size * i,
|
|
vol->cluster_size * blksize, big_buffer)
|
|
!= vol->cluster_size * blksize) {
|
|
ntfs_log_error("Read failed at cluster %lld\n",
|
|
(long long)i);
|
|
goto free;
|
|
}
|
|
|
|
result = 0;
|
|
wipe_needed = FALSE;
|
|
u32_bytes = (byte & 255)*0x01010101;
|
|
buffer = big_buffer;
|
|
for (j = 0; (j < blksize) && !wipe_needed; j++) {
|
|
u32_buffer = (u32*)buffer;
|
|
if (!utils_cluster_in_use(vol, i+j)) {
|
|
for (k = 0; (k < vol->cluster_size)
|
|
&& (*u32_buffer++ == u32_bytes); k+=4) {
|
|
}
|
|
if (k < vol->cluster_size)
|
|
wipe_needed = TRUE;
|
|
}
|
|
buffer += vol->cluster_size;
|
|
}
|
|
|
|
if (!wipe_needed) {
|
|
continue;
|
|
}
|
|
/* else wipe unused clusters in the block */
|
|
buffer = big_buffer;
|
|
|
|
for (j = 0; j < blksize; j++) {
|
|
if (!utils_cluster_in_use(vol, i+j)) {
|
|
memset(buffer, byte, vol->cluster_size);
|
|
total += vol->cluster_size;
|
|
}
|
|
buffer += vol->cluster_size;
|
|
}
|
|
|
|
if ((act == act_wipe)
|
|
&& (ntfs_pwrite(vol->dev, vol->cluster_size * i,
|
|
vol->cluster_size * blksize, big_buffer)
|
|
!= vol->cluster_size * blksize)) {
|
|
ntfs_log_error("Write failed at cluster %lld\n",
|
|
(long long)i);
|
|
goto free;
|
|
}
|
|
}
|
|
|
|
ntfs_log_quiet("wipe_unused_fast 0x%02x, %lld bytes"
|
|
" already wiped, %lld more bytes wiped\n",
|
|
byte, (long long)(unused - total), (long long)total);
|
|
free:
|
|
free(big_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 attribute 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;
|
|
le16 block_size_le;
|
|
u16 block_size;
|
|
VCN cur_vcn = 0;
|
|
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;
|
|
if ((cur_vcn & cu_mask) ||
|
|
(((rlc + 1)->length) && (rlc->lcn != LCN_HOLE))) {
|
|
rlc++;
|
|
continue;
|
|
}
|
|
|
|
if (rlc->lcn == LCN_HOLE) {
|
|
runlist_element *rlt;
|
|
|
|
offset = cur_vcn - rlc->length;
|
|
if (offset == (offset & (~cu_mask))) {
|
|
restart = rlc + 1;
|
|
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, 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");
|
|
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) {
|
|
ntfs_log_verbose("Internal error\n");
|
|
ntfs_log_error("bug or damaged fs: we want "
|
|
"allocate buffer size %lld bytes",
|
|
(long long)size);
|
|
return -1;
|
|
}
|
|
|
|
if ((act == act_info) || (!size)) {
|
|
wiped += size;
|
|
if (rlc->lcn == LCN_HOLE)
|
|
restart = rlc + 1;
|
|
rlc++;
|
|
continue;
|
|
}
|
|
|
|
buf = malloc(size);
|
|
if (!buf) {
|
|
ntfs_log_verbose("Not enough memory\n");
|
|
ntfs_log_error("Not enough memory to allocate "
|
|
"%lld bytes",
|
|
(long long)size);
|
|
return -1;
|
|
}
|
|
memset(buf, byte, size);
|
|
|
|
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");
|
|
ntfs_log_error("ntfs_rl_pwrite failed, offset %llu, "
|
|
"size %lld, vcn %lld",
|
|
(unsigned long long)offset,
|
|
(long long)size, (long long)rlc->vcn);
|
|
return -1;
|
|
}
|
|
wiped += ret;
|
|
next:
|
|
if (rlc->lcn == LCN_HOLE)
|
|
restart = rlc + 1;
|
|
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 attribute 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 (na->data_flags & ATTR_IS_ENCRYPTED)
|
|
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) {
|
|
ntfs_log_verbose("Not enough memory\n");
|
|
ntfs_log_error("Not enough memory to allocate %lld bytes",
|
|
(long long)size);
|
|
return -1;
|
|
}
|
|
memset(buf, byte, size);
|
|
|
|
wiped = ntfs_rl_pwrite(vol, na->rl, 0, offset, size, buf);
|
|
if (wiped == -1) {
|
|
ntfs_log_verbose("Internal error\n");
|
|
ntfs_log_error("Couldn't wipe tail");
|
|
}
|
|
|
|
free(buf);
|
|
return wiped;
|
|
}
|
|
|
|
/*
|
|
* Wipe a data attribute tail
|
|
*
|
|
* Return: >0 Success, the clusters were wiped
|
|
* 0 Nothing to wipe
|
|
* -1 Error, something went wrong
|
|
*/
|
|
|
|
static s64 wipe_attr_tail(ntfs_inode *ni, ntfschar *name, int namelen,
|
|
int byte, enum action act)
|
|
{
|
|
ntfs_attr *na;
|
|
ntfs_volume *vol = ni->vol;
|
|
s64 wiped;
|
|
|
|
wiped = -1;
|
|
na = ntfs_attr_open(ni, AT_DATA, name, namelen);
|
|
if (!na) {
|
|
ntfs_log_error("Couldn't open $DATA attribute\n");
|
|
goto close_attr;
|
|
}
|
|
|
|
if (!NAttrNonResident(na)) {
|
|
ntfs_log_verbose("Resident $DATA attribute. Skipping.\n");
|
|
goto close_attr;
|
|
}
|
|
|
|
if (ntfs_attr_map_whole_runlist(na)) {
|
|
ntfs_log_verbose("Internal error\n");
|
|
ntfs_log_error("Can't map runlist (inode %lld)\n",
|
|
(long long)ni->mft_no);
|
|
goto close_attr;
|
|
}
|
|
|
|
if (na->data_flags & ATTR_COMPRESSION_MASK)
|
|
wiped = wipe_compressed_attribute(vol, byte, act, na);
|
|
else
|
|
wiped = wipe_attribute(vol, byte, act, na);
|
|
|
|
if (wiped == -1) {
|
|
ntfs_log_error(" (inode %lld)\n", (long long)ni->mft_no);
|
|
}
|
|
|
|
close_attr:
|
|
ntfs_attr_close(na);
|
|
return (wiped);
|
|
}
|
|
|
|
/**
|
|
* wipe_tails - Wipe the file tails in all its data attributes
|
|
* @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 nr_mft_records, inode_num;
|
|
ntfs_attr_search_ctx *ctx;
|
|
ntfs_inode *ni;
|
|
ATTR_RECORD *a;
|
|
ntfschar *name;
|
|
|
|
if (!vol || (byte < 0))
|
|
return -1;
|
|
|
|
nr_mft_records = vol->mft_na->initialized_size >>
|
|
vol->mft_record_size_bits;
|
|
|
|
/* Avoid getting fixup warnings on unitialized inodes */
|
|
NVolSetNoFixupWarn(vol);
|
|
|
|
for (inode_num = FILE_first_user; inode_num < nr_mft_records;
|
|
inode_num++) {
|
|
s64 attr_wiped;
|
|
s64 wiped = 0;
|
|
|
|
ntfs_log_verbose("Inode %lld - ", (long long)inode_num);
|
|
ni = ntfs_inode_open(vol, inode_num);
|
|
if (!ni) {
|
|
if (opts.verbose)
|
|
ntfs_log_verbose("Could not open inode\n");
|
|
else
|
|
ntfs_log_verbose("\r");
|
|
continue;
|
|
}
|
|
|
|
if (ni->mrec->base_mft_record) {
|
|
ntfs_log_verbose("Not base mft record. Skipping\n");
|
|
goto close_inode;
|
|
}
|
|
|
|
ctx = ntfs_attr_get_search_ctx(ni, (MFT_RECORD*)NULL);
|
|
if (!ctx) {
|
|
ntfs_log_error("Can't get a context, aborting\n");
|
|
ntfs_inode_close(ni);
|
|
goto close_abort;
|
|
}
|
|
while (!ntfs_attr_lookup(AT_DATA, NULL, 0, CASE_SENSITIVE, 0,
|
|
NULL, 0, ctx)) {
|
|
a = ctx->attr;
|
|
|
|
if (!ctx->al_entry || !ctx->al_entry->lowest_vcn) {
|
|
name = (ntfschar*)((u8*)a
|
|
+ le16_to_cpu(a->name_offset));
|
|
attr_wiped = wipe_attr_tail(ni, name,
|
|
a->name_length, byte, act);
|
|
if (attr_wiped > 0)
|
|
wiped += attr_wiped;
|
|
}
|
|
}
|
|
ntfs_attr_put_search_ctx(ctx);
|
|
if (wiped) {
|
|
ntfs_log_verbose("Wiped %llu bytes\n",
|
|
(unsigned long long)wiped);
|
|
total += wiped;
|
|
} else
|
|
ntfs_log_verbose("Nothing to wipe\n");
|
|
close_inode:
|
|
ntfs_inode_close(ni);
|
|
}
|
|
close_abort :
|
|
NVolClearNoFixupWarn(vol);
|
|
ntfs_log_quiet("wipe_tails 0x%02x, %lld bytes\n", byte,
|
|
(long long)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 nr_mft_records, i;
|
|
s64 total = 0;
|
|
s64 result = 0;
|
|
int size = 0;
|
|
MFT_RECORD *rec = NULL;
|
|
|
|
if (!vol || (byte < 0))
|
|
return -1;
|
|
|
|
rec = (MFT_RECORD*)malloc(vol->mft_record_size);
|
|
if (!rec) {
|
|
ntfs_log_error("malloc failed\n");
|
|
return -1;
|
|
}
|
|
|
|
nr_mft_records = vol->mft_na->initialized_size >>
|
|
vol->mft_record_size_bits;
|
|
|
|
for (i = 0; i < 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, rec);
|
|
if (result != 1) {
|
|
ntfs_log_error("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 = le32_to_cpu(rec->bytes_in_use) - 4;
|
|
|
|
if ((size <= 0) || (size > (int)vol->mft_record_size)) {
|
|
ntfs_log_error("Bad mft record %lld\n",
|
|
(long long)i);
|
|
total = -1;
|
|
goto free;
|
|
}
|
|
if (act == act_info) {
|
|
//ntfs_log_info("mft %d\n", size);
|
|
total += size;
|
|
continue;
|
|
}
|
|
|
|
memset(((u8*) rec) + size, byte, vol->mft_record_size - size);
|
|
} else {
|
|
const u16 usa_offset =
|
|
(vol->major_ver == 3) ? 0x0030 : 0x002A;
|
|
const u32 usa_size = 1 +
|
|
(vol->mft_record_size >> NTFS_BLOCK_SIZE_BITS);
|
|
const u16 attrs_offset =
|
|
((usa_offset + usa_size) + 7) & ~((u16) 7);
|
|
const u32 bytes_in_use = attrs_offset + 8;
|
|
|
|
if(usa_size > 0xFFFF || (usa_offset + usa_size) >
|
|
(NTFS_BLOCK_SIZE - sizeof(u16)))
|
|
{
|
|
ntfs_log_error("%d: usa_size out of bounds "
|
|
"(%u)\n", __LINE__, usa_size);
|
|
total = -1;
|
|
goto free;
|
|
}
|
|
|
|
if (act == act_info) {
|
|
total += vol->mft_record_size;
|
|
continue;
|
|
}
|
|
|
|
// Build the record from scratch
|
|
memset(rec, 0, vol->mft_record_size);
|
|
|
|
// Common values
|
|
rec->magic = magic_FILE;
|
|
rec->usa_ofs = cpu_to_le16(usa_offset);
|
|
rec->usa_count = cpu_to_le16((u16) usa_size);
|
|
rec->sequence_number = const_cpu_to_le16(0x0001);
|
|
rec->attrs_offset = cpu_to_le16(attrs_offset);
|
|
rec->bytes_in_use = cpu_to_le32(bytes_in_use);
|
|
rec->bytes_allocated = cpu_to_le32(vol->mft_record_size);
|
|
rec->next_attr_instance = const_cpu_to_le16(0x0001);
|
|
|
|
// End marker.
|
|
*((le32*) (((u8*) rec) + attrs_offset)) = const_cpu_to_le32(0xFFFFFFFF);
|
|
}
|
|
|
|
result = ntfs_attr_mst_pwrite(vol->mft_na, vol->mft_record_size * i,
|
|
1, vol->mft_record_size, rec);
|
|
if (result != 1) {
|
|
ntfs_log_error("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;
|
|
le16 *usnp;
|
|
offset = le16_to_cpu(rec->usa_ofs);
|
|
usnp = (le16*) (((u8*) rec) + offset);
|
|
*usnp = cpu_to_le16(le16_to_cpu(*usnp) - 1);
|
|
|
|
result = ntfs_attr_mst_pwrite(vol->mftmirr_na, vol->mft_record_size * i,
|
|
1, vol->mft_record_size, rec);
|
|
if (result != 1) {
|
|
ntfs_log_error("error attr mst write %lld\n",
|
|
(long long)i);
|
|
total = -1;
|
|
goto free;
|
|
}
|
|
}
|
|
|
|
total += vol->mft_record_size;
|
|
}
|
|
|
|
ntfs_log_quiet("wipe_mft 0x%02x, %lld bytes\n", byte, (long long)total);
|
|
free:
|
|
free(rec);
|
|
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 $BITMAP 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
|
|
__attribute__((unused)), 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) {
|
|
ntfs_log_verbose("malloc failed\n");
|
|
ntfs_log_error("Couldn't allocate %lld bytes",
|
|
(long long)nab->data_size);
|
|
return -1;
|
|
}
|
|
|
|
if (ntfs_attr_pread(nab, 0, nab->data_size, bitmap)
|
|
!= nab->data_size) {
|
|
ntfs_log_verbose("Internal error\n");
|
|
ntfs_log_error("Couldn't read $BITMAP");
|
|
total = -1;
|
|
goto free_bitmap;
|
|
}
|
|
|
|
buf = malloc(indx_record_size);
|
|
if (!buf) {
|
|
ntfs_log_verbose("malloc failed\n");
|
|
ntfs_log_error("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) {
|
|
ntfs_log_verbose("ntfs_rl_pread failed\n");
|
|
ntfs_log_error("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))
|
|
ntfs_log_error("damaged fs: mst_post_read_fixup failed");
|
|
|
|
if ((le32_to_cpu(indx->index.allocated_size) + 0x18) !=
|
|
indx_record_size) {
|
|
ntfs_log_verbose("Internal error\n");
|
|
ntfs_log_error("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))
|
|
ntfs_log_error("damaged fs: mst_pre_write_protect failed");
|
|
if (opts.verbose > 1)
|
|
ntfs_log_verbose("+");
|
|
} else {
|
|
wipe_size = indx_record_size;
|
|
memset(buf, byte, wipe_size);
|
|
if (opts.verbose > 1)
|
|
ntfs_log_verbose("x");
|
|
}
|
|
|
|
wiped = ntfs_rl_pwrite(vol, naa->rl, 0, offset, indx_record_size, buf);
|
|
if (wiped != indx_record_size) {
|
|
ntfs_log_verbose("ntfs_rl_pwrite failed\n");
|
|
ntfs_log_error("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))
|
|
ntfs_log_verbose("\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;
|
|
le32 indx_record_size_le;
|
|
|
|
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_le);
|
|
if (!indx_record_size) {
|
|
ntfs_log_verbose("Internal error\n");
|
|
ntfs_log_error("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 nr_mft_records, inode_num;
|
|
ntfs_inode *ni;
|
|
ntfs_attr *naa;
|
|
ntfs_attr *nab;
|
|
ntfs_attr *nar;
|
|
|
|
if (!vol || (byte < 0))
|
|
return -1;
|
|
|
|
nr_mft_records = vol->mft_na->initialized_size >>
|
|
vol->mft_record_size_bits;
|
|
|
|
/* Avoid getting fixup warnings on unitialized inodes */
|
|
NVolSetNoFixupWarn(vol);
|
|
|
|
for (inode_num = 5; inode_num < nr_mft_records; inode_num++) {
|
|
u32 indx_record_size;
|
|
s64 wiped;
|
|
|
|
ntfs_log_verbose("Inode %lld - ", (long long)inode_num);
|
|
ni = ntfs_inode_open(vol, inode_num);
|
|
if (!ni) {
|
|
if (opts.verbose > 2)
|
|
ntfs_log_verbose("Could not open inode\n");
|
|
else
|
|
ntfs_log_verbose("\r");
|
|
continue;
|
|
}
|
|
|
|
if (ni->mrec->base_mft_record) {
|
|
if (opts.verbose > 2)
|
|
ntfs_log_verbose("Not base mft record. Skipping\n");
|
|
else
|
|
ntfs_log_verbose("\r");
|
|
goto close_inode;
|
|
}
|
|
|
|
naa = ntfs_attr_open(ni, AT_INDEX_ALLOCATION, NTFS_INDEX_I30, 4);
|
|
if (!naa) {
|
|
if (opts.verbose > 2)
|
|
ntfs_log_verbose("Couldn't open $INDEX_ALLOCATION\n");
|
|
else
|
|
ntfs_log_verbose("\r");
|
|
goto close_inode;
|
|
}
|
|
|
|
if (!NAttrNonResident(naa)) {
|
|
ntfs_log_verbose("Resident $INDEX_ALLOCATION\n");
|
|
ntfs_log_error("damaged fs: Resident $INDEX_ALLOCATION "
|
|
"(inode %lld)\n", (long long)inode_num);
|
|
goto close_attr_allocation;
|
|
}
|
|
|
|
if (ntfs_attr_map_whole_runlist(naa)) {
|
|
ntfs_log_verbose("Internal error\n");
|
|
ntfs_log_error("Can't map runlist for $INDEX_ALLOCATION "
|
|
"(inode %lld)\n", (long long)inode_num);
|
|
goto close_attr_allocation;
|
|
}
|
|
|
|
nab = ntfs_attr_open(ni, AT_BITMAP, NTFS_INDEX_I30, 4);
|
|
if (!nab) {
|
|
ntfs_log_verbose("Couldn't open $BITMAP\n");
|
|
ntfs_log_error("damaged fs: $INDEX_ALLOCATION is present, "
|
|
"but we can't open $BITMAP with same "
|
|
"name (inode %lld)\n", (long long)inode_num);
|
|
goto close_attr_allocation;
|
|
}
|
|
|
|
nar = ntfs_attr_open(ni, AT_INDEX_ROOT, NTFS_INDEX_I30, 4);
|
|
if (!nar) {
|
|
ntfs_log_verbose("Couldn't open $INDEX_ROOT\n");
|
|
ntfs_log_error("damaged fs: $INDEX_ALLOCATION is present, but "
|
|
"we can't open $INDEX_ROOT with same name"
|
|
" (inode %lld)\n", (long long)inode_num);
|
|
goto close_attr_bitmap;
|
|
}
|
|
|
|
if (NAttrNonResident(nar)) {
|
|
ntfs_log_verbose("Not resident $INDEX_ROOT\n");
|
|
ntfs_log_error("damaged fs: Not resident $INDEX_ROOT "
|
|
"(inode %lld)\n", (long long)inode_num);
|
|
goto close_attr_root;
|
|
}
|
|
|
|
indx_record_size = get_indx_record_size(nar);
|
|
if (!indx_record_size) {
|
|
ntfs_log_error(" (inode %lld)\n", (long long)inode_num);
|
|
goto close_attr_root;
|
|
}
|
|
|
|
wiped = wipe_index_allocation(vol, byte, act,
|
|
naa, nab, indx_record_size);
|
|
if (wiped == -1) {
|
|
ntfs_log_error(" (inode %lld)\n",
|
|
(long long)inode_num);
|
|
goto close_attr_root;
|
|
}
|
|
|
|
if (wiped) {
|
|
ntfs_log_verbose("Wiped %llu bytes\n",
|
|
(unsigned long long)wiped);
|
|
total += wiped;
|
|
} else
|
|
ntfs_log_verbose("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);
|
|
}
|
|
|
|
NVolClearNoFixupWarn(vol);
|
|
ntfs_log_quiet("wipe_directory 0x%02x, %lld bytes\n", byte,
|
|
(long long)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 consistent 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
|
|
__attribute__((unused)))
|
|
{
|
|
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;
|
|
|
|
/* We can wipe logfile only with 0xff. */
|
|
byte = 0xff;
|
|
|
|
if (!vol || (byte < 0))
|
|
return -1;
|
|
|
|
//ntfs_log_quiet("wipe_logfile(not implemented) 0x%02x\n", byte);
|
|
|
|
if ((ni = ntfs_inode_open(vol, FILE_LogFile)) == NULL) {
|
|
ntfs_log_debug("Failed to open inode FILE_LogFile.\n");
|
|
return -1;
|
|
}
|
|
|
|
if ((na = ntfs_attr_open(ni, AT_DATA, AT_UNNAMED, 0)) == NULL) {
|
|
ntfs_log_debug("Failed to open $FILE_LogFile/$DATA.\n");
|
|
goto error_exit;
|
|
}
|
|
|
|
/* The $DATA attribute of the $LogFile has to be non-resident. */
|
|
if (!NAttrNonResident(na)) {
|
|
ntfs_log_debug("$LogFile $DATA attribute is resident!?!\n");
|
|
errno = EIO;
|
|
goto io_error_exit;
|
|
}
|
|
|
|
/* Get length of $LogFile contents. */
|
|
len = na->data_size;
|
|
if (!len) {
|
|
ntfs_log_debug("$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) {
|
|
ntfs_log_debug("Amount of $LogFile data read does not "
|
|
"correspond to expected length!\n");
|
|
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) {
|
|
ntfs_log_debug("Failed to set the $LogFile attribute "
|
|
"value.\n");
|
|
if (count != -1)
|
|
errno = EIO;
|
|
goto io_error_exit;
|
|
}
|
|
|
|
pos += count;
|
|
}
|
|
|
|
ntfs_attr_close(na);
|
|
ntfs_inode_close(ni);
|
|
ntfs_log_quiet("wipe_logfile 0x%02x, %lld bytes\n", byte,
|
|
(long long)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
|
|
__attribute__((unused)))
|
|
{
|
|
// 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;
|
|
|
|
//ntfs_log_quiet("wipe_pagefile(not implemented) 0x%02x\n", byte);
|
|
|
|
ni = ntfs_pathname_to_inode(vol, NULL, "pagefile.sys");
|
|
if (!ni) {
|
|
ntfs_log_debug("Failed to open inode of pagefile.sys.\n");
|
|
return 0;
|
|
}
|
|
|
|
if ((na = ntfs_attr_open(ni, AT_DATA, AT_UNNAMED, 0)) == NULL) {
|
|
ntfs_log_debug("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)) {
|
|
ntfs_log_debug("pagefile.sys $DATA attribute is resident!?!\n");
|
|
errno = EIO;
|
|
goto io_error_exit;
|
|
}
|
|
|
|
/* Get length of pagefile.sys contents. */
|
|
len = na->data_size;
|
|
if (!len) {
|
|
ntfs_log_debug("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) {
|
|
ntfs_log_debug("Failed to set the pagefile.sys "
|
|
"attribute value.\n");
|
|
if (count != -1)
|
|
errno = EIO;
|
|
goto io_error_exit;
|
|
}
|
|
|
|
pos += count;
|
|
}
|
|
|
|
ntfs_attr_close(na);
|
|
ntfs_inode_close(ni);
|
|
ntfs_log_quiet("wipe_pagefile 0x%02x, %lld bytes\n", byte,
|
|
(long long)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;
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
|
|
/* Avoid getting fixup warnings on unitialized inodes */
|
|
NVolSetNoFixupWarn(nv);
|
|
/* 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) {
|
|
|
|
NVolClearNoFixupWarn(nv);
|
|
ntfs_attr_close(mft);
|
|
free_file(file);
|
|
return -3;
|
|
}
|
|
NVolClearNoFixupWarn(nv);
|
|
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 = const_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 = const_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 = const_cpu_to_sle64(0);
|
|
ctx->attr->highest_vcn = const_cpu_to_sle64(0);
|
|
ctx->attr->allocated_size = const_cpu_to_sle64(0);
|
|
ctx->attr->data_size = const_cpu_to_sle64(0);
|
|
ctx->attr->initialized_size = const_cpu_to_sle64(0);
|
|
ctx->attr->compressed_size = const_cpu_to_sle64(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
|
|
*
|
|
* 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)
|
|
ntfs_log_quiet("%s is in 'no-action' mode, it will NOT write to disk."
|
|
"\n\n", EXEC_NAME);
|
|
|
|
ntfs_log_quiet("%s is about to wipe:\n", EXEC_NAME);
|
|
if (opts.unused)
|
|
ntfs_log_quiet("\tunused disk space\n");
|
|
if (opts.unused_fast)
|
|
ntfs_log_quiet("\tunused disk space (fast)\n");
|
|
if (opts.tails)
|
|
ntfs_log_quiet("\tfile tails\n");
|
|
if (opts.mft)
|
|
ntfs_log_quiet("\tunused mft areas\n");
|
|
if (opts.directory)
|
|
ntfs_log_quiet("\tunused directory index space\n");
|
|
if (opts.logfile)
|
|
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) {
|
|
for (i = 0; opts.bytes[i] >= 0; i++)
|
|
ntfs_log_quiet("0x%02x ", opts.bytes[i]);
|
|
}
|
|
ntfs_log_quiet("\n");
|
|
|
|
if (opts.count > 1)
|
|
ntfs_log_quiet("%s will repeat these operations %d times.\n", EXEC_NAME, opts.count);
|
|
ntfs_log_quiet("\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 res;
|
|
int i, j;
|
|
enum action act = act_info;
|
|
|
|
ntfs_log_set_handler(ntfs_log_handler_outerr);
|
|
|
|
res = parse_options(argc, argv);
|
|
if (res >= 0)
|
|
return (res);
|
|
|
|
utils_set_locale();
|
|
|
|
if (!opts.info)
|
|
print_summary();
|
|
|
|
if (opts.info || opts.noaction)
|
|
flags = NTFS_MNT_RDONLY;
|
|
if (opts.force)
|
|
flags |= NTFS_MNT_RECOVER;
|
|
|
|
vol = utils_mount_volume(opts.device, flags);
|
|
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) {
|
|
ntfs_log_quiet("\n%s will begin in 5 seconds, press CTRL-C to abort.\n", EXEC_NAME);
|
|
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;
|
|
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 || opts.unused_fast) {
|
|
if (opts.unused_fast)
|
|
wiped = wipe_unused_fast(vol, byte,
|
|
act);
|
|
else
|
|
wiped = wipe_unused(vol, byte, act);
|
|
if (wiped < 0)
|
|
goto umount;
|
|
else
|
|
total += wiped;
|
|
}
|
|
|
|
if (opts.undel) {
|
|
wiped = wipe_unrm(vol);
|
|
if (wiped != 0)
|
|
goto umount;
|
|
/*
|
|
else
|
|
total += wiped;
|
|
*/
|
|
}
|
|
|
|
if (act == act_info)
|
|
break;
|
|
}
|
|
|
|
if (opts.noaction || opts.info)
|
|
ntfs_log_info("%lld bytes would be wiped"
|
|
" (excluding undelete data)\n",
|
|
(long long)total);
|
|
else
|
|
ntfs_log_info("%lld bytes were wiped"
|
|
" (excluding undelete data)\n",
|
|
(long long)total);
|
|
}
|
|
result = 0;
|
|
umount:
|
|
ntfs_umount(vol, FALSE);
|
|
free:
|
|
if (opts.bytes)
|
|
free(opts.bytes);
|
|
return result;
|
|
}
|