blob: b74f4c5f172e4a02ea39a6bc51b11e3ec1ed0818
1 | /** |
2 | * ntfsundelete - Part of the Linux-NTFS project. |
3 | * |
4 | * Copyright (c) 2002-2005 Richard Russon |
5 | * Copyright (c) 2004-2005 Holger Ohmacht |
6 | * Copyright (c) 2005 Anton Altaparmakov |
7 | * Copyright (c) 2007 Yura Pakhuchiy |
8 | * Copyright (c) 2013 Jean-Pierre Andre |
9 | * |
10 | * This utility will recover deleted files from an NTFS volume. |
11 | * |
12 | * This program is free software; you can redistribute it and/or modify |
13 | * it under the terms of the GNU General Public License as published by |
14 | * the Free Software Foundation; either version 2 of the License, or |
15 | * (at your option) any later version. |
16 | * |
17 | * This program is distributed in the hope that it will be useful, |
18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
20 | * GNU General Public License for more details. |
21 | * |
22 | * You should have received a copy of the GNU General Public License |
23 | * along with this program (in the main directory of the Linux-NTFS |
24 | * distribution in the file COPYING); if not, write to the Free Software |
25 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
26 | */ |
27 | |
28 | #include "config.h" |
29 | |
30 | #ifdef HAVE_FEATURES_H |
31 | #include <features.h> |
32 | #endif |
33 | #ifdef HAVE_STDIO_H |
34 | #include <stdio.h> |
35 | #endif |
36 | #ifdef HAVE_STDLIB_H |
37 | #include <stdlib.h> |
38 | #endif |
39 | #ifdef HAVE_STRING_H |
40 | #include <string.h> |
41 | #endif |
42 | #ifdef HAVE_ERRNO_H |
43 | #include <errno.h> |
44 | #endif |
45 | #ifdef HAVE_SYS_TYPES_H |
46 | #include <sys/types.h> |
47 | #endif |
48 | #ifdef HAVE_SYS_STAT_H |
49 | #include <sys/stat.h> |
50 | #endif |
51 | #ifdef HAVE_UNISTD_H |
52 | #include <unistd.h> |
53 | #endif |
54 | #ifdef HAVE_FCNTL_H |
55 | #include <fcntl.h> |
56 | #endif |
57 | #ifdef HAVE_GETOPT_H |
58 | #include <getopt.h> |
59 | #endif |
60 | #ifdef HAVE_TIME_H |
61 | #include <time.h> |
62 | #endif |
63 | #ifdef HAVE_LIMITS_H |
64 | #include <limits.h> |
65 | #endif |
66 | #ifdef HAVE_STDARG_H |
67 | #include <stdarg.h> |
68 | #endif |
69 | #ifdef HAVE_UTIME_H |
70 | #include <utime.h> |
71 | #endif |
72 | #ifdef HAVE_REGEX_H |
73 | #include <regex.h> |
74 | #endif |
75 | |
76 | #if !defined(REG_NOERROR) || (REG_NOERROR != 0) |
77 | #define REG_NOERROR 0 |
78 | #endif |
79 | |
80 | #ifndef REG_NOMATCH |
81 | #define REG_NOMATCH 1 |
82 | #endif |
83 | |
84 | #include "ntfsundelete.h" |
85 | #include "bootsect.h" |
86 | #include "mft.h" |
87 | #include "attrib.h" |
88 | #include "layout.h" |
89 | #include "inode.h" |
90 | #include "device.h" |
91 | #include "utils.h" |
92 | #include "debug.h" |
93 | #include "ntfstime.h" |
94 | /* #include "version.h" */ |
95 | #include "logging.h" |
96 | #include "misc.h" |
97 | |
98 | #ifdef HAVE_WINDOWS_H |
99 | /* |
100 | * Replacements for functions which do not exist on Windows |
101 | */ |
102 | #define ftruncate(fd, size) ntfs_win32_ftruncate(fd, size) |
103 | #endif |
104 | |
105 | static const char *EXEC_NAME = "ntfsundelete"; |
106 | static const char *MFTFILE = "mft"; |
107 | static const char *UNNAMED = "<unnamed>"; |
108 | static const char *NONE = "<none>"; |
109 | static const char *UNKNOWN = "unknown"; |
110 | static struct options opts; |
111 | |
112 | typedef struct |
113 | { |
114 | u32 begin; |
115 | u32 end; |
116 | } range; |
117 | |
118 | static short with_regex; /* Flag Regular expression available */ |
119 | static short avoid_duplicate_printing; /* Flag No duplicate printing of file infos */ |
120 | static range *ranges; /* Array containing all Inode-Ranges for undelete */ |
121 | static long nr_entries; /* Number of range entries */ |
122 | |
123 | #ifdef HAVE_WINDOWS_H |
124 | /* |
125 | * Replacement for strftime() on Windows |
126 | * |
127 | * strftime() on Windows uses format codes different from those |
128 | * defined in C99 sect. 7.23.3.5 |
129 | * Use snprintf() instead. |
130 | */ |
131 | static int win32_strftime(char *buffer, int size, const char *format, |
132 | const struct tm *ptm) |
133 | { |
134 | int ret; |
135 | |
136 | if (!strcmp(format, "%F %R")) |
137 | ret = snprintf(buffer, size, "%4d-%02d-%02d %02d:%02d", |
138 | ptm->tm_year + 1900, ptm->tm_mon + 1, ptm->tm_mday, |
139 | ptm->tm_hour, ptm->tm_min); |
140 | else |
141 | ret = snprintf(buffer, size, "%4d-%02d-%02d", |
142 | ptm->tm_year + 1900, ptm->tm_mon + 1, ptm->tm_mday); |
143 | return (ret); |
144 | } |
145 | #define strftime(buf, sz, fmt, ptm) win32_strftime(buf, sz, fmt, ptm) |
146 | #endif |
147 | |
148 | #ifndef HAVE_REGEX_H |
149 | |
150 | /* |
151 | * Pattern matching routing for systems with no regex. |
152 | */ |
153 | |
154 | typedef struct REGEX { |
155 | ntfschar *upcase; |
156 | u32 upcase_len; |
157 | int flags; |
158 | int pattern_len; |
159 | ntfschar pattern[1]; |
160 | } *regex_t; |
161 | |
162 | enum { REG_NOSUB = 1, REG_ICASE = 2 }; |
163 | |
164 | static BOOL patmatch(regex_t *re, const ntfschar *f, int flen, |
165 | const ntfschar *p, int plen, BOOL dot) |
166 | { |
167 | regex_t pre; |
168 | BOOL ok; |
169 | BOOL anyextens; |
170 | int i; |
171 | unsigned int c; |
172 | |
173 | pre = *re; |
174 | if (pre->flags & REG_ICASE) { |
175 | while ((flen > 0) && (plen > 0) |
176 | && ((*f == *p) |
177 | || (*p == const_cpu_to_le16('?')) |
178 | || ((c = le16_to_cpu(*f)) < pre->upcase_len |
179 | ? pre->upcase[c] : c) == *p)) { |
180 | flen--; |
181 | if (*f++ == const_cpu_to_le16('.')) |
182 | dot = TRUE; |
183 | plen--; |
184 | p++; |
185 | } |
186 | } else { |
187 | while ((flen > 0) && (plen > 0) |
188 | && ((*f == *p) || (*p == const_cpu_to_le16('?')))) { |
189 | flen--; |
190 | if (*f++ == const_cpu_to_le16('.')) |
191 | dot = TRUE; |
192 | plen--; |
193 | p++; |
194 | } |
195 | } |
196 | if ((flen <= 0) && (plen <= 0)) |
197 | ok = TRUE; |
198 | else { |
199 | ok = FALSE; |
200 | plen--; |
201 | if (*p++ == const_cpu_to_le16('*')) { |
202 | /* special case "*.*" requires the end or a dot */ |
203 | anyextens = FALSE; |
204 | if ((plen == 2) |
205 | && (p[0] == const_cpu_to_le16('.')) |
206 | && (p[1] == const_cpu_to_le16('*')) |
207 | && !dot) { |
208 | for (i=0; (i<flen) && !anyextens; i++) |
209 | if (f[i] == const_cpu_to_le16('.')) |
210 | anyextens = TRUE; |
211 | } |
212 | if (!plen || anyextens) |
213 | ok = TRUE; |
214 | else |
215 | while ((flen > 0) && !ok) |
216 | if (patmatch(re,f,flen,p,plen,dot)) |
217 | ok = TRUE; |
218 | else { |
219 | flen--; |
220 | f++; |
221 | } |
222 | } |
223 | } |
224 | return (ok); |
225 | } |
226 | |
227 | static int regcomp(regex_t *re, const char *pattern, int flags) |
228 | { |
229 | regex_t pre; |
230 | ntfschar *rp; |
231 | ntfschar *p; |
232 | unsigned int c; |
233 | int lth; |
234 | int i; |
235 | |
236 | pre = (regex_t)malloc(sizeof(struct REGEX) |
237 | + strlen(pattern)*sizeof(ntfschar)); |
238 | *re = pre; |
239 | if (pre) { |
240 | pre->flags = flags; |
241 | pre->upcase_len = 0; |
242 | rp = pre->pattern; |
243 | lth = ntfs_mbstoucs(pattern, &rp); |
244 | pre->pattern_len = lth; |
245 | p = pre->pattern; |
246 | if (flags & REG_ICASE) { |
247 | for (i=0; i<lth; i++) { |
248 | c = le16_to_cpu(*p); |
249 | if (c < pre->upcase_len) |
250 | *p = pre->upcase[c]; |
251 | p++; |
252 | } |
253 | } |
254 | } |
255 | return (*re && (lth > 0) ? 0 : -1); |
256 | } |
257 | |
258 | static int regexec(regex_t *re, const ntfschar *uname, int len, |
259 | char *q __attribute__((unused)), int r __attribute__((unused))) |
260 | { |
261 | BOOL m; |
262 | |
263 | m = patmatch(re, uname, len, (*re)->pattern, (*re)->pattern_len, FALSE); |
264 | return (m ? REG_NOERROR : REG_NOMATCH); |
265 | } |
266 | |
267 | static void regfree(regex_t *re) |
268 | { |
269 | free(*re); |
270 | } |
271 | |
272 | #endif |
273 | |
274 | /** |
275 | * parse_inode_arg - parses the inode expression |
276 | * |
277 | * Parses the optarg after parameter -u for valid ranges |
278 | * |
279 | * Return: Number of correct inode specifications or -1 for error |
280 | */ |
281 | static int parse_inode_arg(void) |
282 | { |
283 | int p; |
284 | u32 range_begin; |
285 | u32 range_end; |
286 | u32 range_temp; |
287 | u32 inode; |
288 | char *opt_arg_ptr; |
289 | char *opt_arg_temp; |
290 | char *opt_arg_end1; |
291 | char *opt_arg_end2; |
292 | |
293 | /* Check whether optarg is available or not */ |
294 | nr_entries = 0; |
295 | if (optarg == NULL) |
296 | return (0); /* bailout if no optarg */ |
297 | |
298 | /* init variables */ |
299 | p = strlen(optarg); |
300 | opt_arg_ptr = optarg; |
301 | opt_arg_end1 = optarg; |
302 | opt_arg_end2 = &(optarg[p]); |
303 | |
304 | /* alloc mem for range table */ |
305 | ranges = (range *) malloc((p + 1) * sizeof(range)); |
306 | if (ranges == NULL) { |
307 | ntfs_log_error("ERROR: Couldn't alloc mem for parsing inodes!\n"); |
308 | return (-1); |
309 | } |
310 | |
311 | /* loop */ |
312 | while ((opt_arg_end1 != opt_arg_end2) && (p > 0)) { |
313 | /* Try to get inode */ |
314 | inode = strtoul(opt_arg_ptr, &opt_arg_end1, 0); |
315 | p--; |
316 | |
317 | /* invalid char at begin */ |
318 | if ((opt_arg_ptr == opt_arg_end1) || (opt_arg_ptr == opt_arg_end2)) { |
319 | ntfs_log_error("ERROR: Invalid Number: %s\n", opt_arg_ptr); |
320 | return (-1); |
321 | } |
322 | |
323 | /* RANGE - Check for range */ |
324 | if (opt_arg_end1[0] == '-') { |
325 | /* get range end */ |
326 | opt_arg_temp = opt_arg_end1; |
327 | opt_arg_end1 = & (opt_arg_temp[1]); |
328 | if (opt_arg_temp >= opt_arg_end2) { |
329 | ntfs_log_error("ERROR: Missing range end!\n"); |
330 | return (-1); |
331 | } |
332 | range_begin = inode; |
333 | |
334 | /* get count */ |
335 | range_end = strtoul(opt_arg_end1, &opt_arg_temp, 0); |
336 | if (opt_arg_temp == opt_arg_end1) { |
337 | ntfs_log_error("ERROR: Invalid Number: %s\n", opt_arg_temp); |
338 | return (-1); |
339 | } |
340 | |
341 | /* check for correct values */ |
342 | if (range_begin > range_end) { |
343 | range_temp = range_end; |
344 | range_end = range_begin; |
345 | range_begin = range_temp; |
346 | } |
347 | |
348 | /* put into struct */ |
349 | ranges[nr_entries].begin = range_begin; |
350 | ranges[nr_entries].end = range_end; |
351 | nr_entries++; |
352 | |
353 | /* Last check */ |
354 | opt_arg_ptr = & (opt_arg_temp[1]); |
355 | if (opt_arg_ptr >= opt_arg_end2) |
356 | break; |
357 | } else if (opt_arg_end1[0] == ',') { |
358 | /* SINGLE VALUE, BUT CONTINUING */ |
359 | /* put inode into range list */ |
360 | ranges[nr_entries].begin = inode; |
361 | ranges[nr_entries].end = inode; |
362 | nr_entries++; |
363 | |
364 | /* Next inode */ |
365 | opt_arg_ptr = & (opt_arg_end1[1]); |
366 | if (opt_arg_ptr >= opt_arg_end2) { |
367 | ntfs_log_error("ERROR: Missing new value at end of input!\n"); |
368 | return (-1); |
369 | } |
370 | continue; |
371 | } else { /* SINGLE VALUE, END */ |
372 | ranges[nr_entries].begin = inode; |
373 | ranges[nr_entries].end = inode; |
374 | nr_entries++; |
375 | } |
376 | } |
377 | return (nr_entries); |
378 | } |
379 | |
380 | /** |
381 | * version - Print version information about the program |
382 | * |
383 | * Print a copyright statement and a brief description of the program. |
384 | * |
385 | * Return: none |
386 | */ |
387 | static void version(void) |
388 | { |
389 | ntfs_log_info("\n%s v%s (libntfs-3g) - Recover deleted files from an " |
390 | "NTFS Volume.\n\n", EXEC_NAME, VERSION); |
391 | ntfs_log_info("Copyright (c) 2002-2005 Richard Russon\n" |
392 | "Copyright (c) 2004-2005 Holger Ohmacht\n" |
393 | "Copyright (c) 2005 Anton Altaparmakov\n" |
394 | "Copyright (c) 2007 Yura Pakhuchiy\n" |
395 | "Copyright (c) 2013 Jean-Pierre Andre\n"); |
396 | ntfs_log_info("\n%s\n%s%s\n", ntfs_gpl, ntfs_bugs, ntfs_home); |
397 | } |
398 | |
399 | /** |
400 | * usage - Print a list of the parameters to the program |
401 | * |
402 | * Print a list of the parameters and options for the program. |
403 | * |
404 | * Return: none |
405 | */ |
406 | static void usage(void) |
407 | { |
408 | ntfs_log_info("\nUsage: %s [options] device\n" |
409 | " -s, --scan Scan for files (default)\n" |
410 | " -p, --percentage NUM Minimum percentage recoverable\n" |
411 | " -m, --match PATTERN Only work on files with matching names\n" |
412 | " -C, --case Case sensitive matching\n" |
413 | " -S, --size RANGE Match files of this size\n" |
414 | " -t, --time SINCE Last referenced since this time\n" |
415 | "\n" |
416 | " -u, --undelete Undelete mode\n" |
417 | " -i, --inodes RANGE Recover these inodes\n" |
418 | //" -I, --interactive Interactive mode\n" |
419 | " -o, --output FILE Save with this filename\n" |
420 | " -O, --optimistic Undelete in-use clusters as well\n" |
421 | " -d, --destination DIR Destination directory\n" |
422 | " -b, --byte NUM Fill missing parts with this byte\n" |
423 | " -T, --truncate Truncate 100%% recoverable file to exact size.\n" |
424 | " -P, --parent Show parent directory\n" |
425 | "\n" |
426 | " -c, --copy RANGE Write a range of MFT records to a file\n" |
427 | "\n" |
428 | " -f, --force Use less caution\n" |
429 | " -q, --quiet Less output\n" |
430 | " -v, --verbose More output\n" |
431 | " -V, --version Display version information\n" |
432 | " -h, --help Display this help\n\n", |
433 | EXEC_NAME); |
434 | ntfs_log_info("%s%s\n", ntfs_bugs, ntfs_home); |
435 | } |
436 | |
437 | /** |
438 | * transform - Convert a shell style pattern to a regex |
439 | * @pattern: String to be converted |
440 | * @regex: Resulting regular expression is put here |
441 | * |
442 | * This will transform patterns, such as "*.doc" to true regular expressions. |
443 | * The function will also place '^' and '$' around the expression to make it |
444 | * behave as the user would expect |
445 | * |
446 | * Before After |
447 | * . \. |
448 | * * .* |
449 | * ? . |
450 | * |
451 | * Notes: |
452 | * The returned string must be freed by the caller. |
453 | * If transform fails, @regex will not be changed. |
454 | * |
455 | * Return: 1, Success, the string was transformed |
456 | * 0, An error occurred |
457 | */ |
458 | static int transform(const char *pattern, char **regex) |
459 | { |
460 | char *result; |
461 | int length, i; |
462 | #ifdef HAVE_REGEX_H |
463 | int j; |
464 | #endif |
465 | |
466 | if (!pattern || !regex) |
467 | return 0; |
468 | |
469 | length = strlen(pattern); |
470 | if (length < 1) { |
471 | ntfs_log_error("Pattern to transform is empty\n"); |
472 | return 0; |
473 | } |
474 | |
475 | for (i = 0; pattern[i]; i++) { |
476 | if ((pattern[i] == '*') || (pattern[i] == '.')) |
477 | length++; |
478 | } |
479 | |
480 | result = malloc(length + 3); |
481 | if (!result) { |
482 | ntfs_log_error("Couldn't allocate memory in transform()\n"); |
483 | return 0; |
484 | } |
485 | |
486 | #ifdef HAVE_REGEX_H |
487 | result[0] = '^'; |
488 | |
489 | for (i = 0, j = 1; pattern[i]; i++, j++) { |
490 | if (pattern[i] == '*') { |
491 | result[j] = '.'; |
492 | j++; |
493 | result[j] = '*'; |
494 | } else if (pattern[i] == '.') { |
495 | result[j] = '\\'; |
496 | j++; |
497 | result[j] = '.'; |
498 | } else if (pattern[i] == '?') { |
499 | result[j] = '.'; |
500 | } else { |
501 | result[j] = pattern[i]; |
502 | } |
503 | } |
504 | |
505 | result[j] = '$'; |
506 | result[j+1] = 0; |
507 | ntfs_log_debug("Pattern '%s' replaced with regex '%s'.\n", pattern, |
508 | result); |
509 | #else |
510 | strcpy(result, pattern); |
511 | #endif |
512 | |
513 | *regex = result; |
514 | return 1; |
515 | } |
516 | |
517 | /** |
518 | * parse_time - Convert a time abbreviation to seconds |
519 | * @string: The string to be converted |
520 | * @since: The absolute time referred to |
521 | * |
522 | * Strings representing times will be converted into a time_t. The numbers will |
523 | * be regarded as seconds unless suffixed. |
524 | * |
525 | * Suffix Description |
526 | * [yY] Year |
527 | * [mM] Month |
528 | * [wW] Week |
529 | * [dD] Day |
530 | * [sS] Second |
531 | * |
532 | * Therefore, passing "1W" will return the time_t representing 1 week ago. |
533 | * |
534 | * Notes: |
535 | * Only the first character of the suffix is read. |
536 | * If parse_time fails, @since will not be changed |
537 | * |
538 | * Return: 1 Success |
539 | * 0 Error, the string was malformed |
540 | */ |
541 | static int parse_time(const char *value, time_t *since) |
542 | { |
543 | long long result; |
544 | time_t now; |
545 | char *suffix = NULL; |
546 | |
547 | if (!value || !since) |
548 | return -1; |
549 | |
550 | ntfs_log_trace("Parsing time '%s' ago.\n", value); |
551 | |
552 | result = strtoll(value, &suffix, 10); |
553 | if (result < 0 || errno == ERANGE) { |
554 | ntfs_log_error("Invalid time '%s'.\n", value); |
555 | return 0; |
556 | } |
557 | |
558 | if (!suffix) { |
559 | ntfs_log_error("Internal error, strtoll didn't return a suffix.\n"); |
560 | return 0; |
561 | } |
562 | |
563 | if (strlen(suffix) > 1) { |
564 | ntfs_log_error("Invalid time suffix '%s'. Use Y, M, W, D or H.\n", suffix); |
565 | return 0; |
566 | } |
567 | |
568 | switch (suffix[0]) { |
569 | case 'y': case 'Y': result *= 12; |
570 | case 'm': case 'M': result *= 4; |
571 | case 'w': case 'W': result *= 7; |
572 | case 'd': case 'D': result *= 24; |
573 | case 'h': case 'H': result *= 3600; |
574 | case 0: |
575 | break; |
576 | |
577 | default: |
578 | ntfs_log_error("Invalid time suffix '%s'. Use Y, M, W, D or H.\n", suffix); |
579 | return 0; |
580 | } |
581 | |
582 | now = time(NULL); |
583 | |
584 | ntfs_log_debug("Time now = %lld, Time then = %lld.\n", (long long) now, |
585 | (long long) result); |
586 | *since = now - result; |
587 | return 1; |
588 | } |
589 | |
590 | /** |
591 | * parse_options - Read and validate the programs command line |
592 | * |
593 | * Read the command line, verify the syntax and parse the options. |
594 | * This function is very long, but quite simple. |
595 | * |
596 | * Return: 1 Success |
597 | * 0 Error, one or more problems |
598 | */ |
599 | static int parse_options(int argc, char *argv[]) |
600 | { |
601 | static const char *sopt = "-b:Cc:d:fh?i:m:o:OPp:sS:t:TuqvV"; |
602 | static const struct option lopt[] = { |
603 | { "byte", required_argument, NULL, 'b' }, |
604 | { "case", no_argument, NULL, 'C' }, |
605 | { "copy", required_argument, NULL, 'c' }, |
606 | { "destination", required_argument, NULL, 'd' }, |
607 | { "force", no_argument, NULL, 'f' }, |
608 | { "help", no_argument, NULL, 'h' }, |
609 | { "inodes", required_argument, NULL, 'i' }, |
610 | //{ "interactive", no_argument, NULL, 'I' }, |
611 | { "match", required_argument, NULL, 'm' }, |
612 | { "optimistic", no_argument, NULL, 'O' }, |
613 | { "output", required_argument, NULL, 'o' }, |
614 | { "parent", no_argument, NULL, 'P' }, |
615 | { "percentage", required_argument, NULL, 'p' }, |
616 | { "quiet", no_argument, NULL, 'q' }, |
617 | { "scan", no_argument, NULL, 's' }, |
618 | { "size", required_argument, NULL, 'S' }, |
619 | { "time", required_argument, NULL, 't' }, |
620 | { "truncate", no_argument, NULL, 'T' }, |
621 | { "undelete", no_argument, NULL, 'u' }, |
622 | { "verbose", no_argument, NULL, 'v' }, |
623 | { "version", no_argument, NULL, 'V' }, |
624 | { NULL, 0, NULL, 0 } |
625 | }; |
626 | |
627 | int c = -1; |
628 | char *end = NULL; |
629 | int err = 0; |
630 | int ver = 0; |
631 | int help = 0; |
632 | int levels = 0; |
633 | |
634 | opterr = 0; /* We'll handle the errors, thank you. */ |
635 | |
636 | opts.mode = MODE_NONE; |
637 | opts.uinode = -1; |
638 | opts.percent = -1; |
639 | opts.fillbyte = -1; |
640 | while ((c = getopt_long(argc, argv, sopt, lopt, NULL)) != -1) { |
641 | switch (c) { |
642 | case 1: /* A non-option argument */ |
643 | if (!opts.device) { |
644 | opts.device = argv[optind-1]; |
645 | } else { |
646 | opts.device = NULL; |
647 | err++; |
648 | } |
649 | break; |
650 | case 'b': |
651 | if (opts.fillbyte == (char)-1) { |
652 | end = NULL; |
653 | opts.fillbyte = strtol(optarg, &end, 0); |
654 | if (end && *end) |
655 | err++; |
656 | } else { |
657 | err++; |
658 | } |
659 | break; |
660 | case 'C': |
661 | opts.match_case++; |
662 | break; |
663 | case 'c': |
664 | if (opts.mode == MODE_NONE) { |
665 | if (!utils_parse_range(optarg, |
666 | &opts.mft_begin, &opts.mft_end, TRUE)) |
667 | err++; |
668 | opts.mode = MODE_COPY; |
669 | } else { |
670 | opts.mode = MODE_ERROR; |
671 | } |
672 | break; |
673 | case 'd': |
674 | if (!opts.dest) |
675 | opts.dest = optarg; |
676 | else |
677 | err++; |
678 | break; |
679 | case 'f': |
680 | opts.force++; |
681 | break; |
682 | case 'h': |
683 | case '?': |
684 | if (ntfs_log_parse_option (argv[optind-1])) |
685 | break; |
686 | help++; |
687 | break; |
688 | case 'i': |
689 | end = NULL; |
690 | /* parse inodes */ |
691 | if (parse_inode_arg() == -1) |
692 | err++; |
693 | if (end && *end) |
694 | err++; |
695 | break; |
696 | case 'm': |
697 | if (!opts.match) { |
698 | if (!transform(optarg, &opts.match)) { |
699 | err++; |
700 | } else { |
701 | /* set regex-flag on true ;) */ |
702 | with_regex= 1; |
703 | } |
704 | } else { |
705 | err++; |
706 | } |
707 | break; |
708 | case 'o': |
709 | if (!opts.output) { |
710 | opts.output = optarg; |
711 | } else { |
712 | err++; |
713 | } |
714 | break; |
715 | case 'O': |
716 | if (!opts.optimistic) { |
717 | opts.optimistic++; |
718 | } else { |
719 | err++; |
720 | } |
721 | break; |
722 | case 'P': |
723 | if (!opts.parent) { |
724 | opts.parent++; |
725 | } else { |
726 | err++; |
727 | } |
728 | break; |
729 | case 'p': |
730 | if (opts.percent == -1) { |
731 | end = NULL; |
732 | opts.percent = strtol(optarg, &end, 0); |
733 | if (end && ((*end != '%') && (*end != 0))) |
734 | err++; |
735 | } else { |
736 | err++; |
737 | } |
738 | break; |
739 | case 'q': |
740 | opts.quiet++; |
741 | ntfs_log_clear_levels(NTFS_LOG_LEVEL_QUIET); |
742 | break; |
743 | case 's': |
744 | if (opts.mode == MODE_NONE) |
745 | opts.mode = MODE_SCAN; |
746 | else |
747 | opts.mode = MODE_ERROR; |
748 | break; |
749 | case 'S': |
750 | if ((opts.size_begin > 0) || (opts.size_end > 0) || |
751 | !utils_parse_range(optarg, &opts.size_begin, |
752 | &opts.size_end, TRUE)) { |
753 | err++; |
754 | } |
755 | break; |
756 | case 't': |
757 | if (opts.since == 0) { |
758 | if (!parse_time(optarg, &opts.since)) |
759 | err++; |
760 | } else { |
761 | err++; |
762 | } |
763 | break; |
764 | case 'T': |
765 | opts.truncate++; |
766 | break; |
767 | case 'u': |
768 | if (opts.mode == MODE_NONE) { |
769 | opts.mode = MODE_UNDELETE; |
770 | } else { |
771 | opts.mode = MODE_ERROR; |
772 | } |
773 | break; |
774 | case 'v': |
775 | opts.verbose++; |
776 | ntfs_log_set_levels(NTFS_LOG_LEVEL_VERBOSE); |
777 | break; |
778 | case 'V': |
779 | ver++; |
780 | break; |
781 | default: |
782 | if (((optopt == 'b') || (optopt == 'c') || |
783 | (optopt == 'd') || (optopt == 'm') || |
784 | (optopt == 'o') || (optopt == 'p') || |
785 | (optopt == 'S') || (optopt == 't') || |
786 | (optopt == 'u')) && (!optarg)) { |
787 | ntfs_log_error("Option '%s' requires an argument.\n", argv[optind-1]); |
788 | } else { |
789 | ntfs_log_error("Unknown option '%s'.\n", argv[optind-1]); |
790 | } |
791 | err++; |
792 | break; |
793 | } |
794 | } |
795 | |
796 | /* Make sure we're in sync with the log levels */ |
797 | levels = ntfs_log_get_levels(); |
798 | if (levels & NTFS_LOG_LEVEL_VERBOSE) |
799 | opts.verbose++; |
800 | if (!(levels & NTFS_LOG_LEVEL_QUIET)) |
801 | opts.quiet++; |
802 | |
803 | if (help || ver) { |
804 | opts.quiet = 0; |
805 | } else { |
806 | if (opts.device == NULL) { |
807 | if (argc > 1) |
808 | ntfs_log_error("You must specify exactly one device.\n"); |
809 | err++; |
810 | } |
811 | |
812 | if (opts.mode == MODE_NONE) { |
813 | opts.mode = MODE_SCAN; |
814 | } |
815 | |
816 | switch (opts.mode) { |
817 | case MODE_SCAN: |
818 | if (opts.output || opts.dest || opts.truncate || |
819 | (opts.fillbyte != (char)-1)) { |
820 | ntfs_log_error("Scan can only be used with --percent, " |
821 | "--match, --ignore-case, --size and --time.\n"); |
822 | err++; |
823 | } |
824 | if (opts.match_case && !opts.match) { |
825 | ntfs_log_error("The --case option doesn't make sense without the --match option\n"); |
826 | err++; |
827 | } |
828 | break; |
829 | |
830 | case MODE_UNDELETE: |
831 | /*if ((opts.percent != -1) || (opts.size_begin > 0) || (opts.size_end > 0)) { |
832 | ntfs_log_error("Undelete can only be used with " |
833 | "--output, --destination, --byte and --truncate.\n"); |
834 | err++; |
835 | }*/ |
836 | break; |
837 | case MODE_COPY: |
838 | if ((opts.fillbyte != (char)-1) || opts.truncate || |
839 | (opts.percent != -1) || |
840 | opts.match || opts.match_case || |
841 | (opts.size_begin > 0) || |
842 | (opts.size_end > 0)) { |
843 | ntfs_log_error("Copy can only be used with --output and --destination.\n"); |
844 | err++; |
845 | } |
846 | break; |
847 | default: |
848 | ntfs_log_error("You can only select one of Scan, Undelete or Copy.\n"); |
849 | err++; |
850 | } |
851 | |
852 | if ((opts.percent < -1) || (opts.percent > 100)) { |
853 | ntfs_log_error("Percentage value must be in the range 0 - 100.\n"); |
854 | err++; |
855 | } |
856 | |
857 | if (opts.quiet) { |
858 | if (opts.verbose) { |
859 | ntfs_log_error("You may not use --quiet and --verbose at the same time.\n"); |
860 | err++; |
861 | } else if (opts.mode == MODE_SCAN) { |
862 | ntfs_log_error("You may not use --quiet when scanning a volume.\n"); |
863 | err++; |
864 | } |
865 | } |
866 | |
867 | if (opts.parent && !opts.verbose) { |
868 | ntfs_log_error("To use --parent, you must also use --verbose.\n"); |
869 | err++; |
870 | } |
871 | } |
872 | |
873 | if (opts.fillbyte == (char)-1) |
874 | opts.fillbyte = 0; |
875 | |
876 | if (ver) |
877 | version(); |
878 | if (help || err) |
879 | usage(); |
880 | |
881 | return (!err && !help && !ver); |
882 | } |
883 | |
884 | /** |
885 | * free_file - Release the resources used by a file object |
886 | * @file: The unwanted file object |
887 | * |
888 | * This will free up the memory used by a file object and iterate through the |
889 | * object's children, freeing their resources too. |
890 | * |
891 | * Return: none |
892 | */ |
893 | static void free_file(struct ufile *file) |
894 | { |
895 | struct ntfs_list_head *item, *tmp; |
896 | |
897 | if (!file) |
898 | return; |
899 | |
900 | ntfs_list_for_each_safe(item, tmp, &file->name) { |
901 | /* List of filenames */ |
902 | struct filename *f = ntfs_list_entry(item, struct filename, list); |
903 | ntfs_log_debug("freeing filename '%s'", f->name ? f->name : |
904 | NONE); |
905 | if (f->name) |
906 | free(f->name); |
907 | if (f->parent_name) { |
908 | ntfs_log_debug(" and parent filename '%s'", |
909 | f->parent_name); |
910 | free(f->parent_name); |
911 | } |
912 | ntfs_log_debug(".\n"); |
913 | free(f); |
914 | } |
915 | |
916 | ntfs_list_for_each_safe(item, tmp, &file->data) { |
917 | /* List of data streams */ |
918 | struct data *d = ntfs_list_entry(item, struct data, list); |
919 | ntfs_log_debug("Freeing data stream '%s'.\n", d->name ? |
920 | d->name : UNNAMED); |
921 | if (d->name) |
922 | free(d->name); |
923 | if (d->runlist) |
924 | free(d->runlist); |
925 | free(d); |
926 | } |
927 | |
928 | free(file->mft); |
929 | free(file); |
930 | } |
931 | |
932 | /** |
933 | * verify_parent - confirm a record is parent of a file |
934 | * @name: a filename of the file |
935 | * @rec: the mft record of the possible parent |
936 | * |
937 | * Check that @rec is the parent of the file represented by @name. |
938 | * If @rec is a directory, but it is created after @name, then we |
939 | * can't determine whether @rec is really @name's parent. |
940 | * |
941 | * Return: @rec's filename, either same name space as @name or lowest space. |
942 | * NULL if can't determine parenthood or on error. |
943 | */ |
944 | static FILE_NAME_ATTR* verify_parent(struct filename* name, MFT_RECORD* rec) |
945 | { |
946 | ATTR_RECORD *attr30; |
947 | FILE_NAME_ATTR *filename_attr = NULL, *lowest_space_name = NULL; |
948 | ntfs_attr_search_ctx *ctx; |
949 | int found_same_space = 1; |
950 | |
951 | if (!name || !rec) |
952 | return NULL; |
953 | |
954 | if (!(rec->flags & MFT_RECORD_IS_DIRECTORY)) { |
955 | return NULL; |
956 | } |
957 | |
958 | ctx = ntfs_attr_get_search_ctx(NULL, rec); |
959 | if (!ctx) { |
960 | ntfs_log_error("ERROR: Couldn't create a search context.\n"); |
961 | return NULL; |
962 | } |
963 | |
964 | attr30 = find_attribute(AT_FILE_NAME, ctx); |
965 | if (!attr30) { |
966 | return NULL; |
967 | } |
968 | |
969 | filename_attr = (FILE_NAME_ATTR*)((char*)attr30 + le16_to_cpu(attr30->value_offset)); |
970 | /* if name is older than this dir -> can't determine */ |
971 | if (ntfs2timespec(filename_attr->creation_time).tv_sec > name->date_c) { |
972 | return NULL; |
973 | } |
974 | |
975 | if (filename_attr->file_name_type != name->name_space) { |
976 | found_same_space = 0; |
977 | lowest_space_name = filename_attr; |
978 | |
979 | while (!found_same_space && (attr30 = find_attribute(AT_FILE_NAME, ctx))) { |
980 | filename_attr = (FILE_NAME_ATTR*)((char*)attr30 + le16_to_cpu(attr30->value_offset)); |
981 | |
982 | if (filename_attr->file_name_type == name->name_space) { |
983 | found_same_space = 1; |
984 | } else { |
985 | if (filename_attr->file_name_type < lowest_space_name->file_name_type) { |
986 | lowest_space_name = filename_attr; |
987 | } |
988 | } |
989 | } |
990 | } |
991 | |
992 | ntfs_attr_put_search_ctx(ctx); |
993 | |
994 | return (found_same_space ? filename_attr : lowest_space_name); |
995 | } |
996 | |
997 | /** |
998 | * get_parent_name - Find the name of a file's parent. |
999 | * @name: the filename whose parent's name to find |
1000 | */ |
1001 | static void get_parent_name(struct filename* name, ntfs_volume* vol) |
1002 | { |
1003 | ntfs_attr* mft_data; |
1004 | MFT_RECORD* rec; |
1005 | FILE_NAME_ATTR* filename_attr; |
1006 | long long inode_num; |
1007 | |
1008 | if (!name || !vol) |
1009 | return; |
1010 | |
1011 | rec = calloc(1, vol->mft_record_size); |
1012 | if (!rec) { |
1013 | ntfs_log_error("ERROR: Couldn't allocate memory in " |
1014 | "get_parent_name()\n"); |
1015 | return; |
1016 | } |
1017 | |
1018 | mft_data = ntfs_attr_open(vol->mft_ni, AT_DATA, AT_UNNAMED, 0); |
1019 | if (!mft_data) { |
1020 | ntfs_log_perror("ERROR: Couldn't open $MFT/$DATA"); |
1021 | } else { |
1022 | inode_num = MREF_LE(name->parent_mref); |
1023 | |
1024 | if (ntfs_attr_pread(mft_data, vol->mft_record_size * inode_num, |
1025 | vol->mft_record_size, rec) < 1) { |
1026 | ntfs_log_error("ERROR: Couldn't read MFT Record %lld" |
1027 | ".\n", inode_num); |
1028 | } else if ((filename_attr = verify_parent(name, rec))) { |
1029 | if (ntfs_ucstombs(filename_attr->file_name, |
1030 | filename_attr->file_name_length, |
1031 | &name->parent_name, 0) < 0) { |
1032 | ntfs_log_debug("ERROR: Couldn't translate " |
1033 | "filename to current " |
1034 | "locale.\n"); |
1035 | name->parent_name = NULL; |
1036 | } |
1037 | } |
1038 | } |
1039 | |
1040 | if (mft_data) { |
1041 | ntfs_attr_close(mft_data); |
1042 | } |
1043 | |
1044 | if (rec) { |
1045 | free(rec); |
1046 | } |
1047 | |
1048 | return; |
1049 | } |
1050 | |
1051 | /* |
1052 | * Rescue the last deleted name of a file |
1053 | * |
1054 | * Under some conditions, when a name is deleted and the MFT |
1055 | * record is shifted to reclaim the space, the name is still |
1056 | * present beyond the end of record. |
1057 | * |
1058 | * For this to be possible, the data record has to be small (less |
1059 | * than 80 bytes), and there must be no other attributes. |
1060 | * So only the names of plain unfragmented files can be rescued. |
1061 | * |
1062 | * Returns NULL when the name cannot be recovered. |
1063 | */ |
1064 | |
1065 | static struct filename *rescue_name(MFT_RECORD *mft, ntfs_attr_search_ctx *ctx) |
1066 | { |
1067 | ATTR_RECORD *rec; |
1068 | struct filename *name; |
1069 | int off_name; |
1070 | int length; |
1071 | int type; |
1072 | |
1073 | name = (struct filename*)NULL; |
1074 | ntfs_attr_reinit_search_ctx(ctx); |
1075 | rec = find_attribute(AT_DATA, ctx); |
1076 | if (rec) { |
1077 | /* |
1078 | * If the data attribute replaced the name attribute, |
1079 | * the name itself is at offset 0x58 from the data attr. |
1080 | * First be sure this location is within the unused part |
1081 | * of the MFT record, then make extra checks. |
1082 | */ |
1083 | off_name = (long)rec - (long)mft + 0x58; |
1084 | if ((off_name >= (int)le32_to_cpu(mft->bytes_in_use)) |
1085 | && ((off_name + 4) |
1086 | <= (int)le32_to_cpu(mft->bytes_allocated))) { |
1087 | length = *((char*)mft + off_name); |
1088 | type = *((char*)mft + off_name + 1); |
1089 | /* check whether the name is fully allocated */ |
1090 | if ((type <= 3) |
1091 | && (length > 0) |
1092 | && ((off_name + 2*length + 2) |
1093 | <= (int)le32_to_cpu(mft->bytes_allocated))) { |
1094 | /* create a (partial) name record */ |
1095 | name = (struct filename*) |
1096 | ntfs_calloc(sizeof(*name)); |
1097 | if (name) { |
1098 | name->uname = (ntfschar*) |
1099 | ((char*)mft + off_name + 2); |
1100 | name->uname_len = length; |
1101 | name->name_space = type; |
1102 | if (ntfs_ucstombs(name->uname, length, |
1103 | &name->name, 0) < 0) { |
1104 | free(name); |
1105 | name = (struct filename*)NULL; |
1106 | } |
1107 | } |
1108 | if (name && name->name) |
1109 | ntfs_log_verbose("Recovered file name %s\n", |
1110 | name->name); |
1111 | } |
1112 | } |
1113 | } |
1114 | return (name); |
1115 | } |
1116 | |
1117 | |
1118 | |
1119 | /** |
1120 | * get_filenames - Read an MFT Record's $FILENAME attributes |
1121 | * @file: The file object to work with |
1122 | * |
1123 | * A single file may have more than one filename. This is quite common. |
1124 | * Windows creates a short DOS name for each long name, e.g. LONGFI~1.XYZ, |
1125 | * LongFiLeName.xyZ. |
1126 | * |
1127 | * The filenames that are found are put in filename objects and added to a |
1128 | * linked list of filenames in the file object. For convenience, the unicode |
1129 | * filename is converted into the current locale and stored in the filename |
1130 | * object. |
1131 | * |
1132 | * One of the filenames is picked (the one with the lowest numbered namespace) |
1133 | * and its locale friendly name is put in pref_name. |
1134 | * |
1135 | * Return: n The number of $FILENAME attributes found |
1136 | * -1 Error |
1137 | */ |
1138 | static int get_filenames(struct ufile *file, ntfs_volume* vol) |
1139 | { |
1140 | ATTR_RECORD *rec; |
1141 | FILE_NAME_ATTR *attr; |
1142 | ntfs_attr_search_ctx *ctx; |
1143 | struct filename *name; |
1144 | int count = 0; |
1145 | int space = 4; |
1146 | |
1147 | if (!file) |
1148 | return -1; |
1149 | |
1150 | ctx = ntfs_attr_get_search_ctx(NULL, file->mft); |
1151 | if (!ctx) |
1152 | return -1; |
1153 | |
1154 | while ((rec = find_attribute(AT_FILE_NAME, ctx))) { |
1155 | /* We know this will always be resident. */ |
1156 | attr = (FILE_NAME_ATTR *)((char *)rec + |
1157 | le16_to_cpu(rec->value_offset)); |
1158 | |
1159 | name = calloc(1, sizeof(*name)); |
1160 | if (!name) { |
1161 | ntfs_log_error("ERROR: Couldn't allocate memory in " |
1162 | "get_filenames().\n"); |
1163 | count = -1; |
1164 | break; |
1165 | } |
1166 | |
1167 | name->uname = attr->file_name; |
1168 | name->uname_len = attr->file_name_length; |
1169 | name->name_space = attr->file_name_type; |
1170 | name->size_alloc = sle64_to_cpu(attr->allocated_size); |
1171 | name->size_data = sle64_to_cpu(attr->data_size); |
1172 | name->flags = attr->file_attributes; |
1173 | |
1174 | name->date_c = ntfs2timespec(attr->creation_time).tv_sec; |
1175 | name->date_a = ntfs2timespec(attr->last_data_change_time).tv_sec; |
1176 | name->date_m = ntfs2timespec(attr->last_mft_change_time).tv_sec; |
1177 | name->date_r = ntfs2timespec(attr->last_access_time).tv_sec; |
1178 | |
1179 | if (ntfs_ucstombs(name->uname, name->uname_len, &name->name, |
1180 | 0) < 0) { |
1181 | ntfs_log_debug("ERROR: Couldn't translate filename to " |
1182 | "current locale.\n"); |
1183 | } |
1184 | |
1185 | name->parent_name = NULL; |
1186 | |
1187 | if (opts.parent) { |
1188 | name->parent_mref = attr->parent_directory; |
1189 | get_parent_name(name, vol); |
1190 | } |
1191 | |
1192 | if (name->name_space < space) { |
1193 | file->pref_name = name->name; |
1194 | file->pref_pname = name->parent_name; |
1195 | space = name->name_space; |
1196 | } |
1197 | |
1198 | file->max_size = max(file->max_size, name->size_alloc); |
1199 | file->max_size = max(file->max_size, name->size_data); |
1200 | |
1201 | ntfs_list_add_tail(&name->list, &file->name); |
1202 | count++; |
1203 | } |
1204 | |
1205 | if (!count) { |
1206 | name = rescue_name(file->mft,ctx); |
1207 | if (name) { |
1208 | /* a name was recovered, get missing attributes */ |
1209 | file->pref_name = name->name; |
1210 | ntfs_attr_reinit_search_ctx(ctx); |
1211 | rec = find_attribute(AT_STANDARD_INFORMATION, ctx); |
1212 | if (rec) { |
1213 | attr = (FILE_NAME_ATTR *)((char *)rec + |
1214 | le16_to_cpu(rec->value_offset)); |
1215 | name->flags = attr->file_attributes; |
1216 | |
1217 | name->date_c = ntfs2timespec(attr->creation_time).tv_sec; |
1218 | name->date_a = ntfs2timespec(attr->last_data_change_time).tv_sec; |
1219 | name->date_m = ntfs2timespec(attr->last_mft_change_time).tv_sec; |
1220 | name->date_r = ntfs2timespec(attr->last_access_time).tv_sec; |
1221 | } |
1222 | rec = find_attribute(AT_DATA, ctx); |
1223 | if (rec) { |
1224 | attr = (FILE_NAME_ATTR *)((char *)rec + |
1225 | le16_to_cpu(rec->value_offset)); |
1226 | name->size_alloc = sle64_to_cpu(attr->allocated_size); |
1227 | name->size_data = sle64_to_cpu(attr->data_size); |
1228 | } |
1229 | ntfs_list_add_tail(&name->list, &file->name); |
1230 | count++; |
1231 | } |
1232 | } |
1233 | ntfs_attr_put_search_ctx(ctx); |
1234 | ntfs_log_debug("File has %d names.\n", count); |
1235 | return count; |
1236 | } |
1237 | |
1238 | /** |
1239 | * get_data - Read an MFT Record's $DATA attributes |
1240 | * @file: The file object to work with |
1241 | * @vol: An ntfs volume obtained from ntfs_mount |
1242 | * |
1243 | * A file may have more than one data stream. All files will have an unnamed |
1244 | * data stream which contains the file's data. Some Windows applications store |
1245 | * extra information in a separate stream. |
1246 | * |
1247 | * The streams that are found are put in data objects and added to a linked |
1248 | * list of data streams in the file object. |
1249 | * |
1250 | * Return: n The number of $FILENAME attributes found |
1251 | * -1 Error |
1252 | */ |
1253 | static int get_data(struct ufile *file, ntfs_volume *vol) |
1254 | { |
1255 | ATTR_RECORD *rec; |
1256 | ntfs_attr_search_ctx *ctx; |
1257 | int count = 0; |
1258 | struct data *data; |
1259 | |
1260 | if (!file) |
1261 | return -1; |
1262 | |
1263 | ctx = ntfs_attr_get_search_ctx(NULL, file->mft); |
1264 | if (!ctx) |
1265 | return -1; |
1266 | |
1267 | while ((rec = find_attribute(AT_DATA, ctx))) { |
1268 | data = calloc(1, sizeof(*data)); |
1269 | if (!data) { |
1270 | ntfs_log_error("ERROR: Couldn't allocate memory in " |
1271 | "get_data().\n"); |
1272 | count = -1; |
1273 | break; |
1274 | } |
1275 | |
1276 | data->resident = !rec->non_resident; |
1277 | data->compressed = (rec->flags & ATTR_IS_COMPRESSED) ? 1 : 0; |
1278 | data->encrypted = (rec->flags & ATTR_IS_ENCRYPTED) ? 1 : 0; |
1279 | |
1280 | if (rec->name_length) { |
1281 | data->uname = (ntfschar *)((char *)rec + |
1282 | le16_to_cpu(rec->name_offset)); |
1283 | data->uname_len = rec->name_length; |
1284 | |
1285 | if (ntfs_ucstombs(data->uname, data->uname_len, |
1286 | &data->name, 0) < 0) { |
1287 | ntfs_log_error("ERROR: Cannot translate name " |
1288 | "into current locale.\n"); |
1289 | } |
1290 | } |
1291 | |
1292 | if (data->resident) { |
1293 | data->size_data = le32_to_cpu(rec->value_length); |
1294 | data->data = (char*)rec + |
1295 | le16_to_cpu(rec->value_offset); |
1296 | } else { |
1297 | data->size_alloc = sle64_to_cpu(rec->allocated_size); |
1298 | data->size_data = sle64_to_cpu(rec->data_size); |
1299 | data->size_init = sle64_to_cpu(rec->initialized_size); |
1300 | data->size_vcn = sle64_to_cpu(rec->highest_vcn) + 1; |
1301 | } |
1302 | |
1303 | data->runlist = ntfs_mapping_pairs_decompress(vol, rec, NULL); |
1304 | if (!data->runlist) { |
1305 | ntfs_log_debug("Couldn't decompress the data runs.\n"); |
1306 | } |
1307 | |
1308 | file->max_size = max(file->max_size, data->size_data); |
1309 | file->max_size = max(file->max_size, data->size_init); |
1310 | |
1311 | ntfs_list_add_tail(&data->list, &file->data); |
1312 | count++; |
1313 | } |
1314 | |
1315 | ntfs_attr_put_search_ctx(ctx); |
1316 | ntfs_log_debug("File has %d data streams.\n", count); |
1317 | return count; |
1318 | } |
1319 | |
1320 | /** |
1321 | * read_record - Read an MFT record into memory |
1322 | * @vol: An ntfs volume obtained from ntfs_mount |
1323 | * @record: The record number to read |
1324 | * |
1325 | * Read the specified MFT record and gather as much information about it as |
1326 | * possible. |
1327 | * |
1328 | * Return: Pointer A ufile object containing the results |
1329 | * NULL Error |
1330 | */ |
1331 | static struct ufile * read_record(ntfs_volume *vol, long long record) |
1332 | { |
1333 | ATTR_RECORD *attr10, *attr20, *attr90; |
1334 | struct ufile *file; |
1335 | ntfs_attr *mft; |
1336 | u32 log_levels; |
1337 | |
1338 | if (!vol) |
1339 | return NULL; |
1340 | |
1341 | file = calloc(1, sizeof(*file)); |
1342 | if (!file) { |
1343 | ntfs_log_error("ERROR: Couldn't allocate memory in read_record()\n"); |
1344 | return NULL; |
1345 | } |
1346 | |
1347 | NTFS_INIT_LIST_HEAD(&file->name); |
1348 | NTFS_INIT_LIST_HEAD(&file->data); |
1349 | file->inode = record; |
1350 | |
1351 | file->mft = malloc(vol->mft_record_size); |
1352 | if (!file->mft) { |
1353 | ntfs_log_error("ERROR: Couldn't allocate memory in read_record()\n"); |
1354 | free_file(file); |
1355 | return NULL; |
1356 | } |
1357 | |
1358 | mft = ntfs_attr_open(vol->mft_ni, AT_DATA, AT_UNNAMED, 0); |
1359 | if (!mft) { |
1360 | ntfs_log_perror("ERROR: Couldn't open $MFT/$DATA"); |
1361 | free_file(file); |
1362 | return NULL; |
1363 | } |
1364 | |
1365 | if (ntfs_attr_mst_pread(mft, vol->mft_record_size * record, 1, vol->mft_record_size, file->mft) < 1) { |
1366 | ntfs_log_error("ERROR: Couldn't read MFT Record %lld.\n", record); |
1367 | ntfs_attr_close(mft); |
1368 | free_file(file); |
1369 | return NULL; |
1370 | } |
1371 | |
1372 | ntfs_attr_close(mft); |
1373 | mft = NULL; |
1374 | |
1375 | /* disable errors logging, while examining suspicious records */ |
1376 | log_levels = ntfs_log_clear_levels(NTFS_LOG_LEVEL_PERROR); |
1377 | attr10 = find_first_attribute(AT_STANDARD_INFORMATION, file->mft); |
1378 | attr20 = find_first_attribute(AT_ATTRIBUTE_LIST, file->mft); |
1379 | attr90 = find_first_attribute(AT_INDEX_ROOT, file->mft); |
1380 | |
1381 | ntfs_log_debug("Attributes present: %s %s %s.\n", attr10?"0x10":"", |
1382 | attr20?"0x20":"", attr90?"0x90":""); |
1383 | |
1384 | if (attr10) { |
1385 | STANDARD_INFORMATION *si; |
1386 | si = (STANDARD_INFORMATION *) ((char *) attr10 + le16_to_cpu(attr10->value_offset)); |
1387 | file->date = ntfs2timespec(si->last_data_change_time).tv_sec; |
1388 | } |
1389 | |
1390 | if (attr20 || !attr10) |
1391 | file->attr_list = 1; |
1392 | if (attr90) |
1393 | file->directory = 1; |
1394 | |
1395 | if (get_filenames(file, vol) < 0) { |
1396 | ntfs_log_error("ERROR: Couldn't get filenames.\n"); |
1397 | } |
1398 | if (get_data(file, vol) < 0) { |
1399 | ntfs_log_error("ERROR: Couldn't get data streams.\n"); |
1400 | } |
1401 | /* restore errors logging */ |
1402 | ntfs_log_set_levels(log_levels); |
1403 | |
1404 | return file; |
1405 | } |
1406 | |
1407 | /** |
1408 | * calc_percentage - Calculate how much of the file is recoverable |
1409 | * @file: The file object to work with |
1410 | * @vol: An ntfs volume obtained from ntfs_mount |
1411 | * |
1412 | * Read through all the $DATA streams and determine if each cluster in each |
1413 | * stream is still free disk space. This is just measuring the potential for |
1414 | * recovery. The data may have still been overwritten by a another file which |
1415 | * was then deleted. |
1416 | * |
1417 | * Files with a resident $DATA stream will have a 100% potential. |
1418 | * |
1419 | * N.B. If $DATA attribute spans more than one MFT record (i.e. badly |
1420 | * fragmented) then only the data in this segment will be used for the |
1421 | * calculation. |
1422 | * |
1423 | * N.B. Currently, compressed and encrypted files cannot be recovered, so they |
1424 | * will return 0%. |
1425 | * |
1426 | * Return: n The percentage of the file that _could_ be recovered |
1427 | * -1 Error |
1428 | */ |
1429 | static int calc_percentage(struct ufile *file, ntfs_volume *vol) |
1430 | { |
1431 | runlist_element *rl = NULL; |
1432 | struct ntfs_list_head *pos; |
1433 | struct data *data; |
1434 | long long i, j; |
1435 | long long start, end; |
1436 | int clusters_inuse, clusters_free; |
1437 | int percent = 0; |
1438 | |
1439 | if (!file || !vol) |
1440 | return -1; |
1441 | |
1442 | if (file->directory) { |
1443 | ntfs_log_debug("Found a directory: not recoverable.\n"); |
1444 | return 0; |
1445 | } |
1446 | |
1447 | if (ntfs_list_empty(&file->data)) { |
1448 | ntfs_log_verbose("File has no data streams.\n"); |
1449 | return 0; |
1450 | } |
1451 | |
1452 | ntfs_list_for_each(pos, &file->data) { |
1453 | data = ntfs_list_entry(pos, struct data, list); |
1454 | clusters_inuse = 0; |
1455 | clusters_free = 0; |
1456 | |
1457 | if (data->encrypted) { |
1458 | ntfs_log_verbose("File is encrypted, recovery is " |
1459 | "impossible.\n"); |
1460 | continue; |
1461 | } |
1462 | |
1463 | if (data->compressed) { |
1464 | ntfs_log_verbose("File is compressed, recovery not yet " |
1465 | "implemented.\n"); |
1466 | continue; |
1467 | } |
1468 | |
1469 | if (data->resident) { |
1470 | ntfs_log_verbose("File is resident, therefore " |
1471 | "recoverable.\n"); |
1472 | percent = 100; |
1473 | data->percent = 100; |
1474 | continue; |
1475 | } |
1476 | |
1477 | rl = data->runlist; |
1478 | if (!rl) { |
1479 | ntfs_log_verbose("File has no runlist, hence no data." |
1480 | "\n"); |
1481 | continue; |
1482 | } |
1483 | |
1484 | if (rl[0].length <= 0) { |
1485 | ntfs_log_verbose("File has an empty runlist, hence no " |
1486 | "data.\n"); |
1487 | continue; |
1488 | } |
1489 | |
1490 | if (rl[0].lcn == LCN_RL_NOT_MAPPED) { /* extended mft record */ |
1491 | ntfs_log_verbose("Missing segment at beginning, %lld " |
1492 | "clusters\n", (long long)rl[0].length); |
1493 | clusters_inuse += rl[0].length; |
1494 | rl++; |
1495 | } |
1496 | |
1497 | for (i = 0; rl[i].length > 0; i++) { |
1498 | if (rl[i].lcn == LCN_RL_NOT_MAPPED) { |
1499 | ntfs_log_verbose("Missing segment at end, %lld " |
1500 | "clusters\n", |
1501 | (long long)rl[i].length); |
1502 | clusters_inuse += rl[i].length; |
1503 | continue; |
1504 | } |
1505 | |
1506 | if (rl[i].lcn == LCN_HOLE) { |
1507 | clusters_free += rl[i].length; |
1508 | continue; |
1509 | } |
1510 | |
1511 | start = rl[i].lcn; |
1512 | end = rl[i].lcn + rl[i].length; |
1513 | |
1514 | for (j = start; j < end; j++) { |
1515 | if (utils_cluster_in_use(vol, j)) |
1516 | clusters_inuse++; |
1517 | else |
1518 | clusters_free++; |
1519 | } |
1520 | } |
1521 | |
1522 | if ((clusters_inuse + clusters_free) == 0) { |
1523 | ntfs_log_error("ERROR: Unexpected error whilst " |
1524 | "calculating percentage for inode %lld\n", |
1525 | file->inode); |
1526 | continue; |
1527 | } |
1528 | |
1529 | data->percent = (clusters_free * 100) / |
1530 | (clusters_inuse + clusters_free); |
1531 | |
1532 | percent = max(percent, data->percent); |
1533 | } |
1534 | |
1535 | ntfs_log_verbose("File is %d%% recoverable\n", percent); |
1536 | return percent; |
1537 | } |
1538 | |
1539 | /** |
1540 | * dump_record - Print everything we know about an MFT record |
1541 | * @file: The file to work with |
1542 | * |
1543 | * Output the contents of the file object. This will print everything that has |
1544 | * been read from the MFT record, or implied by various means. |
1545 | * |
1546 | * Because of the redundant nature of NTFS, there will be some duplication of |
1547 | * information, though it will have been read from different sources. |
1548 | * |
1549 | * N.B. If the filename is missing, or couldn't be converted to the current |
1550 | * locale, "<none>" will be displayed. |
1551 | * |
1552 | * Return: none |
1553 | */ |
1554 | static void dump_record(struct ufile *file) |
1555 | { |
1556 | char buffer[20]; |
1557 | struct ntfs_list_head *item; |
1558 | int i; |
1559 | |
1560 | if (!file) |
1561 | return; |
1562 | |
1563 | ntfs_log_quiet("MFT Record %lld\n", file->inode); |
1564 | ntfs_log_quiet("Type: %s\n", (file->directory) ? "Directory" : "File"); |
1565 | strftime(buffer, sizeof(buffer), "%F %R", localtime(&file->date)); |
1566 | ntfs_log_quiet("Date: %s\n", buffer); |
1567 | |
1568 | if (file->attr_list) |
1569 | ntfs_log_quiet("Metadata may span more than one MFT record\n"); |
1570 | |
1571 | ntfs_list_for_each(item, &file->name) { |
1572 | struct filename *f = |
1573 | ntfs_list_entry(item, struct filename, list); |
1574 | |
1575 | ntfs_log_quiet("Filename: (%d) %s\n", f->name_space, f->name); |
1576 | ntfs_log_quiet("File Flags: "); |
1577 | if (f->flags & FILE_ATTR_SYSTEM) |
1578 | ntfs_log_quiet("System "); |
1579 | if (f->flags & FILE_ATTR_DIRECTORY) |
1580 | ntfs_log_quiet("Directory "); |
1581 | if (f->flags & FILE_ATTR_SPARSE_FILE) |
1582 | ntfs_log_quiet("Sparse "); |
1583 | if (f->flags & FILE_ATTR_REPARSE_POINT) |
1584 | ntfs_log_quiet("Reparse "); |
1585 | if (f->flags & FILE_ATTR_COMPRESSED) |
1586 | ntfs_log_quiet("Compressed "); |
1587 | if (f->flags & FILE_ATTR_ENCRYPTED) |
1588 | ntfs_log_quiet("Encrypted "); |
1589 | if (!(f->flags & (FILE_ATTR_SYSTEM | FILE_ATTR_DIRECTORY | |
1590 | FILE_ATTR_SPARSE_FILE | FILE_ATTR_REPARSE_POINT | |
1591 | FILE_ATTR_COMPRESSED | FILE_ATTR_ENCRYPTED))) { |
1592 | ntfs_log_quiet("%s", NONE); |
1593 | } |
1594 | |
1595 | ntfs_log_quiet("\n"); |
1596 | |
1597 | if (opts.parent) { |
1598 | ntfs_log_quiet("Parent: %s\n", f->parent_name ? |
1599 | f->parent_name : "<non-determined>"); |
1600 | } |
1601 | |
1602 | ntfs_log_quiet("Size alloc: %lld\n", f->size_alloc); |
1603 | ntfs_log_quiet("Size data: %lld\n", f->size_data); |
1604 | |
1605 | strftime(buffer, sizeof(buffer), "%F %R", |
1606 | localtime(&f->date_c)); |
1607 | ntfs_log_quiet("Date C: %s\n", buffer); |
1608 | strftime(buffer, sizeof(buffer), "%F %R", |
1609 | localtime(&f->date_a)); |
1610 | ntfs_log_quiet("Date A: %s\n", buffer); |
1611 | strftime(buffer, sizeof(buffer), "%F %R", |
1612 | localtime(&f->date_m)); |
1613 | ntfs_log_quiet("Date M: %s\n", buffer); |
1614 | strftime(buffer, sizeof(buffer), "%F %R", |
1615 | localtime(&f->date_r)); |
1616 | ntfs_log_quiet("Date R: %s\n", buffer); |
1617 | } |
1618 | |
1619 | ntfs_log_quiet("Data Streams:\n"); |
1620 | ntfs_list_for_each(item, &file->data) { |
1621 | struct data *d = ntfs_list_entry(item, struct data, list); |
1622 | ntfs_log_quiet("Name: %s\n", (d->name) ? d->name : UNNAMED); |
1623 | ntfs_log_quiet("Flags: "); |
1624 | if (d->resident) ntfs_log_quiet("Resident\n"); |
1625 | if (d->compressed) ntfs_log_quiet("Compressed\n"); |
1626 | if (d->encrypted) ntfs_log_quiet("Encrypted\n"); |
1627 | if (!d->resident && !d->compressed && !d->encrypted) |
1628 | ntfs_log_quiet("None\n"); |
1629 | else |
1630 | ntfs_log_quiet("\n"); |
1631 | |
1632 | ntfs_log_quiet("Size alloc: %lld\n", d->size_alloc); |
1633 | ntfs_log_quiet("Size data: %lld\n", d->size_data); |
1634 | ntfs_log_quiet("Size init: %lld\n", d->size_init); |
1635 | ntfs_log_quiet("Size vcn: %lld\n", d->size_vcn); |
1636 | |
1637 | ntfs_log_quiet("Data runs:\n"); |
1638 | if ((!d->runlist) || (d->runlist[0].length <= 0)) { |
1639 | ntfs_log_quiet(" None\n"); |
1640 | } else { |
1641 | for (i = 0; d->runlist[i].length > 0; i++) { |
1642 | ntfs_log_quiet(" %lld @ %lld\n", |
1643 | (long long)d->runlist[i].length, |
1644 | (long long)d->runlist[i].lcn); |
1645 | } |
1646 | } |
1647 | |
1648 | ntfs_log_quiet("Amount potentially recoverable %d%%\n", |
1649 | d->percent); |
1650 | } |
1651 | |
1652 | ntfs_log_quiet("________________________________________\n\n"); |
1653 | } |
1654 | |
1655 | /** |
1656 | * list_record - Print a one line summary of the file |
1657 | * @file: The file to work with |
1658 | * |
1659 | * Print a one line description of a file. |
1660 | * |
1661 | * Inode Flags %age Date Time Size Filename |
1662 | * |
1663 | * The output will contain the file's inode number (MFT Record), some flags, |
1664 | * the percentage of the file that is recoverable, the last modification date, |
1665 | * the size and the filename. |
1666 | * |
1667 | * The flags are F/D = File/Directory, N/R = Data is (Non-)Resident, |
1668 | * C = Compressed, E = Encrypted, ! = Metadata may span multiple records. |
1669 | * |
1670 | * N.B. The file size is stored in many forms in several attributes. This |
1671 | * display the largest it finds. |
1672 | * |
1673 | * N.B. If the filename is missing, or couldn't be converted to the current |
1674 | * locale, "<none>" will be displayed. |
1675 | * |
1676 | * Return: none |
1677 | */ |
1678 | static void list_record(struct ufile *file) |
1679 | { |
1680 | char buffer[20]; |
1681 | struct ntfs_list_head *item; |
1682 | const char *name = NULL; |
1683 | long long size = 0; |
1684 | int percent = 0; |
1685 | |
1686 | char flagd = '.', flagr = '.', flagc = '.', flagx = '.'; |
1687 | |
1688 | strftime(buffer, sizeof(buffer), "%F %R", localtime(&file->date)); |
1689 | |
1690 | if (file->attr_list) |
1691 | flagx = '!'; |
1692 | |
1693 | if (file->directory) |
1694 | flagd = 'D'; |
1695 | else |
1696 | flagd = 'F'; |
1697 | |
1698 | ntfs_list_for_each(item, &file->data) { |
1699 | struct data *d = ntfs_list_entry(item, struct data, list); |
1700 | |
1701 | if (!d->name) { |
1702 | if (d->resident) |
1703 | flagr = 'R'; |
1704 | else |
1705 | flagr = 'N'; |
1706 | if (d->compressed) |
1707 | flagc = 'C'; |
1708 | if (d->encrypted) |
1709 | flagc = 'E'; |
1710 | |
1711 | percent = max(percent, d->percent); |
1712 | } |
1713 | |
1714 | size = max(size, d->size_data); |
1715 | size = max(size, d->size_init); |
1716 | } |
1717 | |
1718 | if (file->pref_name) |
1719 | name = file->pref_name; |
1720 | else |
1721 | name = NONE; |
1722 | |
1723 | ntfs_log_quiet("%-8lld %c%c%c%c %3d%% %s %9lld %s\n", |
1724 | file->inode, flagd, flagr, flagc, flagx, |
1725 | percent, buffer, size, name); |
1726 | |
1727 | } |
1728 | |
1729 | /** |
1730 | * name_match - Does a file have a name matching a regex |
1731 | * @re: The regular expression object |
1732 | * @file: The file to be tested |
1733 | * |
1734 | * Iterate through the file's $FILENAME attributes and compare them against the |
1735 | * regular expression, created with regcomp. |
1736 | * |
1737 | * Return: 1 There is a matching filename. |
1738 | * 0 There is no match. |
1739 | */ |
1740 | static int name_match(regex_t *re, struct ufile *file) |
1741 | { |
1742 | struct ntfs_list_head *item; |
1743 | int result; |
1744 | |
1745 | if (!re || !file) |
1746 | return 0; |
1747 | |
1748 | ntfs_list_for_each(item, &file->name) { |
1749 | struct filename *f = |
1750 | ntfs_list_entry(item, struct filename, list); |
1751 | |
1752 | if (!f->name) |
1753 | continue; |
1754 | #ifdef HAVE_REGEX_H |
1755 | result = regexec(re, f->name, 0, NULL, 0); |
1756 | #else |
1757 | result = regexec(re, f->uname, f->uname_len, NULL, 0); |
1758 | #endif |
1759 | if (result < 0) { |
1760 | ntfs_log_perror("Couldn't compare filename with regex"); |
1761 | return 0; |
1762 | } else if (result == REG_NOERROR) { |
1763 | ntfs_log_debug("Found a matching filename.\n"); |
1764 | return 1; |
1765 | } |
1766 | } |
1767 | |
1768 | ntfs_log_debug("Filename '%s' doesn't match regex.\n", file->pref_name); |
1769 | return 0; |
1770 | } |
1771 | |
1772 | /** |
1773 | * write_data - Write out a block of data |
1774 | * @fd: File descriptor to write to |
1775 | * @buffer: Data to write |
1776 | * @bufsize: Amount of data to write |
1777 | * |
1778 | * Write a block of data to a file descriptor. |
1779 | * |
1780 | * Return: -1 Error, something went wrong |
1781 | * 0 Success, all the data was written |
1782 | */ |
1783 | static unsigned int write_data(int fd, const char *buffer, |
1784 | unsigned int bufsize) |
1785 | { |
1786 | ssize_t result1, result2; |
1787 | |
1788 | if (!buffer) { |
1789 | errno = EINVAL; |
1790 | return -1; |
1791 | } |
1792 | |
1793 | result1 = write(fd, buffer, bufsize); |
1794 | if ((result1 == (ssize_t) bufsize) || (result1 < 0)) |
1795 | return result1; |
1796 | |
1797 | /* Try again with the rest of the buffer */ |
1798 | buffer += result1; |
1799 | bufsize -= result1; |
1800 | |
1801 | result2 = write(fd, buffer, bufsize); |
1802 | if (result2 < 0) |
1803 | return result1; |
1804 | |
1805 | return result1 + result2; |
1806 | } |
1807 | |
1808 | /** |
1809 | * create_pathname - Create a path/file from some components |
1810 | * @dir: Directory in which to create the file (optional) |
1811 | * @name: Filename to give the file (optional) |
1812 | * @stream: Name of the stream (optional) |
1813 | * @buffer: Store the result here |
1814 | * @bufsize: Size of buffer |
1815 | * |
1816 | * Create a filename from various pieces. The output will be of the form: |
1817 | * dir/file |
1818 | * dir/file:stream |
1819 | * file |
1820 | * file:stream |
1821 | * |
1822 | * All the components are optional. If the name is missing, "unknown" will be |
1823 | * used. If the directory is missing the file will be created in the current |
1824 | * directory. If the stream name is present it will be appended to the |
1825 | * filename, delimited by a colon. |
1826 | * |
1827 | * N.B. If the buffer isn't large enough the name will be truncated. |
1828 | * |
1829 | * Return: n Length of the allocated name |
1830 | */ |
1831 | static int create_pathname(const char *dir, const char *name, |
1832 | const char *stream, char *buffer, int bufsize) |
1833 | { |
1834 | if (!name) |
1835 | name = UNKNOWN; |
1836 | |
1837 | if (dir) |
1838 | if (stream) |
1839 | snprintf(buffer, bufsize, "%s/%s:%s", dir, name, stream); |
1840 | else |
1841 | snprintf(buffer, bufsize, "%s/%s", dir, name); |
1842 | else |
1843 | if (stream) |
1844 | snprintf(buffer, bufsize, "%s:%s", name, stream); |
1845 | else |
1846 | snprintf(buffer, bufsize, "%s", name); |
1847 | |
1848 | return strlen(buffer); |
1849 | } |
1850 | |
1851 | /** |
1852 | * open_file - Open a file to write to |
1853 | * @pathname: Path, name and stream of the file to open |
1854 | * |
1855 | * Create a file and return the file descriptor. |
1856 | * |
1857 | * N.B. If option force is given and existing file will be overwritten. |
1858 | * |
1859 | * Return: -1 Error, failed to create the file |
1860 | * n Success, this is the file descriptor |
1861 | */ |
1862 | static int open_file(const char *pathname) |
1863 | { |
1864 | int flags; |
1865 | |
1866 | ntfs_log_verbose("Creating file: %s\n", pathname); |
1867 | |
1868 | if (opts.force) |
1869 | flags = O_RDWR | O_CREAT | O_TRUNC; |
1870 | else |
1871 | flags = O_RDWR | O_CREAT | O_EXCL; |
1872 | #ifdef HAVE_WINDOWS_H |
1873 | flags ^= O_BINARY | O_RDWR | O_WRONLY; |
1874 | #endif |
1875 | |
1876 | return open(pathname, flags, S_IRUSR | S_IWUSR); |
1877 | } |
1878 | |
1879 | /** |
1880 | * set_date - Set the file's date and time |
1881 | * @pathname: Path and name of the file to alter |
1882 | * @date: Date and time to set |
1883 | * |
1884 | * Give a file a particular date and time. |
1885 | * |
1886 | * Return: 1 Success, set the file's date and time |
1887 | * 0 Error, failed to change the file's date and time |
1888 | */ |
1889 | static int set_date(const char *pathname, time_t date) |
1890 | { |
1891 | struct utimbuf ut; |
1892 | |
1893 | if (!pathname) |
1894 | return 0; |
1895 | |
1896 | ut.actime = date; |
1897 | ut.modtime = date; |
1898 | if (utime(pathname, &ut)) { |
1899 | ntfs_log_error("ERROR: Couldn't set the file's date and time\n"); |
1900 | return 0; |
1901 | } |
1902 | return 1; |
1903 | } |
1904 | |
1905 | /** |
1906 | * undelete_file - Recover a deleted file from an NTFS volume |
1907 | * @vol: An ntfs volume obtained from ntfs_mount |
1908 | * @inode: MFT Record number to be recovered |
1909 | * |
1910 | * Read an MFT Record and try an recover any data associated with it. Some of |
1911 | * the clusters may be in use; these will be filled with zeros or the fill byte |
1912 | * supplied in the options. |
1913 | * |
1914 | * Each data stream will be recovered and saved to a file. The file's name will |
1915 | * be the original filename and it will be written to the current directory. |
1916 | * Any named data stream will be saved as filename:streamname. |
1917 | * |
1918 | * The output file's name and location can be altered by using the command line |
1919 | * options. |
1920 | * |
1921 | * N.B. We cannot tell if someone has overwritten some of the data since the |
1922 | * file was deleted. |
1923 | * |
1924 | * Return: 0 Error, something went wrong |
1925 | * 1 Success, the data was recovered |
1926 | */ |
1927 | static int undelete_file(ntfs_volume *vol, long long inode) |
1928 | { |
1929 | char pathname[256]; |
1930 | char *buffer = NULL; |
1931 | unsigned int bufsize; |
1932 | struct ufile *file; |
1933 | int i, j; |
1934 | long long start, end; |
1935 | runlist_element *rl; |
1936 | struct ntfs_list_head *item; |
1937 | int fd = -1; |
1938 | long long k; |
1939 | int result = 0; |
1940 | char *name; |
1941 | long long cluster_count; /* I'll need this variable (see below). +mabs */ |
1942 | |
1943 | if (!vol) |
1944 | return 0; |
1945 | |
1946 | /* try to get record */ |
1947 | file = read_record(vol, inode); |
1948 | if (!file || !file->mft) { |
1949 | ntfs_log_error("Can't read info from mft record %lld.\n", inode); |
1950 | return 0; |
1951 | } |
1952 | |
1953 | /* if flag was not set, print file informations */ |
1954 | if (avoid_duplicate_printing == 0) { |
1955 | if (opts.verbose) { |
1956 | dump_record(file); |
1957 | } else { |
1958 | list_record(file); |
1959 | //ntfs_log_quiet("\n"); |
1960 | } |
1961 | } |
1962 | |
1963 | bufsize = vol->cluster_size; |
1964 | buffer = malloc(bufsize); |
1965 | if (!buffer) |
1966 | goto free; |
1967 | |
1968 | /* calc_percentage() must be called before dump_record() or |
1969 | * list_record(). Otherwise, when undeleting, a file will always be |
1970 | * listed as 0% recoverable even if successfully undeleted. +mabs |
1971 | */ |
1972 | if (file->mft->flags & MFT_RECORD_IN_USE) { |
1973 | ntfs_log_error("Record is in use by the mft\n"); |
1974 | if (!opts.force) { |
1975 | free(buffer); |
1976 | free_file(file); |
1977 | return 0; |
1978 | } |
1979 | ntfs_log_verbose("Forced to continue.\n"); |
1980 | } |
1981 | |
1982 | if (calc_percentage(file, vol) == 0) { |
1983 | ntfs_log_quiet("File has no recoverable data.\n"); |
1984 | goto free; |
1985 | } |
1986 | |
1987 | if (ntfs_list_empty(&file->data)) { |
1988 | ntfs_log_quiet("File has no data. There is nothing to recover.\n"); |
1989 | goto free; |
1990 | } |
1991 | |
1992 | ntfs_list_for_each(item, &file->data) { |
1993 | struct data *d = ntfs_list_entry(item, struct data, list); |
1994 | char defname[sizeof(UNKNOWN) + 25]; |
1995 | |
1996 | if (opts.output) |
1997 | name = opts.output; |
1998 | else |
1999 | if (file->pref_name) |
2000 | name = file->pref_name; |
2001 | else { |
2002 | sprintf(defname,"%s%lld",UNKNOWN, |
2003 | (long long)file->inode); |
2004 | name = defname; |
2005 | } |
2006 | |
2007 | create_pathname(opts.dest, name, d->name, pathname, sizeof(pathname)); |
2008 | if (d->resident) { |
2009 | fd = open_file(pathname); |
2010 | if (fd < 0) { |
2011 | ntfs_log_perror("Couldn't create file"); |
2012 | goto free; |
2013 | } |
2014 | |
2015 | ntfs_log_verbose("File has resident data.\n"); |
2016 | if (write_data(fd, d->data, d->size_data) < d->size_data) { |
2017 | ntfs_log_perror("Write failed"); |
2018 | close(fd); |
2019 | goto free; |
2020 | } |
2021 | |
2022 | if (close(fd) < 0) { |
2023 | ntfs_log_perror("Close failed"); |
2024 | } |
2025 | fd = -1; |
2026 | } else { |
2027 | rl = d->runlist; |
2028 | if (!rl) { |
2029 | ntfs_log_verbose("File has no runlist, hence no data.\n"); |
2030 | continue; |
2031 | } |
2032 | |
2033 | if (rl[0].length <= 0) { |
2034 | ntfs_log_verbose("File has an empty runlist, hence no data.\n"); |
2035 | continue; |
2036 | } |
2037 | |
2038 | fd = open_file(pathname); |
2039 | if (fd < 0) { |
2040 | ntfs_log_perror("Couldn't create output file"); |
2041 | goto free; |
2042 | } |
2043 | |
2044 | if (rl[0].lcn == LCN_RL_NOT_MAPPED) { /* extended mft record */ |
2045 | ntfs_log_verbose("Missing segment at beginning, %lld " |
2046 | "clusters.\n", |
2047 | (long long)rl[0].length); |
2048 | memset(buffer, opts.fillbyte, bufsize); |
2049 | for (k = 0; k < rl[0].length * vol->cluster_size; k += bufsize) { |
2050 | if (write_data(fd, buffer, bufsize) < bufsize) { |
2051 | ntfs_log_perror("Write failed"); |
2052 | close(fd); |
2053 | goto free; |
2054 | } |
2055 | } |
2056 | } |
2057 | |
2058 | cluster_count = 0LL; |
2059 | for (i = 0; rl[i].length > 0; i++) { |
2060 | |
2061 | if (rl[i].lcn == LCN_RL_NOT_MAPPED) { |
2062 | ntfs_log_verbose("Missing segment at end, " |
2063 | "%lld clusters.\n", |
2064 | (long long)rl[i].length); |
2065 | memset(buffer, opts.fillbyte, bufsize); |
2066 | for (k = 0; k < rl[i].length * vol->cluster_size; k += bufsize) { |
2067 | if (write_data(fd, buffer, bufsize) < bufsize) { |
2068 | ntfs_log_perror("Write failed"); |
2069 | close(fd); |
2070 | goto free; |
2071 | } |
2072 | cluster_count++; |
2073 | } |
2074 | continue; |
2075 | } |
2076 | |
2077 | if (rl[i].lcn == LCN_HOLE) { |
2078 | ntfs_log_verbose("File has a sparse section.\n"); |
2079 | memset(buffer, 0, bufsize); |
2080 | for (k = 0; k < rl[i].length * vol->cluster_size; k += bufsize) { |
2081 | if (write_data(fd, buffer, bufsize) < bufsize) { |
2082 | ntfs_log_perror("Write failed"); |
2083 | close(fd); |
2084 | goto free; |
2085 | } |
2086 | } |
2087 | continue; |
2088 | } |
2089 | |
2090 | start = rl[i].lcn; |
2091 | end = rl[i].lcn + rl[i].length; |
2092 | |
2093 | for (j = start; j < end; j++) { |
2094 | if (utils_cluster_in_use(vol, j) && !opts.optimistic) { |
2095 | memset(buffer, opts.fillbyte, bufsize); |
2096 | if (write_data(fd, buffer, bufsize) < bufsize) { |
2097 | ntfs_log_perror("Write failed"); |
2098 | close(fd); |
2099 | goto free; |
2100 | } |
2101 | } else { |
2102 | if (ntfs_cluster_read(vol, j, 1, buffer) < 1) { |
2103 | ntfs_log_perror("Read failed"); |
2104 | close(fd); |
2105 | goto free; |
2106 | } |
2107 | if (write_data(fd, buffer, bufsize) < bufsize) { |
2108 | ntfs_log_perror("Write failed"); |
2109 | close(fd); |
2110 | goto free; |
2111 | } |
2112 | cluster_count++; |
2113 | } |
2114 | } |
2115 | } |
2116 | ntfs_log_quiet("\n"); |
2117 | |
2118 | /* |
2119 | * The following block of code implements the --truncate option. |
2120 | * Its semantics are as follows: |
2121 | * IF opts.truncate is set AND data stream currently being recovered is |
2122 | * non-resident AND data stream has no holes (100% recoverability) AND |
2123 | * 0 <= (data->size_alloc - data->size_data) <= vol->cluster_size AND |
2124 | * cluster_count * vol->cluster_size == data->size_alloc THEN file |
2125 | * currently being written is truncated to data->size_data bytes before |
2126 | * it's closed. |
2127 | * This multiple checks try to ensure that only files with consistent |
2128 | * values of size/occupied clusters are eligible for truncation. Note |
2129 | * that resident streams need not be truncated, since the original code |
2130 | * already recovers their exact length. +mabs |
2131 | */ |
2132 | if (opts.truncate) { |
2133 | if (d->percent == 100 && d->size_alloc >= d->size_data && |
2134 | (d->size_alloc - d->size_data) <= (long long)vol->cluster_size && |
2135 | cluster_count * (long long)vol->cluster_size == d->size_alloc) { |
2136 | if (ftruncate(fd, (off_t)d->size_data)) |
2137 | ntfs_log_perror("Truncation failed"); |
2138 | } else ntfs_log_quiet("Truncation not performed because file has an " |
2139 | "inconsistent $MFT record.\n"); |
2140 | } |
2141 | |
2142 | if (close(fd) < 0) { |
2143 | ntfs_log_perror("Close failed"); |
2144 | } |
2145 | fd = -1; |
2146 | |
2147 | } |
2148 | set_date(pathname, file->date); |
2149 | if (d->name) |
2150 | ntfs_log_quiet("Undeleted '%s:%s' successfully.\n", file->pref_name, d->name); |
2151 | else |
2152 | ntfs_log_quiet("Undeleted '%s' successfully.\n", file->pref_name); |
2153 | } |
2154 | result = 1; |
2155 | free: |
2156 | if (buffer) |
2157 | free(buffer); |
2158 | free_file(file); |
2159 | return result; |
2160 | } |
2161 | |
2162 | /** |
2163 | * scan_disk - Search an NTFS volume for files that could be undeleted |
2164 | * @vol: An ntfs volume obtained from ntfs_mount |
2165 | * |
2166 | * Read through all the MFT entries looking for deleted files. For each one |
2167 | * determine how much of the data lies in unused disk space. |
2168 | * |
2169 | * The list can be filtered by name, size and date, using command line options. |
2170 | * |
2171 | * Return: -1 Error, something went wrong |
2172 | * n Success, the number of recoverable files |
2173 | */ |
2174 | static int scan_disk(ntfs_volume *vol) |
2175 | { |
2176 | s64 nr_mft_records; |
2177 | const int BUFSIZE = 8192; |
2178 | char *buffer = NULL; |
2179 | int results = 0; |
2180 | ntfs_attr *attr; |
2181 | long long size; |
2182 | long long bmpsize; |
2183 | long long i; |
2184 | int j, k, b; |
2185 | int percent; |
2186 | struct ufile *file; |
2187 | regex_t re; |
2188 | |
2189 | if (!vol) |
2190 | return -1; |
2191 | |
2192 | attr = ntfs_attr_open(vol->mft_ni, AT_BITMAP, AT_UNNAMED, 0); |
2193 | if (!attr) { |
2194 | ntfs_log_perror("ERROR: Couldn't open $MFT/$BITMAP"); |
2195 | return -1; |
2196 | } |
2197 | NVolSetNoFixupWarn(vol); |
2198 | bmpsize = attr->initialized_size; |
2199 | |
2200 | buffer = malloc(BUFSIZE); |
2201 | if (!buffer) { |
2202 | ntfs_log_error("ERROR: Couldn't allocate memory in scan_disk()\n"); |
2203 | results = -1; |
2204 | goto out; |
2205 | } |
2206 | |
2207 | if (opts.match) { |
2208 | int flags = REG_NOSUB; |
2209 | |
2210 | if (!opts.match_case) |
2211 | flags |= REG_ICASE; |
2212 | if (regcomp(&re, opts.match, flags)) { |
2213 | ntfs_log_error("ERROR: Couldn't create a regex.\n"); |
2214 | goto out; |
2215 | } |
2216 | #ifndef HAVE_REGEX_H |
2217 | re->upcase = vol->upcase; |
2218 | re->upcase_len = vol->upcase_len; |
2219 | #endif |
2220 | } |
2221 | |
2222 | nr_mft_records = vol->mft_na->initialized_size >> |
2223 | vol->mft_record_size_bits; |
2224 | |
2225 | ntfs_log_quiet("Inode Flags %%age Date Time Size Filename\n"); |
2226 | ntfs_log_quiet("-----------------------------------------------------------------------\n"); |
2227 | for (i = 0; i < bmpsize; i += BUFSIZE) { |
2228 | long long read_count = min((bmpsize - i), BUFSIZE); |
2229 | size = ntfs_attr_pread(attr, i, read_count, buffer); |
2230 | if (size < 0) |
2231 | break; |
2232 | |
2233 | for (j = 0; j < size; j++) { |
2234 | b = buffer[j]; |
2235 | for (k = 0; k < 8; k++, b>>=1) { |
2236 | if (((i+j)*8+k) >= nr_mft_records) |
2237 | goto done; |
2238 | if (b & 1) |
2239 | continue; |
2240 | file = read_record(vol, (i+j)*8+k); |
2241 | if (!file) { |
2242 | ntfs_log_error("Couldn't read MFT Record %lld.\n", |
2243 | (long long)(i+j)*8+k); |
2244 | continue; |
2245 | } |
2246 | |
2247 | if ((opts.since > 0) && (file->date <= opts.since)) |
2248 | goto skip; |
2249 | if (opts.match && !name_match(&re, file)) |
2250 | goto skip; |
2251 | if (opts.size_begin && (opts.size_begin > file->max_size)) |
2252 | goto skip; |
2253 | if (opts.size_end && (opts.size_end < file->max_size)) |
2254 | goto skip; |
2255 | |
2256 | percent = calc_percentage(file, vol); |
2257 | if ((opts.percent == -1) || (percent >= opts.percent)) { |
2258 | if (opts.verbose) |
2259 | dump_record(file); |
2260 | else |
2261 | list_record(file); |
2262 | |
2263 | /* Was -u specified with no inode |
2264 | so undelete file by regex */ |
2265 | if (opts.mode == MODE_UNDELETE) { |
2266 | if (!undelete_file(vol, file->inode)) |
2267 | ntfs_log_verbose("ERROR: Failed to undelete " |
2268 | "inode %lli\n!", |
2269 | file->inode); |
2270 | ntfs_log_info("\n"); |
2271 | } |
2272 | } |
2273 | if (((opts.percent == -1) && (percent > 0)) || |
2274 | ((opts.percent > 0) && (percent >= opts.percent))) { |
2275 | results++; |
2276 | } |
2277 | skip: |
2278 | free_file(file); |
2279 | } |
2280 | } |
2281 | } |
2282 | done: |
2283 | ntfs_log_quiet("\nFiles with potentially recoverable content: %d\n", |
2284 | results); |
2285 | out: |
2286 | if (opts.match) |
2287 | regfree(&re); |
2288 | free(buffer); |
2289 | NVolClearNoFixupWarn(vol); |
2290 | if (attr) |
2291 | ntfs_attr_close(attr); |
2292 | return results; |
2293 | } |
2294 | |
2295 | /** |
2296 | * copy_mft - Write a range of MFT Records to a file |
2297 | * @vol: An ntfs volume obtained from ntfs_mount |
2298 | * @mft_begin: First MFT Record to save |
2299 | * @mft_end: Last MFT Record to save |
2300 | * |
2301 | * Read a number of MFT Records and write them to a file. |
2302 | * |
2303 | * Return: 0 Success, all the records were written |
2304 | * 1 Error, something went wrong |
2305 | */ |
2306 | static int copy_mft(ntfs_volume *vol, long long mft_begin, long long mft_end) |
2307 | { |
2308 | s64 nr_mft_records; |
2309 | char pathname[256]; |
2310 | ntfs_attr *mft; |
2311 | char *buffer; |
2312 | const char *name; |
2313 | long long i; |
2314 | int result = 1; |
2315 | int fd; |
2316 | |
2317 | if (!vol) |
2318 | return 1; |
2319 | |
2320 | if (mft_end < mft_begin) { |
2321 | ntfs_log_error("Range to copy is backwards.\n"); |
2322 | return 1; |
2323 | } |
2324 | |
2325 | buffer = malloc(vol->mft_record_size); |
2326 | if (!buffer) { |
2327 | ntfs_log_error("Couldn't allocate memory in copy_mft()\n"); |
2328 | return 1; |
2329 | } |
2330 | |
2331 | mft = ntfs_attr_open(vol->mft_ni, AT_DATA, AT_UNNAMED, 0); |
2332 | if (!mft) { |
2333 | ntfs_log_perror("Couldn't open $MFT/$DATA"); |
2334 | goto free; |
2335 | } |
2336 | |
2337 | name = opts.output; |
2338 | if (!name) { |
2339 | name = MFTFILE; |
2340 | ntfs_log_debug("No output filename, defaulting to '%s'.\n", |
2341 | name); |
2342 | } |
2343 | |
2344 | create_pathname(opts.dest, name, NULL, pathname, sizeof(pathname)); |
2345 | fd = open_file(pathname); |
2346 | if (fd < 0) { |
2347 | ntfs_log_perror("Couldn't open output file '%s'", name); |
2348 | goto attr; |
2349 | } |
2350 | |
2351 | nr_mft_records = vol->mft_na->initialized_size >> |
2352 | vol->mft_record_size_bits; |
2353 | |
2354 | mft_end = min(mft_end, nr_mft_records - 1); |
2355 | |
2356 | ntfs_log_debug("MFT records:\n"); |
2357 | ntfs_log_debug("\tTotal: %8lld\n", nr_mft_records); |
2358 | ntfs_log_debug("\tBegin: %8lld\n", mft_begin); |
2359 | ntfs_log_debug("\tEnd: %8lld\n", mft_end); |
2360 | |
2361 | for (i = mft_begin; i <= mft_end; i++) { |
2362 | if (ntfs_attr_pread(mft, vol->mft_record_size * i, |
2363 | vol->mft_record_size, buffer) < vol->mft_record_size) { |
2364 | ntfs_log_perror("Couldn't read MFT Record %lld", i); |
2365 | goto close; |
2366 | } |
2367 | |
2368 | if (write_data(fd, buffer, vol->mft_record_size) < vol->mft_record_size) { |
2369 | ntfs_log_perror("Write failed"); |
2370 | goto close; |
2371 | } |
2372 | } |
2373 | |
2374 | ntfs_log_verbose("Read %lld MFT Records\n", mft_end - mft_begin + 1); |
2375 | result = 0; |
2376 | close: |
2377 | close(fd); |
2378 | attr: |
2379 | ntfs_attr_close(mft); |
2380 | free: |
2381 | free(buffer); |
2382 | return result; |
2383 | } |
2384 | |
2385 | /** |
2386 | * handle_undelete |
2387 | * |
2388 | * Handles the undelete |
2389 | */ |
2390 | static int handle_undelete(ntfs_volume *vol) |
2391 | { |
2392 | int result = 1; |
2393 | int i; |
2394 | unsigned long long inode; |
2395 | |
2396 | /* Check whether (an) inode(s) was specified or at least a regex! */ |
2397 | if (nr_entries == 0) { |
2398 | if (with_regex == 0) { |
2399 | ntfs_log_error("ERROR: NO inode(s) AND NO match-regex " |
2400 | "specified!\n"); |
2401 | } else { |
2402 | avoid_duplicate_printing= 1; |
2403 | result = !scan_disk(vol); |
2404 | if (result) |
2405 | ntfs_log_verbose("ERROR: Failed to scan device " |
2406 | "'%s'.\n", opts.device); |
2407 | } |
2408 | } else { |
2409 | /* Normal undelete by specifying inode(s) */ |
2410 | ntfs_log_quiet("Inode Flags %%age Date Size Filename\n"); |
2411 | ntfs_log_quiet("---------------------------------------------------------------\n"); |
2412 | |
2413 | /* loop all given inodes */ |
2414 | for (i = 0; i < nr_entries; i++) { |
2415 | for (inode = ranges[i].begin; inode <= ranges[i].end; inode ++) { |
2416 | /* Now undelete file */ |
2417 | result = !undelete_file(vol, inode); |
2418 | if (result) |
2419 | ntfs_log_verbose("ERROR: Failed to " |
2420 | "undelete inode %lli\n!", inode); |
2421 | } |
2422 | } |
2423 | } |
2424 | return (result); |
2425 | } |
2426 | |
2427 | /** |
2428 | * main - Begin here |
2429 | * |
2430 | * Start from here. |
2431 | * |
2432 | * Return: 0 Success, the program worked |
2433 | * 1 Error, something went wrong |
2434 | */ |
2435 | int main(int argc, char *argv[]) |
2436 | { |
2437 | ntfs_volume *vol; |
2438 | int result = 1; |
2439 | |
2440 | ntfs_log_set_handler(ntfs_log_handler_outerr); |
2441 | |
2442 | with_regex = 0; |
2443 | avoid_duplicate_printing = 0; |
2444 | |
2445 | if (!parse_options(argc, argv)) |
2446 | goto free; |
2447 | |
2448 | utils_set_locale(); |
2449 | |
2450 | vol = utils_mount_volume(opts.device, NTFS_MNT_RDONLY | |
2451 | (opts.force ? NTFS_MNT_RECOVER : 0)); |
2452 | if (!vol) |
2453 | return 1; |
2454 | |
2455 | /* handling of the different modes */ |
2456 | switch (opts.mode) { |
2457 | /* Scanning */ |
2458 | case MODE_SCAN: |
2459 | result = !scan_disk(vol); |
2460 | if (result) |
2461 | ntfs_log_verbose("ERROR: Failed to scan device '%s'.\n", |
2462 | opts.device); |
2463 | break; |
2464 | |
2465 | /* Undelete-handling */ |
2466 | case MODE_UNDELETE: |
2467 | result= handle_undelete(vol); |
2468 | break; |
2469 | |
2470 | /* Handling of copy mft */ |
2471 | case MODE_COPY: |
2472 | result = !copy_mft(vol, opts.mft_begin, opts.mft_end); |
2473 | if (result) |
2474 | ntfs_log_verbose("ERROR: Failed to read MFT blocks " |
2475 | "%lld-%lld.\n", (long long)opts.mft_begin, |
2476 | (long long)min((vol->mft_na->initialized_size >> |
2477 | vol->mft_record_size_bits) , opts.mft_end)); |
2478 | break; |
2479 | default: |
2480 | ; /* Cannot happen */ |
2481 | } |
2482 | |
2483 | ntfs_umount(vol, FALSE); |
2484 | free: |
2485 | if (opts.match) |
2486 | free(opts.match); |
2487 | |
2488 | return result; |
2489 | } |
2490 | |
2491 |