mirror of
https://git.code.sf.net/p/ntfs-3g/ntfs-3g.git
synced 2024-12-05 07:53:38 +08:00
Initial revision
This commit is contained in:
parent
d701a635bc
commit
44c49c55e5
@ -1,933 +0,0 @@
|
||||
/**
|
||||
* ntfsclone - Part of the Linux-NTFS project.
|
||||
*
|
||||
* Copyright (c) 2003 Szabolcs Szakacsits
|
||||
*
|
||||
* ntfsclone clones NTFS data and/or metadata to a sparse file or stdout.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <getopt.h>
|
||||
|
||||
#include "debug.h"
|
||||
#include "types.h"
|
||||
#include "support.h"
|
||||
#include "endians.h"
|
||||
#include "bootsect.h"
|
||||
#include "disk_io.h"
|
||||
#include "attrib.h"
|
||||
#include "volume.h"
|
||||
#include "mft.h"
|
||||
#include "bitmap.h"
|
||||
#include "inode.h"
|
||||
#include "runlist.h"
|
||||
#include "utils.h"
|
||||
|
||||
static const char *EXEC_NAME = "ntfsclone";
|
||||
|
||||
struct {
|
||||
int verbose;
|
||||
int quiet;
|
||||
int debug;
|
||||
int force;
|
||||
int overwrite;
|
||||
int stdout;
|
||||
int metadata_only;
|
||||
char *output;
|
||||
char *volume;
|
||||
} opt;
|
||||
|
||||
struct bitmap {
|
||||
u8 *bm;
|
||||
s64 size;
|
||||
};
|
||||
|
||||
struct progress_bar {
|
||||
u64 start;
|
||||
u64 stop;
|
||||
int resolution;
|
||||
float unit;
|
||||
};
|
||||
|
||||
struct __ntfs_walk_clusters_ctx {
|
||||
ntfs_inode *ni; /* inode being processed */
|
||||
ntfs_attr_search_ctx *ctx; /* inode attribute being processed */
|
||||
u64 inuse; /* number of clusters in use */
|
||||
};
|
||||
|
||||
typedef struct __ntfs_walk_clusters_ctx ntfs_walk_clusters_ctx;
|
||||
typedef int (ntfs_walk_op)(ntfs_inode *ni, void *data);
|
||||
|
||||
struct ntfs_walk_cluster {
|
||||
ntfs_walk_op *inode_op; /* not implemented yet */
|
||||
ntfs_walk_clusters_ctx *image;
|
||||
};
|
||||
|
||||
|
||||
ntfs_volume *vol = NULL;
|
||||
struct bitmap lcn_bitmap;
|
||||
|
||||
int fd_out;
|
||||
FILE *msg_out = NULL;
|
||||
|
||||
int nr_used_mft_records = 0;
|
||||
int wipe = 0;
|
||||
int wiped_unused_mft_data = 0;
|
||||
int wiped_unused_mft = 0;
|
||||
int wiped_resident_data = 0;
|
||||
int wiped_timestamp_data = 0;
|
||||
|
||||
#define NTFS_MBYTE (1000 * 1000)
|
||||
|
||||
#define ERR_PREFIX "ERROR"
|
||||
#define PERR_PREFIX ERR_PREFIX "(%d): "
|
||||
#define NERR_PREFIX ERR_PREFIX ": "
|
||||
|
||||
#define LAST_METADATA_INODE 11
|
||||
|
||||
#define NTFS_MAX_CLUSTER_SIZE 65536
|
||||
|
||||
#define rounded_up_division(a, b) (((a) + (b - 1)) / (b))
|
||||
|
||||
GEN_PRINTF(Eprintf, stderr, NULL, FALSE)
|
||||
GEN_PRINTF(Vprintf, msg_out, &opt.verbose, TRUE)
|
||||
GEN_PRINTF(Qprintf, msg_out, &opt.quiet, FALSE)
|
||||
GEN_PRINTF(Printf, msg_out, NULL, FALSE)
|
||||
|
||||
|
||||
void perr_printf(const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
int eo = errno;
|
||||
|
||||
Printf(PERR_PREFIX, eo);
|
||||
va_start(ap, fmt);
|
||||
vfprintf(msg_out, fmt, ap);
|
||||
va_end(ap);
|
||||
Printf(": %s\n", strerror(eo));
|
||||
fflush(msg_out);
|
||||
}
|
||||
|
||||
void err_printf(const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
Printf(NERR_PREFIX);
|
||||
va_start(ap, fmt);
|
||||
vfprintf(msg_out, fmt, ap);
|
||||
va_end(ap);
|
||||
fflush(msg_out);
|
||||
}
|
||||
|
||||
int err_exit(const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
Printf(NERR_PREFIX);
|
||||
va_start(ap, fmt);
|
||||
vfprintf(msg_out, fmt, ap);
|
||||
va_end(ap);
|
||||
fflush(msg_out);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
|
||||
int perr_exit(const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
int eo = errno;
|
||||
|
||||
Printf(PERR_PREFIX, eo);
|
||||
va_start(ap, fmt);
|
||||
vfprintf(msg_out, fmt, ap);
|
||||
va_end(ap);
|
||||
Printf(": %s\n", strerror(eo));
|
||||
fflush(msg_out);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
|
||||
void usage()
|
||||
{
|
||||
Eprintf("\nUsage: %s [options] device\n"
|
||||
" Clone NTFS data to a sparse file or send it to stdout.\n"
|
||||
"\n"
|
||||
" -o FILE --output FILE Clone NTFS to the non-existent FILE\n"
|
||||
" -O FILE Clone NTFS to FILE, overwriting if exists\n"
|
||||
" -m --metadata Clone *only* metadata (for NTFS experts)\n"
|
||||
" -f --force Force to progress (DANGEROUS)\n"
|
||||
" -h --help Display this help\n"
|
||||
#ifdef DEBUG
|
||||
" -d --debug Show debug information\n"
|
||||
#endif
|
||||
"\n"
|
||||
" If FILE is '-' then send NTFS data to stdout replacing non used\n"
|
||||
" NTFS and partition space with zeros.\n"
|
||||
"\n", EXEC_NAME);
|
||||
Eprintf("%s%s\n", ntfs_bugs, ntfs_home);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
|
||||
void parse_options(int argc, char **argv)
|
||||
{
|
||||
static const char *sopt = "-dfhmo:O:";
|
||||
static const struct option lopt[] = {
|
||||
#ifdef DEBUG
|
||||
{ "debug", no_argument, NULL, 'd' },
|
||||
#endif
|
||||
{ "force", no_argument, NULL, 'f' },
|
||||
{ "help", no_argument, NULL, 'h' },
|
||||
{ "metadata", no_argument, NULL, 'm' },
|
||||
{ "output", required_argument, NULL, 'o' },
|
||||
{ NULL, 0, NULL, 0 }
|
||||
};
|
||||
|
||||
char c;
|
||||
|
||||
memset(&opt, 0, sizeof(opt));
|
||||
|
||||
while ((c = getopt_long(argc, argv, sopt, lopt, NULL)) != -1) {
|
||||
switch (c) {
|
||||
case 1: /* A non-option argument */
|
||||
if (opt.volume)
|
||||
usage();
|
||||
opt.volume = argv[optind-1];
|
||||
break;
|
||||
case 'd':
|
||||
opt.debug++;
|
||||
break;
|
||||
case 'f':
|
||||
opt.force++;
|
||||
break;
|
||||
case 'h':
|
||||
case '?':
|
||||
usage();
|
||||
case 'm':
|
||||
opt.metadata_only++;
|
||||
break;
|
||||
case 'O':
|
||||
opt.overwrite++;
|
||||
case 'o':
|
||||
if (opt.output)
|
||||
usage();
|
||||
opt.output = argv[optind-1];
|
||||
break;
|
||||
default:
|
||||
err_printf("Unknown option '%s'.\n", argv[optind-1]);
|
||||
usage();
|
||||
}
|
||||
}
|
||||
|
||||
if (opt.output == NULL) {
|
||||
err_printf("You must specify an output file.\n");
|
||||
usage();
|
||||
}
|
||||
|
||||
if (strcmp(opt.output, "-") == 0)
|
||||
opt.stdout++;
|
||||
|
||||
if (opt.volume == NULL) {
|
||||
err_printf("You must specify a device file.\n");
|
||||
usage();
|
||||
}
|
||||
|
||||
if (opt.metadata_only && opt.stdout)
|
||||
err_exit("Cloning only metadata to stdout isn't supported yet!\n");
|
||||
|
||||
msg_out = stdout;
|
||||
|
||||
/* FIXME: this is a workaround for loosing debug info if stdout != stderr
|
||||
and for the uncontrollable verbose messages in libntfs. Ughhh. */
|
||||
if (opt.stdout)
|
||||
msg_out = stderr;
|
||||
else if (opt.debug)
|
||||
stderr = stdout;
|
||||
else
|
||||
if (!(stderr = fopen("/dev/null", "rw")))
|
||||
perr_exit("Couldn't open /dev/null");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* nr_clusters_to_bitmap_byte_size
|
||||
*
|
||||
* Take the number of clusters in the volume and calculate the size of $Bitmap.
|
||||
* The size will always be a multiple of 8 bytes.
|
||||
*/
|
||||
s64 nr_clusters_to_bitmap_byte_size(s64 nr_clusters)
|
||||
{
|
||||
s64 bm_bsize;
|
||||
|
||||
bm_bsize = rounded_up_division(nr_clusters, 8);
|
||||
|
||||
bm_bsize = (bm_bsize + 7) & ~7;
|
||||
Dprintf("Bitmap byte size : %lld (%lld clusters)\n",
|
||||
bm_bsize, rounded_up_division(bm_bsize, vol->cluster_size));
|
||||
|
||||
return bm_bsize;
|
||||
}
|
||||
|
||||
int is_critical_meatadata(ntfs_walk_clusters_ctx *image)
|
||||
{
|
||||
s64 inode;
|
||||
|
||||
inode = image->ni->mft_no;
|
||||
|
||||
if (inode <= LAST_METADATA_INODE)
|
||||
if (inode != FILE_LogFile)
|
||||
return 1;
|
||||
|
||||
if (image->ctx->attr->type != AT_DATA)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void write_cluster(const char *buff)
|
||||
{
|
||||
int count;
|
||||
|
||||
if ((count = write(fd_out, buff, vol->cluster_size)) == -1)
|
||||
perr_exit("write");
|
||||
|
||||
if (count != vol->cluster_size)
|
||||
err_exit("Partial write not yet handled\n");
|
||||
}
|
||||
|
||||
void copy_cluster()
|
||||
{
|
||||
int count;
|
||||
char buff[NTFS_MAX_CLUSTER_SIZE]; /* overflow checked at mount time */
|
||||
|
||||
/* FIXME: handle partial read/writes */
|
||||
if ((count = read(vol->fd, buff, vol->cluster_size)) == -1)
|
||||
perr_exit("read");
|
||||
|
||||
if (count != vol->cluster_size)
|
||||
err_exit("Partial read not yet handled\n");
|
||||
|
||||
write_cluster(buff);
|
||||
}
|
||||
|
||||
void lseek_to_cluster(s64 lcn)
|
||||
{
|
||||
off_t pos;
|
||||
|
||||
pos = (off_t)(lcn * vol->cluster_size);
|
||||
|
||||
if (lseek(vol->fd, pos, SEEK_SET) == (off_t)-1)
|
||||
perr_exit("lseek input");
|
||||
|
||||
if (opt.stdout)
|
||||
return;
|
||||
|
||||
if (lseek(fd_out, pos, SEEK_SET) == (off_t)-1)
|
||||
perr_exit("lseek output");
|
||||
}
|
||||
|
||||
void dump_clusters(ntfs_walk_clusters_ctx *image, runlist *rl)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (opt.stdout)
|
||||
return;
|
||||
|
||||
if (opt.metadata_only && !is_critical_meatadata(image))
|
||||
return;
|
||||
|
||||
lseek_to_cluster(rl->lcn);
|
||||
|
||||
/* FIXME: this could give pretty suboptimal performance */
|
||||
for (i = 0; i < rl->length; i++)
|
||||
copy_cluster();
|
||||
}
|
||||
|
||||
void dump_to_stdout()
|
||||
{
|
||||
s64 i, pos, count;
|
||||
u8 bm[NTFS_BUF_SIZE];
|
||||
void *buff;
|
||||
|
||||
Printf("Dumping NTFS to stdout ...\n");
|
||||
|
||||
if ((buff = calloc(1, vol->cluster_size)) == NULL)
|
||||
perr_exit("dump_to_stdout");
|
||||
|
||||
pos = 0;
|
||||
while (1) {
|
||||
count = ntfs_attr_pread(vol->lcnbmp_na, pos, NTFS_BUF_SIZE, bm);
|
||||
if (count == -1)
|
||||
perr_exit("Couldn't read $Bitmap (pos = %Ld)\n", pos);
|
||||
|
||||
if (count == 0)
|
||||
return;
|
||||
|
||||
for (i = 0; i < count; i++, pos++) {
|
||||
u64 cl; /* current cluster */
|
||||
|
||||
for (cl = pos * 8; cl < (pos + 1) * 8; cl++) {
|
||||
|
||||
if (cl > vol->nr_clusters - 1)
|
||||
return;
|
||||
|
||||
if (ntfs_bit_get(bm, i * 8 + cl % 8)) {
|
||||
lseek_to_cluster(cl);
|
||||
copy_cluster();
|
||||
} else
|
||||
write_cluster(buff);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define WIPE_TIMESTAMPS(atype, attr) \
|
||||
do { \
|
||||
atype *ats; \
|
||||
ats = (atype *)((char*)(attr) + (attr)->value_offset); \
|
||||
\
|
||||
ats->creation_time = 0; \
|
||||
ats->last_data_change_time = 0; \
|
||||
ats->last_mft_change_time= 0; \
|
||||
ats->last_access_time = 0; \
|
||||
\
|
||||
wiped_timestamp_data += 32; \
|
||||
\
|
||||
} while(0)
|
||||
|
||||
void wipe_timestamps(ntfs_walk_clusters_ctx *image)
|
||||
{
|
||||
ATTR_RECORD *a = image->ctx->attr;
|
||||
|
||||
if (image->ni->mft_no <= LAST_METADATA_INODE)
|
||||
return;
|
||||
|
||||
if (a->type == AT_FILE_NAME)
|
||||
WIPE_TIMESTAMPS(FILE_NAME_ATTR, a);
|
||||
|
||||
else if (a->type == AT_STANDARD_INFORMATION)
|
||||
WIPE_TIMESTAMPS(STANDARD_INFORMATION, a);
|
||||
}
|
||||
|
||||
void wipe_resident_data(ntfs_walk_clusters_ctx *image)
|
||||
{
|
||||
ATTR_RECORD *a;
|
||||
int i, n = 0;
|
||||
char *p;
|
||||
|
||||
a = image->ctx->attr;
|
||||
p = (char *)a + a->value_offset;
|
||||
|
||||
if (image->ni->mft_no <= LAST_METADATA_INODE)
|
||||
return;
|
||||
|
||||
if (a->type != AT_DATA)
|
||||
return;
|
||||
|
||||
for (i = 0; i < a->value_length; i++) {
|
||||
if (p[i]) {
|
||||
p[i] = 0;
|
||||
n++;
|
||||
}
|
||||
}
|
||||
|
||||
wiped_resident_data += n;
|
||||
}
|
||||
|
||||
void walk_runs(struct ntfs_walk_cluster *walk)
|
||||
{
|
||||
int i, j;
|
||||
runlist *rl;
|
||||
ATTR_RECORD *a;
|
||||
ntfs_attr_search_ctx *ctx;
|
||||
|
||||
ctx = walk->image->ctx;
|
||||
a = ctx->attr;
|
||||
|
||||
if (!a->non_resident) {
|
||||
if (wipe) {
|
||||
wipe_resident_data(walk->image);
|
||||
wipe_timestamps(walk->image);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(rl = ntfs_mapping_pairs_decompress(vol, a, NULL)))
|
||||
perr_exit("ntfs_decompress_mapping_pairs");
|
||||
|
||||
for (i = 0; rl[i].length; i++) {
|
||||
s64 lcn = rl[i].lcn;
|
||||
s64 lcn_length = rl[i].length;
|
||||
|
||||
if (lcn == LCN_HOLE || lcn == LCN_RL_NOT_MAPPED)
|
||||
continue;
|
||||
|
||||
/* FIXME: ntfs_mapping_pairs_decompress should return error */
|
||||
if (lcn < 0 || lcn_length < 0)
|
||||
err_exit("Corrupt runlist in inode %lld attr %x LCN "
|
||||
"%llx length %llx\n", ctx->ntfs_ino->mft_no,
|
||||
le32_to_cpu (a->type), lcn, lcn_length);
|
||||
|
||||
if (!wipe)
|
||||
dump_clusters(walk->image, rl + i);
|
||||
|
||||
for (j = 0; j < lcn_length; j++) {
|
||||
u64 k = (u64)lcn + j;
|
||||
if (ntfs_bit_get_and_set(lcn_bitmap.bm, k, 1))
|
||||
err_exit("Cluster %lu referenced twice!\n"
|
||||
"You didn't shutdown your Windows"
|
||||
"properly?\n", k);
|
||||
}
|
||||
|
||||
walk->image->inuse += lcn_length;
|
||||
}
|
||||
|
||||
free(rl);
|
||||
}
|
||||
|
||||
|
||||
void walk_attributes(struct ntfs_walk_cluster *walk)
|
||||
{
|
||||
ntfs_attr_search_ctx *ctx;
|
||||
|
||||
if (!(ctx = ntfs_attr_get_search_ctx(walk->image->ni, NULL)))
|
||||
perr_exit("ntfs_get_attr_search_ctx");
|
||||
|
||||
while (!ntfs_attrs_walk(ctx)) {
|
||||
if (ctx->attr->type == AT_END)
|
||||
break;
|
||||
|
||||
walk->image->ctx = ctx;
|
||||
walk_runs(walk);
|
||||
}
|
||||
|
||||
ntfs_attr_put_search_ctx(ctx);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void compare_bitmaps(struct bitmap *a)
|
||||
{
|
||||
s64 i, pos, count;
|
||||
int mismatch = 0;
|
||||
u8 bm[NTFS_BUF_SIZE];
|
||||
|
||||
Printf("Accounting clusters ...\n");
|
||||
|
||||
pos = 0;
|
||||
while (1) {
|
||||
count = ntfs_attr_pread(vol->lcnbmp_na, pos, NTFS_BUF_SIZE, bm);
|
||||
if (count == -1)
|
||||
perr_exit("Couldn't get $Bitmap $DATA");
|
||||
|
||||
if (count == 0) {
|
||||
if (a->size != pos)
|
||||
err_exit("$Bitmap file size doesn't match "
|
||||
"calculated size (%Ld != %Ld)\n",
|
||||
a->size, pos);
|
||||
break;
|
||||
}
|
||||
|
||||
for (i = 0; i < count; i++, pos++) {
|
||||
u64 cl; /* current cluster */
|
||||
|
||||
if (a->bm[pos] == bm[i])
|
||||
continue;
|
||||
|
||||
for (cl = pos * 8; cl < (pos + 1) * 8; cl++) {
|
||||
char bit;
|
||||
|
||||
bit = ntfs_bit_get(a->bm, cl);
|
||||
if (bit == ntfs_bit_get(bm, i * 8 + cl % 8))
|
||||
continue;
|
||||
|
||||
if (++mismatch > 10)
|
||||
continue;
|
||||
|
||||
Printf("Cluster accounting failed at %Lu "
|
||||
"(0x%Lx): %s cluster in $Bitmap\n",
|
||||
cl, cl, bit ? "missing" : "extra");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mismatch) {
|
||||
Printf("Totally %d cluster accounting mismatches.\n",
|
||||
mismatch);
|
||||
err_exit("Filesystem check failed! Windows wasn't shutdown "
|
||||
"properly or inconsistent\nfilesystem. Please run "
|
||||
"chkdsk on Windows.\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void progress_init(struct progress_bar *p, u64 start, u64 stop, int res)
|
||||
{
|
||||
p->start = start;
|
||||
p->stop = stop;
|
||||
p->unit = 100.0 / (stop - start);
|
||||
p->resolution = res;
|
||||
}
|
||||
|
||||
|
||||
void progress_update(struct progress_bar *p, u64 current)
|
||||
{
|
||||
float percent = p->unit * current;
|
||||
|
||||
if (current != p->stop) {
|
||||
if ((current - p->start) % p->resolution)
|
||||
return;
|
||||
Printf("%6.2f percent completed\r", percent);
|
||||
} else
|
||||
Printf("100.00 percent completed\n");
|
||||
fflush(msg_out);
|
||||
}
|
||||
|
||||
int wipe_data(char *p, int pos, int len)
|
||||
{
|
||||
int wiped = 0;
|
||||
|
||||
p += pos;
|
||||
for (; len > 0; len--) {
|
||||
if (p[len]) {
|
||||
p[len] = 0;
|
||||
wiped++;
|
||||
}
|
||||
}
|
||||
|
||||
return wiped;
|
||||
}
|
||||
|
||||
void wipe_unused_mft_data(ntfs_inode *ni)
|
||||
{
|
||||
int unused;
|
||||
MFT_RECORD *m = ni->mrec;
|
||||
|
||||
/* FIXME: MFTMirr update is broken in libntfs */
|
||||
if (ni->mft_no <= LAST_METADATA_INODE)
|
||||
return;
|
||||
|
||||
unused = m->bytes_allocated - m->bytes_in_use;
|
||||
wiped_unused_mft_data += wipe_data((char *)m, m->bytes_in_use, unused);
|
||||
}
|
||||
|
||||
void wipe_unused_mft(ntfs_inode *ni)
|
||||
{
|
||||
int unused;
|
||||
MFT_RECORD *m = ni->mrec;
|
||||
|
||||
/* FIXME: MFTMirr update is broken in libntfs */
|
||||
if (ni->mft_no <= LAST_METADATA_INODE)
|
||||
return;
|
||||
|
||||
/* MFT_RECORD doesn't have the XP specific 6 bytes, so add it */
|
||||
unused = m->bytes_in_use - (sizeof(MFT_RECORD) + 6);
|
||||
wiped_unused_mft += wipe_data((char *)m, sizeof(MFT_RECORD), unused);
|
||||
}
|
||||
|
||||
|
||||
int walk_clusters(ntfs_volume *vol, struct ntfs_walk_cluster *walk)
|
||||
{
|
||||
s64 inode = 0;
|
||||
s64 last_mft_rec;
|
||||
ntfs_inode *ni;
|
||||
struct progress_bar progress;
|
||||
|
||||
Printf("Scanning volume ...\n");
|
||||
|
||||
last_mft_rec = vol->nr_mft_records - 1;
|
||||
progress_init(&progress, inode, last_mft_rec, 100);
|
||||
|
||||
for (; inode <= last_mft_rec; inode++) {
|
||||
|
||||
int err, deleted_inode;
|
||||
MFT_REF mref = (MFT_REF)inode;
|
||||
|
||||
progress_update(&progress, inode);
|
||||
|
||||
/* FIXME: Terribe kludge for libntfs not being able to return
|
||||
a deleted MFT record as inode */
|
||||
ni = (ntfs_inode*)calloc(1, sizeof(ntfs_inode));
|
||||
if (!ni)
|
||||
perr_exit("walk_clusters");
|
||||
|
||||
ni->vol = vol;
|
||||
|
||||
err = ntfs_file_record_read(vol, mref, &ni->mrec, NULL);
|
||||
if (err == -1) {
|
||||
free(ni);
|
||||
continue;
|
||||
}
|
||||
|
||||
deleted_inode = !(ni->mrec->flags & MFT_RECORD_IN_USE);
|
||||
|
||||
if (deleted_inode) {
|
||||
|
||||
ni->mft_no = MREF(mref);
|
||||
if (wipe) {
|
||||
wipe_unused_mft(ni);
|
||||
wipe_unused_mft_data(ni);
|
||||
if (ntfs_mft_record_write(vol, ni->mft_no, ni->mrec))
|
||||
perr_exit("ntfs_mft_record_write");
|
||||
}
|
||||
}
|
||||
|
||||
if (ni->mrec)
|
||||
free(ni->mrec);
|
||||
free(ni);
|
||||
|
||||
if (deleted_inode)
|
||||
continue;
|
||||
|
||||
if ((ni = ntfs_inode_open(vol, mref)) == NULL) {
|
||||
/* FIXME: continue only if it make sense, e.g.
|
||||
MFT record not in use based on $MFT bitmap */
|
||||
if (errno == EIO || errno == ENOENT)
|
||||
continue;
|
||||
perr_exit("Reading inode %lld failed", inode);
|
||||
}
|
||||
|
||||
if (wipe)
|
||||
nr_used_mft_records++;
|
||||
|
||||
if ((ni->mrec->base_mft_record) != 0)
|
||||
goto out;
|
||||
|
||||
walk->image->ni = ni;
|
||||
walk_attributes(walk);
|
||||
out:
|
||||
if (wipe) {
|
||||
wipe_unused_mft_data(ni);
|
||||
if (ntfs_mft_record_write(vol, ni->mft_no, ni->mrec))
|
||||
perr_exit("ntfs_mft_record_write");
|
||||
}
|
||||
|
||||
if (ntfs_inode_close(ni))
|
||||
perr_exit("ntfs_inode_close for inode %Ld", inode);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* $Bitmap can overlap the end of the volume. Any bits in this region
|
||||
* must be set. This region also encompasses the backup boot sector.
|
||||
*/
|
||||
void bitmap_file_data_fixup(s64 cluster, struct bitmap *bm)
|
||||
{
|
||||
for (; cluster < bm->size << 3; cluster++)
|
||||
ntfs_bit_set(bm->bm, (u64)cluster, 1);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Allocate a block of memory with one bit for each cluster of the disk.
|
||||
* All the bits are set to 0, except those representing the region beyond the
|
||||
* end of the disk.
|
||||
*/
|
||||
void setup_lcn_bitmap()
|
||||
{
|
||||
/* Determine lcn bitmap byte size and allocate it. */
|
||||
lcn_bitmap.size = nr_clusters_to_bitmap_byte_size(vol->nr_clusters);
|
||||
|
||||
if (!(lcn_bitmap.bm = (unsigned char *)calloc(1, lcn_bitmap.size)))
|
||||
perr_exit("Failed to allocate internal buffer");
|
||||
|
||||
bitmap_file_data_fixup(vol->nr_clusters, &lcn_bitmap);
|
||||
}
|
||||
|
||||
|
||||
s64 volume_size(ntfs_volume *vol, s64 nr_clusters)
|
||||
{
|
||||
return nr_clusters * vol->cluster_size;
|
||||
}
|
||||
|
||||
|
||||
void print_volume_size(char *str, s64 bytes)
|
||||
{
|
||||
Printf("%s: %lld bytes (%lld MB)\n",
|
||||
str, bytes, rounded_up_division(bytes, NTFS_MBYTE));
|
||||
}
|
||||
|
||||
|
||||
void print_disk_usage(ntfs_walk_clusters_ctx *image)
|
||||
{
|
||||
s64 total, used, free;
|
||||
|
||||
total = vol->nr_clusters * vol->cluster_size;
|
||||
used = image->inuse * vol->cluster_size;
|
||||
free = total - used;
|
||||
|
||||
Printf("Space in use : %lld MB (%.1f%%) ",
|
||||
rounded_up_division(used, NTFS_MBYTE),
|
||||
100.0 * ((float)used / total));
|
||||
|
||||
Printf("\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* First perform some checks to determine if the volume is already mounted, or
|
||||
* is dirty (Windows wasn't shutdown properly). If everything is OK, then mount
|
||||
* the volume (load the metadata into memory).
|
||||
*/
|
||||
void mount_volume(unsigned long new_mntflag)
|
||||
{
|
||||
unsigned long mntflag;
|
||||
|
||||
if (ntfs_check_if_mounted(opt.volume, &mntflag))
|
||||
perr_exit("Failed to check '%s' mount state", opt.volume);
|
||||
|
||||
if (mntflag & NTFS_MF_MOUNTED) {
|
||||
if (!(mntflag & NTFS_MF_READONLY))
|
||||
err_exit("Device %s is mounted read-write. "
|
||||
"You must 'umount' it first.\n", opt.volume);
|
||||
if (!new_mntflag)
|
||||
err_exit("Device %s is mounted. "
|
||||
"You must 'umount' it first.\n", opt.volume);
|
||||
}
|
||||
|
||||
if (!(vol = ntfs_mount(opt.volume, new_mntflag))) {
|
||||
|
||||
int err = errno;
|
||||
|
||||
perr_printf("ntfs_mount failed");
|
||||
if (err == EINVAL) {
|
||||
Printf("Apparently device '%s' doesn't have a "
|
||||
"valid NTFS. Maybe you selected\nthe whole "
|
||||
"disk instead of a partition (e.g. /dev/hda, "
|
||||
"not /dev/hda1)?\n", opt.volume);
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (vol->flags & VOLUME_IS_DIRTY)
|
||||
if (opt.force-- <= 0)
|
||||
err_exit("Volume is dirty. Run chkdsk and "
|
||||
"please try again (or see -f option).\n");
|
||||
|
||||
if (NTFS_MAX_CLUSTER_SIZE < vol->cluster_size)
|
||||
err_exit("Cluster size %u is too large!\n", vol->cluster_size);
|
||||
|
||||
Printf("NTFS volume version: %d.%d\n", vol->major_ver, vol->minor_ver);
|
||||
if (ntfs_version_is_supported(vol))
|
||||
perr_exit("Unknown NTFS version");
|
||||
|
||||
Printf("Cluster size : %u bytes\n", vol->cluster_size);
|
||||
print_volume_size("Current volume size",
|
||||
volume_size(vol, vol->nr_clusters));
|
||||
}
|
||||
|
||||
struct ntfs_walk_cluster backup_clusters = { NULL, NULL };
|
||||
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
ntfs_walk_clusters_ctx image;
|
||||
s64 device_size; /* in bytes */
|
||||
int flags, wiped_total = 0;
|
||||
|
||||
/* print to stderr, stdout can be an NTFS image ... */
|
||||
Eprintf("%s v%s\n", EXEC_NAME, VERSION);
|
||||
msg_out = stderr;
|
||||
|
||||
parse_options(argc, argv);
|
||||
|
||||
utils_set_locale();
|
||||
|
||||
mount_volume(MS_RDONLY);
|
||||
|
||||
device_size = ntfs_device_size_get(vol->fd, vol->sector_size);
|
||||
device_size *= vol->sector_size;
|
||||
if (device_size <= 0)
|
||||
err_exit("Couldn't get device size (%Ld)!\n", device_size);
|
||||
|
||||
print_volume_size("Current device size", device_size);
|
||||
|
||||
if (device_size < vol->nr_clusters * vol->cluster_size)
|
||||
err_exit("Current NTFS volume size is bigger than the device "
|
||||
"size (%Ld)!\nCorrupt partition table or incorrect "
|
||||
"device partitioning?\n", device_size);
|
||||
|
||||
if (opt.stdout) {
|
||||
if ((fd_out = fileno(stdout)) == -1)
|
||||
perr_exit("fileno for stdout failed");
|
||||
} else {
|
||||
flags = O_CREAT | O_TRUNC | O_WRONLY;
|
||||
if (!opt.overwrite)
|
||||
flags |= O_EXCL;
|
||||
|
||||
if ((fd_out = open(opt.output, flags, S_IRWXU)) == -1)
|
||||
perr_exit("opening file '%s' failed", opt.output);
|
||||
|
||||
if (ftruncate(fd_out, device_size) == -1)
|
||||
perr_exit("ftruncate failed for file '%s'", opt.output);
|
||||
}
|
||||
|
||||
setup_lcn_bitmap();
|
||||
memset(&image, 0, sizeof(image));
|
||||
backup_clusters.image = ℑ
|
||||
|
||||
walk_clusters(vol, &backup_clusters);
|
||||
compare_bitmaps(&lcn_bitmap);
|
||||
print_disk_usage(&image);
|
||||
|
||||
free(lcn_bitmap.bm);
|
||||
|
||||
/* FIXME: save backup boot sector */
|
||||
|
||||
if (opt.stdout) {
|
||||
dump_to_stdout();
|
||||
fsync(fd_out);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
Printf("Syncing image file ...\n");
|
||||
if (fsync(fd_out) == -1)
|
||||
perr_exit("fsync");
|
||||
|
||||
if (!opt.metadata_only)
|
||||
exit(0);
|
||||
|
||||
wipe = 1;
|
||||
opt.volume = opt.output;
|
||||
mount_volume(0);
|
||||
|
||||
setup_lcn_bitmap();
|
||||
memset(&image, 0, sizeof(image));
|
||||
backup_clusters.image = ℑ
|
||||
|
||||
walk_clusters(vol, &backup_clusters);
|
||||
|
||||
Printf("Num of MFT records = %8Ld\n", vol->nr_mft_records);
|
||||
Printf("Num of used MFT records = %8d\n", nr_used_mft_records);
|
||||
|
||||
Printf("Wiped unused MFT data = %8d\n", wiped_unused_mft_data);
|
||||
Printf("Wiped deleted MFT data = %8d\n", wiped_unused_mft);
|
||||
Printf("Wiped resident user data = %8d\n", wiped_resident_data);
|
||||
Printf("Wiped timestamp data = %8d\n", wiped_timestamp_data);
|
||||
|
||||
wiped_total += wiped_unused_mft_data;
|
||||
wiped_total += wiped_unused_mft;
|
||||
wiped_total += wiped_resident_data;
|
||||
wiped_total += wiped_timestamp_data;
|
||||
Printf("Wiped totally = %8d\n", wiped_total);
|
||||
|
||||
exit(0);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user