diff --git a/ntfsprogs/ntfsundelete.8.in b/ntfsprogs/ntfsundelete.8.in index b35c2e40..a4e2b3f8 100644 --- a/ntfsprogs/ntfsundelete.8.in +++ b/ntfsprogs/ntfsundelete.8.in @@ -264,6 +264,17 @@ option. Only match files that have been altered since this time. The time must be given as number using a suffix of d, w, m, y for days, weeks, months or years ago. .TP +.BI \-T +.br +.ns +.TP +.BI \-\-truncate +If +.BR ntfsundelete +is confident about the size of a deleted file, then it will restore the file to +exactly that size. The default behaviour is to round up the size to the nearest +cluster (which will be a multiple of 512 bytes). +.TP .BI "\-u " nums .br .ns diff --git a/ntfsprogs/ntfsundelete.c b/ntfsprogs/ntfsundelete.c index 0e4f2864..5d479b57 100644 --- a/ntfsprogs/ntfsundelete.c +++ b/ntfsprogs/ntfsundelete.c @@ -223,6 +223,7 @@ static void usage (void) " -o file --output file Save with this filename\n" " -d dir --destination dir Destination directory\n" " -b num --byte num Fill missing parts with this byte\n" + " -T --truncate Truncate 100%% recoverable file to exact size.\n" "\n" " -c range --copy range Write a range of MFT records to a file\n" "\n" @@ -389,7 +390,7 @@ static int parse_time (const char *value, time_t *since) */ static int parse_options (int argc, char *argv[]) { - static const char *sopt = "-b:Cc:d:fh?m:o:p:sS:t:u:qvV"; + static const char *sopt = "-b:Cc:d:fh?m:o:p:sS:t:Tu:qvV"; static const struct option lopt[] = { { "byte", required_argument, NULL, 'b' }, { "case", no_argument, NULL, 'C' }, @@ -403,6 +404,7 @@ static int parse_options (int argc, char *argv[]) { "scan", no_argument, NULL, 's' }, { "size", required_argument, NULL, 'S' }, { "time", required_argument, NULL, 't' }, + { "truncate", no_argument, NULL, 'T' }, { "undelete", required_argument, NULL, 'u' }, { "quiet", no_argument, NULL, 'q' }, { "verbose", no_argument, NULL, 'v' }, @@ -518,6 +520,9 @@ static int parse_options (int argc, char *argv[]) err++; } break; + case 'T': + opts.truncate++; + break; case 'u': if (opts.mode == MODE_NONE) { @@ -568,7 +573,7 @@ static int parse_options (int argc, char *argv[]) switch (opts.mode) { case MODE_SCAN: - if (opts.output || opts.dest || + if (opts.output || opts.dest || opts.truncate || (opts.fillbyte != (char)-1)) { Eprintf ("Scan can only be used with --percent, " "--match, --ignore-case, --size and --time.\n"); @@ -583,16 +588,16 @@ static int parse_options (int argc, char *argv[]) if ((opts.percent != -1) || opts.match || opts.match_case || (opts.size_begin > 0) || (opts.size_end > 0)) { Eprintf ("Undelete can only be used with " - "--output, --destination and --byte.\n"); + "--output, --destination, --byte and --truncate.\n"); err++; } break; case MODE_COPY: - if ((opts.fillbyte != (char)-1) || - (opts.percent != -1) || - opts.match || opts.match_case || - (opts.size_begin > 0) || - (opts.size_end > 0)) { + if ((opts.fillbyte != (char)-1) || opts.truncate || + (opts.percent != -1) || + opts.match || opts.match_case || + (opts.size_begin > 0) || + (opts.size_end > 0)) { Eprintf ("Copy can only be used with --output and --destination.\n"); err++; } @@ -1522,6 +1527,7 @@ static int undelete_file (ntfs_volume *vol, long long inode) long long k; int result = 0; char *name; + long long cluster_count; /* I'll need this variable (see below). +mabs */ if (!vol) return 0; @@ -1537,6 +1543,20 @@ static int undelete_file (ntfs_volume *vol, long long inode) if (!buffer) goto free; + if (file->mft->flags & MFT_RECORD_IN_USE) { /* These two statement blocks were */ + Eprintf ("Record is in use by the mft\n"); /* relocated from below because */ + if (!opts.force) { /* calc_percentage() must be called */ + free_file (file); /* before dump_record() or list_record(). */ + return 0; /* Otherwise, when undeleting, a file */ + } /* will always be listed as 0% recoverable */ + Vprintf ("Forced to continue.\n"); /* even if successfully undeleted. +mabs */ + } + + if (calc_percentage (file, vol) == 0) { + Qprintf ("File has no recoverable data.\n"); + goto free; + } + if (opts.verbose) { dump_record (file); } else { @@ -1546,20 +1566,6 @@ static int undelete_file (ntfs_volume *vol, long long inode) Qprintf ("\n"); } - if (file->mft->flags & MFT_RECORD_IN_USE) { - Eprintf ("Record is in use by the mft\n"); - if (!opts.force) { - free_file (file); - return 0; - } - Vprintf ("Forced to continue.\n"); - } - - if (calc_percentage (file, vol) == 0) { - Qprintf ("File has no recoverable data.\n"); - goto free; - } - if (list_empty (&file->data)) { Qprintf ("File has no data. There is nothing to recover.\n"); goto free; @@ -1624,6 +1630,7 @@ static int undelete_file (ntfs_volume *vol, long long inode) } } + cluster_count = 0LL; for (i = 0; rl[i].length > 0; i++) { if (rl[i].lcn == LCN_RL_NOT_MAPPED) { @@ -1637,6 +1644,7 @@ static int undelete_file (ntfs_volume *vol, long long inode) close (fd); goto free; } + cluster_count++; } continue; } @@ -1676,10 +1684,35 @@ static int undelete_file (ntfs_volume *vol, long long inode) close (fd); goto free; } + cluster_count++; } } } Qprintf ("\n"); + + /* The following block of code implements the --truncate option. */ + /* Its semantics are as follows: */ + /* IF opts.truncate is set AND data stream currently being recovered is */ + /* non-resident AND data stream has no holes (100% recoverability) AND */ + /* 0 <= (data->size_alloc - data->size_data) <= vol->cluster_size AND */ + /* cluster_count * vol->cluster_size == data->size_alloc THEN file */ + /* currently being written is truncated to data->size_data bytes before */ + /* it's closed. */ + /* This multiple checks try to ensure that only files with consistent */ + /* values of size/occupied clusters are eligible for truncation. Note */ + /* that resident streams need not be truncated, since the original code */ + /* already recovers their exact length. +mabs */ + + if (opts.truncate) { + if (d->percent == 100 && d->size_alloc >= d->size_data && + (d->size_alloc - d->size_data) <= (long long)vol->cluster_size && + cluster_count * (long long)vol->cluster_size == d->size_alloc) { + if (ftruncate(fd, (off_t)d->size_data)) + Eprintf("Truncation failed: %s\n", strerror(errno)); + } else Qprintf("Truncation not performed because file has an " + "inconsistent $MFT record.\n"); + } + if (close (fd) < 0) { Eprintf ("Close failed: %s\n", strerror (errno)); } diff --git a/ntfsprogs/ntfsundelete.h b/ntfsprogs/ntfsundelete.h index d2cb4c66..2e0a2189 100644 --- a/ntfsprogs/ntfsundelete.h +++ b/ntfsprogs/ntfsundelete.h @@ -46,6 +46,7 @@ struct options { char fillbyte; /* Use for unrecoverable sections */ char *match; /* Pattern for filename matching */ int match_case; /* Case sensitive matching */ + int truncate; /* Truncate files to exact size. */ int quiet; /* Less output */ int verbose; /* Extra output */ int force; /* Override common sense */