blob: 279ebac2bdb611bab58771c9a03fc4eb4431a394
1 | /** |
2 | * ntfsdump_logfile - Part of the Linux-NTFS project. |
3 | * |
4 | * Copyright (c) 2000-2005 Anton Altaparmakov |
5 | * |
6 | * This utility will interpret the contents of the journal ($LogFile) of an |
7 | * NTFS partition and display the results on stdout. Errors will be output to |
8 | * stderr. |
9 | * |
10 | * This program is free software; you can redistribute it and/or modify |
11 | * it under the terms of the GNU General Public License as published by |
12 | * the Free Software Foundation; either version 2 of the License, or |
13 | * (at your option) any later version. |
14 | * |
15 | * This program is distributed in the hope that it will be useful, |
16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
18 | * GNU General Public License for more details. |
19 | * |
20 | * You should have received a copy of the GNU General Public License |
21 | * along with this program (in the main directory of the Linux-NTFS source |
22 | * in the file COPYING); if not, write to the Free Software Foundation, |
23 | * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
24 | */ |
25 | /* TODO: |
26 | * - Remove the need for clipping at 64MiB. |
27 | * - Add normal command line switchs (use getopt_long()). |
28 | * - For a volume: allow dumping only uncommitted records. |
29 | * - For a file: get an optional command line parameter for the last SN. |
30 | * - Sanity checks. |
31 | */ |
32 | |
33 | #include "config.h" |
34 | |
35 | #ifdef HAVE_SYS_TYPES_H |
36 | #include <sys/types.h> |
37 | #endif |
38 | #ifdef HAVE_SYS_STAT_H |
39 | #include <sys/stat.h> |
40 | #endif |
41 | #ifdef HAVE_UNISTD_H |
42 | #include <unistd.h> |
43 | #endif |
44 | #ifdef HAVE_STDARG_H |
45 | #include <stdarg.h> |
46 | #endif |
47 | #ifdef HAVE_STDLIB_H |
48 | #include <stdlib.h> |
49 | #endif |
50 | #ifdef HAVE_STDIO_H |
51 | #include <stdio.h> |
52 | #endif |
53 | #ifdef HAVE_STRING_H |
54 | #include <string.h> |
55 | #endif |
56 | #ifdef HAVE_ERRNO_H |
57 | #include <errno.h> |
58 | #endif |
59 | #ifdef HAVE_FCNTL_H |
60 | #include <fcntl.h> |
61 | #endif |
62 | |
63 | #include "types.h" |
64 | #include "endians.h" |
65 | #include "volume.h" |
66 | #include "inode.h" |
67 | #include "attrib.h" |
68 | #include "layout.h" |
69 | #include "logfile.h" |
70 | #include "mst.h" |
71 | #include "utils.h" |
72 | /* #include "version.h" */ |
73 | #include "logging.h" |
74 | |
75 | typedef struct { |
76 | BOOL is_volume; |
77 | const char *filename; |
78 | s64 data_size; |
79 | union { |
80 | struct { |
81 | ntfs_volume *vol; |
82 | ntfs_inode *ni; |
83 | ntfs_attr *na; |
84 | }; |
85 | struct { |
86 | int fd; |
87 | }; |
88 | }; |
89 | } logfile_file; |
90 | |
91 | /** |
92 | * logfile_close |
93 | */ |
94 | static int logfile_close(logfile_file *logfile) |
95 | { |
96 | if (logfile->is_volume) { |
97 | if (logfile->na) |
98 | ntfs_attr_close(logfile->na); |
99 | if (logfile->ni && ntfs_inode_close(logfile->ni)) |
100 | ntfs_log_perror("Warning: Failed to close $LogFile " |
101 | "(inode %i)", FILE_LogFile); |
102 | if (ntfs_umount(logfile->vol, 0)) |
103 | ntfs_log_perror("Warning: Failed to umount %s", |
104 | logfile->filename); |
105 | } else { |
106 | if (close(logfile->fd)) |
107 | ntfs_log_perror("Warning: Failed to close file %s", |
108 | logfile->filename); |
109 | } |
110 | return 0; |
111 | } |
112 | |
113 | /** |
114 | * device_err_exit - put an error message, cleanup and exit. |
115 | * @vol: volume to unmount. |
116 | * @ni: Inode to free. |
117 | * @na: Attribute to close. |
118 | * |
119 | * Use when you wish to exit and collate all the cleanups together. |
120 | * if you don't have some parameter to pass, just pass NULL. |
121 | */ |
122 | __attribute__((noreturn)) |
123 | __attribute__((format(printf, 4, 5))) |
124 | static void device_err_exit(ntfs_volume *vol, ntfs_inode *ni, |
125 | ntfs_attr *na, const char *fmt, ...) |
126 | { |
127 | va_list ap; |
128 | |
129 | if (na) |
130 | ntfs_attr_close(na); |
131 | if (ni && ntfs_inode_close(ni)) |
132 | ntfs_log_perror("Warning: Failed to close $LogFile (inode %i)", |
133 | FILE_LogFile); |
134 | if (ntfs_umount(vol, 0)) |
135 | ntfs_log_perror("Warning: Failed to umount"); |
136 | |
137 | fprintf(stderr, "ERROR: "); |
138 | va_start(ap, fmt); |
139 | vfprintf(stderr, fmt, ap); |
140 | va_end(ap); |
141 | |
142 | ntfs_log_error("Aborting...\n"); |
143 | exit(1); |
144 | } |
145 | |
146 | /** |
147 | * log_err_exit - |
148 | */ |
149 | __attribute__((noreturn)) |
150 | __attribute__((format(printf, 2, 3))) |
151 | static void log_err_exit(u8 *buf, const char *fmt, ...) |
152 | { |
153 | va_list ap; |
154 | |
155 | free(buf); |
156 | |
157 | fprintf(stderr, "ERROR: "); |
158 | va_start(ap, fmt); |
159 | vfprintf(stderr, fmt, ap); |
160 | va_end(ap); |
161 | |
162 | ntfs_log_error("Aborting...\n"); |
163 | exit(1); |
164 | } |
165 | |
166 | /** |
167 | * usage - |
168 | */ |
169 | __attribute__((noreturn)) |
170 | static void usage(const char *exec_name) |
171 | { |
172 | ntfs_log_error("%s v%s (libntfs-3g) - Interpret and display information " |
173 | "about the journal\n($LogFile) of an NTFS volume.\n" |
174 | "Copyright (c) 2000-2005 Anton Altaparmakov.\n" |
175 | "%s is free software, released under the GNU General " |
176 | "Public License\nand you are welcome to redistribute " |
177 | "it under certain conditions.\n%s comes with " |
178 | "ABSOLUTELY NO WARRANTY; for details read the GNU\n" |
179 | "General Public License to be found in the file " |
180 | "COPYING in the main Linux-NTFS\ndistribution " |
181 | "directory.\nUsage: %s device\n e.g. %s /dev/hda6\n" |
182 | "Alternative usage: %s -f file\n e.g. %s -f " |
183 | "MyCopyOfTheLogFile\n", exec_name, VERSION, |
184 | exec_name, exec_name, |
185 | exec_name, exec_name, exec_name, exec_name); |
186 | exit(1); |
187 | } |
188 | |
189 | /** |
190 | * logfile_open |
191 | */ |
192 | static int logfile_open(BOOL is_volume, const char *filename, |
193 | logfile_file *logfile) |
194 | { |
195 | if (is_volume) { |
196 | ntfs_volume *vol; |
197 | ntfs_inode *ni; |
198 | ntfs_attr *na; |
199 | |
200 | /* Porting note: NTFS_MNT_FORENSIC is not needed when we mount |
201 | * the volume in read-only mode. No changes will be made to the |
202 | * logfile or anything else when we are in read only-mode. */ |
203 | vol = ntfs_mount(filename, NTFS_MNT_RDONLY); |
204 | if (!vol) |
205 | log_err_exit(NULL, "Failed to mount %s: %s\n", |
206 | filename, strerror(errno)); |
207 | ntfs_log_info("Mounted NTFS volume %s (NTFS v%i.%i) on device %s.\n", |
208 | vol->vol_name ? vol->vol_name : "<NO_NAME>", |
209 | vol->major_ver, vol->minor_ver, filename); |
210 | if (ntfs_version_is_supported(vol)) |
211 | device_err_exit(vol, NULL, NULL, |
212 | "Unsupported NTFS version.\n"); |
213 | ni = ntfs_inode_open(vol, FILE_LogFile); |
214 | if (!ni) |
215 | device_err_exit(vol, NULL, NULL, "Failed to " |
216 | "open $LogFile (inode %i): %s\n", |
217 | FILE_LogFile, strerror(errno)); |
218 | na = ntfs_attr_open(ni, AT_DATA, AT_UNNAMED, 0); |
219 | if (!na) |
220 | device_err_exit(vol, ni, NULL, "Failed to open " |
221 | "$LogFile/$DATA (attribute 0x%x):" |
222 | " %s\n", (unsigned int) |
223 | le32_to_cpu(AT_DATA), strerror(errno)); |
224 | if (!na->data_size) |
225 | device_err_exit(vol, ni, na, "$LogFile has zero " |
226 | "length. Run chkdsk /f to correct " |
227 | "this.\n"); |
228 | logfile->data_size = na->data_size; |
229 | logfile->vol = vol; |
230 | logfile->ni = ni; |
231 | logfile->na = na; |
232 | } else { |
233 | struct stat sbuf; |
234 | int fd; |
235 | |
236 | if (stat(filename, &sbuf) == -1) { |
237 | if (errno == ENOENT) |
238 | log_err_exit(NULL, "The file %s does not " |
239 | "exist. Did you specify it " |
240 | "correctly?\n", filename); |
241 | log_err_exit(NULL, "Error getting information about " |
242 | "%s: %s\n", filename, strerror(errno)); |
243 | } |
244 | |
245 | fd = open(filename, O_RDONLY); |
246 | if (fd == -1) |
247 | log_err_exit(NULL, "Failed to open file %s: %s\n", |
248 | filename, strerror(errno)); |
249 | logfile->data_size = sbuf.st_size; |
250 | logfile->fd = fd; |
251 | } |
252 | |
253 | logfile->is_volume = is_volume; |
254 | logfile->filename = filename; |
255 | |
256 | return 0; |
257 | } |
258 | |
259 | /** |
260 | * logfile_read |
261 | */ |
262 | static int logfile_pread(logfile_file *logfile, int ofs, int count, u8 *buf) |
263 | { |
264 | int br; |
265 | |
266 | if (logfile->is_volume) { |
267 | br = (int)ntfs_attr_pread(logfile->na, ofs, count, buf); |
268 | } else { |
269 | if (lseek(logfile->fd, ofs, SEEK_SET)==-1) { |
270 | ntfs_log_error("Could not seek to offset %u\n", ofs); |
271 | return 0; |
272 | } |
273 | br = read(logfile->fd, buf, count); |
274 | } |
275 | if (br != count) { |
276 | ntfs_log_error("Only %d out of %d bytes read starting at %d\n", |
277 | br, count, ofs); |
278 | } |
279 | return br; |
280 | } |
281 | |
282 | /** |
283 | * restart_header_sanity() |
284 | */ |
285 | static void restart_header_sanity(RESTART_PAGE_HEADER *rstr, u8 *buf) |
286 | { |
287 | unsigned int usa_end_ofs, page_size; |
288 | |
289 | /* Only CHKD records are allowed to have chkdsk_lsn set. */ |
290 | if (!ntfs_is_chkd_record(rstr->magic) && |
291 | sle64_to_cpu(rstr->chkdsk_lsn)) |
292 | log_err_exit(buf, "$LogFile is corrupt: Restart page header " |
293 | "magic is not CHKD but a chkdsk LSN is " |
294 | "specified. Cannot handle this yet.\n"); |
295 | /* Both system and log page size must be >= 512 and a power of 2. */ |
296 | page_size = le32_to_cpu(rstr->log_page_size); |
297 | if (page_size < 512 || page_size & (page_size - 1)) |
298 | log_err_exit(buf, "$LogFile is corrupt: Restart page header " |
299 | "specifies invalid log page size. Cannot " |
300 | "handle this yet.\n"); |
301 | if (page_size != le32_to_cpu(rstr->system_page_size)) { |
302 | page_size = le32_to_cpu(rstr->system_page_size); |
303 | if (page_size < 512 || page_size & (page_size - 1)) |
304 | log_err_exit(buf, "$LogFile is corrupt: Restart page " |
305 | "header specifies invalid system page " |
306 | "size. Cannot handle this yet.\n"); |
307 | } |
308 | /* Abort if the version number is not 1.1. */ |
309 | if (sle16_to_cpu(rstr->major_ver != 1) || |
310 | sle16_to_cpu(rstr->minor_ver != 1)) |
311 | log_err_exit(buf, "Unknown $LogFile version %i.%i. Only know " |
312 | "how to handle version 1.1.\n", |
313 | sle16_to_cpu(rstr->major_ver), |
314 | sle16_to_cpu(rstr->minor_ver)); |
315 | /* Verify the location and size of the update sequence array. */ |
316 | usa_end_ofs = le16_to_cpu(rstr->usa_ofs) + |
317 | le16_to_cpu(rstr->usa_count) * sizeof(u16); |
318 | if (page_size / NTFS_BLOCK_SIZE + 1 != le16_to_cpu(rstr->usa_count)) |
319 | log_err_exit(buf, "Restart page header in $LogFile is " |
320 | "corrupt: Update sequence array size is " |
321 | "wrong. Cannot handle this yet.\n"); |
322 | if (le16_to_cpu(rstr->usa_ofs) < sizeof(RESTART_PAGE_HEADER)) |
323 | log_err_exit(buf, "Restart page header in $LogFile is " |
324 | "corrupt: Update sequence array overlaps " |
325 | "restart page header. Cannot handle this " |
326 | "yet.\n"); |
327 | if (usa_end_ofs > NTFS_BLOCK_SIZE - sizeof(u16)) |
328 | log_err_exit(buf, "Restart page header in $LogFile is " |
329 | "corrupt: Update sequence array overlaps or " |
330 | "is behind first protected sequence number. " |
331 | "Cannot handle this yet.\n"); |
332 | if (usa_end_ofs > le16_to_cpu(rstr->restart_area_offset)) |
333 | log_err_exit(buf, "Restart page header in $LogFile is " |
334 | "corrupt: Update sequence array overlaps or " |
335 | "is behind restart area. Cannot handle this " |
336 | "yet.\n"); |
337 | /* Finally, verify the offset of the restart area. */ |
338 | if (le16_to_cpu(rstr->restart_area_offset) & 7) |
339 | log_err_exit(buf, "Restart page header in $LogFile is " |
340 | "corrupt: Restart area offset is not aligned " |
341 | "to 8-byte boundary. Cannot handle this " |
342 | "yet.\n"); |
343 | } |
344 | |
345 | /** |
346 | * dump_restart_areas_header |
347 | */ |
348 | static void dump_restart_areas_header(RESTART_PAGE_HEADER *rstr) |
349 | { |
350 | ntfs_log_info("\nRestart page header:\n"); |
351 | ntfs_log_info("magic = %s\n", ntfs_is_rstr_record(rstr->magic) ? "RSTR" : |
352 | "CHKD"); |
353 | ntfs_log_info("usa_ofs = %u (0x%x)\n", le16_to_cpu(rstr->usa_ofs), |
354 | le16_to_cpu(rstr->usa_ofs)); |
355 | ntfs_log_info("usa_count = %u (0x%x)\n", le16_to_cpu(rstr->usa_count), |
356 | le16_to_cpu(rstr->usa_count)); |
357 | ntfs_log_info("chkdsk_lsn = %lli (0x%llx)\n", |
358 | (long long)sle64_to_cpu(rstr->chkdsk_lsn), |
359 | (unsigned long long)sle64_to_cpu(rstr->chkdsk_lsn)); |
360 | ntfs_log_info("system_page_size = %u (0x%x)\n", |
361 | (unsigned int)le32_to_cpu(rstr->system_page_size), |
362 | (unsigned int)le32_to_cpu(rstr->system_page_size)); |
363 | ntfs_log_info("log_page_size = %u (0x%x)\n", |
364 | (unsigned int)le32_to_cpu(rstr->log_page_size), |
365 | (unsigned int)le32_to_cpu(rstr->log_page_size)); |
366 | ntfs_log_info("restart_offset = %u (0x%x)\n", |
367 | le16_to_cpu(rstr->restart_area_offset), |
368 | le16_to_cpu(rstr->restart_area_offset)); |
369 | } |
370 | |
371 | /** |
372 | * dump_restart_areas_area |
373 | */ |
374 | static void dump_restart_areas_area(RESTART_PAGE_HEADER *rstr) |
375 | { |
376 | LOG_CLIENT_RECORD *lcr; |
377 | RESTART_AREA *ra; |
378 | int client; |
379 | |
380 | ra = (RESTART_AREA*)((u8*)rstr + |
381 | le16_to_cpu(rstr->restart_area_offset)); |
382 | ntfs_log_info("current_lsn = %lli (0x%llx)\n", |
383 | (long long)sle64_to_cpu(ra->current_lsn), |
384 | (unsigned long long)sle64_to_cpu(ra->current_lsn)); |
385 | ntfs_log_info("log_clients = %u (0x%x)\n", le16_to_cpu(ra->log_clients), |
386 | le16_to_cpu(ra->log_clients)); |
387 | ntfs_log_info("client_free_list = %i (0x%x)\n", |
388 | (s16)le16_to_cpu(ra->client_free_list), |
389 | le16_to_cpu(ra->client_free_list)); |
390 | ntfs_log_info("client_in_use_list = %i (0x%x)\n", |
391 | (s16)le16_to_cpu(ra->client_in_use_list), |
392 | le16_to_cpu(ra->client_in_use_list)); |
393 | ntfs_log_info("flags = 0x%.4x\n", le16_to_cpu(ra->flags)); |
394 | ntfs_log_info("seq_number_bits = %u (0x%x)\n", |
395 | (unsigned int)le32_to_cpu(ra->seq_number_bits), |
396 | (unsigned int)le32_to_cpu(ra->seq_number_bits)); |
397 | ntfs_log_info("restart_area_length = %u (0x%x)\n", |
398 | le16_to_cpu(ra->restart_area_length), |
399 | le16_to_cpu(ra->restart_area_length)); |
400 | ntfs_log_info("client_array_offset = %u (0x%x)\n", |
401 | le16_to_cpu(ra->client_array_offset), |
402 | le16_to_cpu(ra->client_array_offset)); |
403 | ntfs_log_info("file_size = %lli (0x%llx)\n", |
404 | (long long)sle64_to_cpu(ra->file_size), |
405 | (unsigned long long)sle64_to_cpu(ra->file_size)); |
406 | ntfs_log_info("last_lsn_data_length = %u (0x%x)\n", |
407 | (unsigned int)le32_to_cpu(ra->last_lsn_data_length), |
408 | (unsigned int)le32_to_cpu(ra->last_lsn_data_length)); |
409 | ntfs_log_info("log_record_header_length = %u (0x%x)\n", |
410 | le16_to_cpu(ra->log_record_header_length), |
411 | le16_to_cpu(ra->log_record_header_length)); |
412 | ntfs_log_info("log_page_data_offset = %u (0x%x)\n", |
413 | le16_to_cpu(ra->log_page_data_offset), |
414 | le16_to_cpu(ra->log_page_data_offset)); |
415 | ntfs_log_info("restart_log_open_count = %u (0x%x)\n", |
416 | (unsigned)le32_to_cpu(ra->restart_log_open_count), |
417 | (unsigned)le32_to_cpu(ra->restart_log_open_count)); |
418 | lcr = (LOG_CLIENT_RECORD*)((u8*)ra + |
419 | le16_to_cpu(ra->client_array_offset)); |
420 | for (client = 0; client < le16_to_cpu(ra->log_clients); client++) { |
421 | char *client_name; |
422 | |
423 | ntfs_log_info("\nLog client record number %i:\n", client + 1); |
424 | ntfs_log_info("oldest_lsn = %lli (0x%llx)\n", |
425 | (long long)sle64_to_cpu(lcr->oldest_lsn), |
426 | (unsigned long long) |
427 | sle64_to_cpu(lcr->oldest_lsn)); |
428 | ntfs_log_info("client_restart_lsn = %lli (0x%llx)\n", (long long) |
429 | sle64_to_cpu(lcr->client_restart_lsn), |
430 | (unsigned long long) |
431 | sle64_to_cpu(lcr->client_restart_lsn)); |
432 | ntfs_log_info("prev_client = %i (0x%x)\n", |
433 | (s16)le16_to_cpu(lcr->prev_client), |
434 | le16_to_cpu(lcr->prev_client)); |
435 | ntfs_log_info("next_client = %i (0x%x)\n", |
436 | (s16)le16_to_cpu(lcr->next_client), |
437 | le16_to_cpu(lcr->next_client)); |
438 | ntfs_log_info("seq_number = %u (0x%x)\n", le16_to_cpu(lcr->seq_number), |
439 | le16_to_cpu(lcr->seq_number)); |
440 | ntfs_log_info("client_name_length = %u (0x%x)\n", |
441 | (unsigned int)le32_to_cpu(lcr->client_name_length) / 2, |
442 | (unsigned int)le32_to_cpu(lcr->client_name_length) / 2); |
443 | if (le32_to_cpu(lcr->client_name_length)) { |
444 | client_name = NULL; |
445 | if (ntfs_ucstombs(lcr->client_name, |
446 | le32_to_cpu(lcr->client_name_length) / |
447 | 2, &client_name, 0) < 0) { |
448 | ntfs_log_perror("Failed to convert log client name"); |
449 | client_name = strdup("<conversion error>"); |
450 | } |
451 | } else |
452 | client_name = strdup("<unnamed>"); |
453 | ntfs_log_info("client_name = %s\n", client_name); |
454 | free(client_name); |
455 | /* |
456 | * Log client records are fixed size so we can simply use the |
457 | * C increment operator to get to the next one. |
458 | */ |
459 | lcr++; |
460 | } |
461 | } |
462 | |
463 | /** |
464 | * dump_restart_areas() |
465 | */ |
466 | static void *dump_restart_areas(RESTART_PAGE_HEADER *rstr, u8 *buf, |
467 | unsigned int page_size) |
468 | { |
469 | int pass = 1; |
470 | |
471 | rstr_pass_loc: |
472 | if (ntfs_is_chkd_record(rstr->magic)) |
473 | log_err_exit(buf, "The %s restart page header in $LogFile has " |
474 | "been modified by chkdsk. Do not know how to " |
475 | "handle this yet. Reboot into Windows to fix " |
476 | "this.\n", (u8*)rstr == buf ? "first" : |
477 | "second"); |
478 | if (ntfs_mst_post_read_fixup((NTFS_RECORD*)rstr, page_size) || |
479 | ntfs_is_baad_record(rstr->magic)) |
480 | log_err_exit(buf, "$LogFile incomplete multi sector transfer " |
481 | "detected in restart page header. Cannot " |
482 | "handle this yet.\n"); |
483 | if (pass == 1) |
484 | ntfs_log_info("$LogFile version %i.%i.\n", |
485 | sle16_to_cpu(rstr->major_ver), |
486 | sle16_to_cpu(rstr->minor_ver)); |
487 | else /* if (pass == 2) */ { |
488 | RESTART_AREA *ra; |
489 | |
490 | /* |
491 | * rstr is now the second restart page so we declare rstr1 |
492 | * as the first restart page as this one has been verified in |
493 | * the first pass so we can use all its members safely. |
494 | */ |
495 | RESTART_PAGE_HEADER *rstr1 = (RESTART_PAGE_HEADER*)buf; |
496 | |
497 | /* Exclude the usa from the comparison. */ |
498 | ra = (RESTART_AREA*)((u8*)rstr1 + |
499 | le16_to_cpu(rstr1->restart_area_offset)); |
500 | if (!memcmp(rstr1, rstr, le16_to_cpu(rstr1->usa_ofs)) && |
501 | !memcmp((u8*)rstr1 + le16_to_cpu( |
502 | rstr1->restart_area_offset), (u8*)rstr + |
503 | le16_to_cpu(rstr->restart_area_offset), |
504 | le16_to_cpu(ra->restart_area_length))) { |
505 | puts("\nSkipping analysis of second restart page " |
506 | "because it fully matches the first " |
507 | "one."); |
508 | goto skip_rstr_pass; |
509 | } |
510 | /* |
511 | * The $LogFile versions specified in each of the two restart |
512 | * page headers must match. |
513 | */ |
514 | if (rstr1->major_ver != rstr->major_ver || |
515 | rstr1->minor_ver != rstr->minor_ver) |
516 | log_err_exit(buf, "Second restart area specifies " |
517 | "different $LogFile version to first " |
518 | "restart area. Cannot handle this " |
519 | "yet.\n"); |
520 | } |
521 | /* The restart page header is in rstr and it is mst deprotected. */ |
522 | ntfs_log_info("\n%s restart page:\n", pass == 1 ? "1st" : "2nd"); |
523 | dump_restart_areas_header(rstr); |
524 | |
525 | ntfs_log_info("\nRestart area:\n"); |
526 | dump_restart_areas_area(rstr); |
527 | |
528 | skip_rstr_pass: |
529 | if (pass == 1) { |
530 | rstr = (RESTART_PAGE_HEADER*)((u8*)rstr + page_size); |
531 | ++pass; |
532 | goto rstr_pass_loc; |
533 | } |
534 | |
535 | return rstr; |
536 | } |
537 | |
538 | /** |
539 | * dump_log_records() |
540 | */ |
541 | static void dump_log_record(LOG_RECORD *lr) |
542 | { |
543 | unsigned int i; |
544 | ntfs_log_info("this lsn = 0x%llx\n", |
545 | (unsigned long long)le64_to_cpu(lr->this_lsn)); |
546 | ntfs_log_info("client previous lsn = 0x%llx\n", (unsigned long long) |
547 | le64_to_cpu(lr->client_previous_lsn)); |
548 | ntfs_log_info("client undo next lsn = 0x%llx\n", (unsigned long long) |
549 | le64_to_cpu(lr->client_undo_next_lsn)); |
550 | ntfs_log_info("client data length = 0x%x\n", |
551 | (unsigned int)le32_to_cpu(lr->client_data_length)); |
552 | ntfs_log_info("client_id.seq_number = 0x%x\n", |
553 | le16_to_cpu(lr->client_id.seq_number)); |
554 | ntfs_log_info("client_id.client_index = 0x%x\n", |
555 | le16_to_cpu(lr->client_id.client_index)); |
556 | ntfs_log_info("record type = 0x%x\n", |
557 | (unsigned int)le32_to_cpu(lr->record_type)); |
558 | ntfs_log_info("transaction_id = 0x%x\n", |
559 | (unsigned int)le32_to_cpu(lr->transaction_id)); |
560 | ntfs_log_info("flags = 0x%x:", lr->flags); |
561 | if (!lr->flags) |
562 | ntfs_log_info(" NONE\n"); |
563 | else { |
564 | int _b = 0; |
565 | |
566 | if (lr->flags & LOG_RECORD_MULTI_PAGE) { |
567 | ntfs_log_info(" LOG_RECORD_MULTI_PAGE"); |
568 | _b = 1; |
569 | } |
570 | if (lr->flags & ~LOG_RECORD_MULTI_PAGE) { |
571 | if (_b) |
572 | ntfs_log_info(" |"); |
573 | ntfs_log_info(" Unknown flags"); |
574 | } |
575 | ntfs_log_info("\n"); |
576 | } |
577 | ntfs_log_info("redo_operation = 0x%x\n", le16_to_cpu(lr->redo_operation)); |
578 | ntfs_log_info("undo_operation = 0x%x\n", le16_to_cpu(lr->undo_operation)); |
579 | ntfs_log_info("redo_offset = 0x%x\n", le16_to_cpu(lr->redo_offset)); |
580 | ntfs_log_info("redo_length = 0x%x\n", le16_to_cpu(lr->redo_length)); |
581 | ntfs_log_info("undo_offset = 0x%x\n", le16_to_cpu(lr->undo_offset)); |
582 | ntfs_log_info("undo_length = 0x%x\n", le16_to_cpu(lr->undo_length)); |
583 | ntfs_log_info("target_attribute = 0x%x\n", le16_to_cpu(lr->target_attribute)); |
584 | ntfs_log_info("lcns_to_follow = 0x%x\n", le16_to_cpu(lr->lcns_to_follow)); |
585 | ntfs_log_info("record_offset = 0x%x\n", le16_to_cpu(lr->record_offset)); |
586 | ntfs_log_info("attribute_offset = 0x%x\n", le16_to_cpu(lr->attribute_offset)); |
587 | ntfs_log_info("target_vcn = 0x%llx\n", |
588 | (unsigned long long)sle64_to_cpu(lr->target_vcn)); |
589 | if (le16_to_cpu(lr->lcns_to_follow) > 0) |
590 | ntfs_log_info("Array of lcns:\n"); |
591 | for (i = 0; i < le16_to_cpu(lr->lcns_to_follow); i++) |
592 | ntfs_log_info("lcn_list[%u].lcn = 0x%llx\n", i, (unsigned long long) |
593 | sle64_to_cpu(lr->lcn_list[i].lcn)); |
594 | } |
595 | |
596 | /** |
597 | * dump_log_records() |
598 | */ |
599 | static void dump_log_records(RECORD_PAGE_HEADER *rcrd, u8 *buf, |
600 | int buf_size, unsigned int page_size) |
601 | { |
602 | LOG_RECORD *lr; |
603 | int pass = 0; |
604 | int client; |
605 | |
606 | /* Reuse pass for log area. */ |
607 | rcrd_pass_loc: |
608 | rcrd = (RECORD_PAGE_HEADER*)((u8*)rcrd + page_size); |
609 | if ((u8*)rcrd + page_size > buf + buf_size) |
610 | return; |
611 | ntfs_log_info("\nLog record page number %i", pass); |
612 | if (!ntfs_is_rcrd_record(rcrd->magic) && |
613 | !ntfs_is_chkd_record(rcrd->magic)) { |
614 | unsigned int i; |
615 | for (i = 0; i < page_size; i++) |
616 | if (((u8*)rcrd)[i] != (u8)-1) |
617 | break; |
618 | if (i < page_size) |
619 | puts(" is corrupt (magic is not RCRD or CHKD)."); |
620 | else |
621 | puts(" is empty."); |
622 | pass++; |
623 | goto rcrd_pass_loc; |
624 | } else |
625 | puts(":"); |
626 | /* Dump log record page */ |
627 | ntfs_log_info("magic = %s\n", ntfs_is_rcrd_record(rcrd->magic) ? "RCRD" : |
628 | "CHKD"); |
629 | // TODO: I am here... (AIA) |
630 | ntfs_log_info("copy.last_lsn/file_offset = 0x%llx\n", (unsigned long long) |
631 | le64_to_cpu(rcrd->copy.last_lsn)); |
632 | ntfs_log_info("flags = 0x%x\n", (unsigned int)le32_to_cpu(rcrd->flags)); |
633 | ntfs_log_info("page count = %i\n", le16_to_cpu(rcrd->page_count)); |
634 | ntfs_log_info("page position = %i\n", le16_to_cpu(rcrd->page_position)); |
635 | ntfs_log_info("header.next_record_offset = 0x%llx\n", (unsigned long long) |
636 | le64_to_cpu(rcrd->header.packed.next_record_offset)); |
637 | ntfs_log_info("header.last_end_lsn = 0x%llx\n", (unsigned long long) |
638 | le64_to_cpu(rcrd->header.packed.last_end_lsn)); |
639 | /* |
640 | * Where does the 0x40 come from? Is it just usa_offset + |
641 | * usa_client * 2 + 7 & ~7 or is it derived from somewhere? |
642 | */ |
643 | lr = (LOG_RECORD*)((u8*)rcrd + 0x40); |
644 | client = 0; |
645 | do { |
646 | ntfs_log_info("\nLog record %i:\n", client); |
647 | dump_log_record(lr); |
648 | client++; |
649 | lr = (LOG_RECORD*)((u8*)lr + 0x70); |
650 | } while (((u8*)lr + 0x70 <= (u8*)rcrd + |
651 | le64_to_cpu(rcrd->header.packed.next_record_offset))); |
652 | |
653 | pass++; |
654 | goto rcrd_pass_loc; |
655 | } |
656 | |
657 | /** |
658 | * main - |
659 | */ |
660 | int main(int argc, char **argv) |
661 | { |
662 | RESTART_PAGE_HEADER *rstr; |
663 | RECORD_PAGE_HEADER *rcrd; |
664 | unsigned int page_size; |
665 | int buf_size, br, err; |
666 | logfile_file logfile; |
667 | u8 *buf; |
668 | |
669 | ntfs_log_set_handler(ntfs_log_handler_outerr); |
670 | |
671 | ntfs_log_info("\n"); |
672 | if (argc < 2 || argc > 3) |
673 | /* print usage and exit */ |
674 | usage(argv[0]); |
675 | /* |
676 | * If one argument, it is a device containing an NTFS volume which we |
677 | * need to mount and read the $LogFile from so we can dump its |
678 | * contents. |
679 | * |
680 | * If two arguments the first one must be "-f" and the second one is |
681 | * the path and name of the $LogFile (or copy thereof) which we need to |
682 | * read and dump the contents of. |
683 | */ |
684 | |
685 | if (argc == 2) { |
686 | logfile_open(TRUE, argv[1], &logfile); |
687 | } else /* if (argc == 3) */ { |
688 | if (strncmp(argv[1], "-f", strlen("-f"))) |
689 | usage(argv[0]); |
690 | |
691 | logfile_open(FALSE, argv[2], &logfile); |
692 | } |
693 | |
694 | buf_size = 64 * 1024 * 1024; |
695 | |
696 | if (logfile.data_size <= buf_size) |
697 | buf_size = logfile.data_size; |
698 | else |
699 | ntfs_log_error("Warning: $LogFile is too big. " |
700 | "Only analysing the first 64MiB.\n"); |
701 | |
702 | /* For simplicity we read all of $LogFile/$DATA into memory. */ |
703 | buf = malloc(buf_size); |
704 | if (!buf) { |
705 | ntfs_log_perror("Failed to allocate buffer for file data"); |
706 | logfile_close(&logfile); |
707 | exit(1); |
708 | } |
709 | |
710 | br = logfile_pread(&logfile, 0, buf_size, buf); |
711 | err = errno; |
712 | logfile_close(&logfile); |
713 | if (br != buf_size) { |
714 | log_err_exit(buf, "Failed to read $LogFile/$DATA: %s\n", |
715 | br < 0 ? strerror(err) : "Partial read."); |
716 | } |
717 | |
718 | /* |
719 | * We now have the entirety of the journal ($LogFile/$DATA or argv[2]) |
720 | * in the memory buffer buf and this has a size of buf_size. Note we |
721 | * apply a size capping at 64MiB, so if the journal is any bigger we |
722 | * only have the first 64MiB. This should not be a problem as I have |
723 | * never seen such a large $LogFile. Usually it is only a few MiB in |
724 | * size. |
725 | */ |
726 | rstr = (RESTART_PAGE_HEADER*)buf; |
727 | |
728 | /* Check for presence of restart area signature. */ |
729 | if (!ntfs_is_rstr_record(rstr->magic) && |
730 | !ntfs_is_chkd_record(rstr->magic)) { |
731 | s8 *pos = (s8*)buf; |
732 | s8 *end = pos + buf_size; |
733 | while (pos < end && *pos == -1) |
734 | pos++; |
735 | if (pos != end) |
736 | log_err_exit(buf, "$LogFile contents are corrupt " |
737 | "(magic RSTR is missing). Cannot " |
738 | "handle this yet.\n"); |
739 | /* All bytes are -1. */ |
740 | free(buf); |
741 | puts("$LogFile is not initialized."); |
742 | return 0; |
743 | } |
744 | |
745 | /* |
746 | * First, verify the restart page header for consistency. |
747 | */ |
748 | restart_header_sanity(rstr, buf); |
749 | page_size = le32_to_cpu(rstr->log_page_size); |
750 | |
751 | /* |
752 | * Second, verify the restart area itself. |
753 | */ |
754 | // TODO: Implement this. |
755 | ntfs_log_error("Warning: Sanity checking of restart area not implemented " |
756 | "yet.\n"); |
757 | /* |
758 | * Third and last, verify the array of log client records. |
759 | */ |
760 | // TODO: Implement this. |
761 | ntfs_log_error("Warning: Sanity checking of array of log client records not " |
762 | "implemented yet.\n"); |
763 | |
764 | /* |
765 | * Dump the restart headers & areas. |
766 | */ |
767 | rcrd = (RECORD_PAGE_HEADER*)dump_restart_areas(rstr, buf, page_size); |
768 | ntfs_log_info("\n\nFinished with restart pages. " |
769 | "Beginning with log pages.\n"); |
770 | |
771 | /* |
772 | * Dump the log areas. |
773 | */ |
774 | dump_log_records(rcrd, buf, buf_size, page_size); |
775 | |
776 | free(buf); |
777 | return 0; |
778 | } |
779 | |
780 |