From 274b89cec9108649450196553ea0486f067824ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Pierre=20Andr=C3=A9?= Date: Fri, 17 Apr 2015 09:03:08 +0200 Subject: [PATCH 01/61] Avoided truncation in a debug message in secaudit When compiled for Windows 64-bit, pointers have to be displayed as "long long" instead of "long" which is 32-bit --- src/secaudit.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/secaudit.c b/src/secaudit.c index 8ffb2a91..c5aa7e3a 100644 --- a/src/secaudit.c +++ b/src/secaudit.c @@ -7272,9 +7272,15 @@ void dumpalloc(const char *txt) if (firstalloc) { printf("alloc table at %s\n",txt); for (q=firstalloc; q; q=q->next) +#ifdef __x86_64__ + printf("%08llx : %u bytes at %08llx allocated at %s line %d\n", + (long long)q,(unsigned int)q->size, + (long long)q->alloc,q->file,q->line); +#else printf("%08lx : %u bytes at %08lx allocated at %s line %d\n", (long)q,(unsigned int)q->size, (long)q->alloc,q->file,q->line); +#endif } } @@ -7364,7 +7370,13 @@ BOOL chkisalloc(void *p, const char *file, int line) } else q = (struct CHKALLOC*)NULL; if (!p || !q) { - printf("error in %s %d : 0x%lx not allocated\n",file,line,(long)p); +#ifdef __x86_64__ + printf("error in %s %d : 0x%llx not allocated\n",file,line, + (long long)p); +#else + printf("error in %s %d : 0x%lx not allocated\n",file,line, + (long)p); +#endif } return (p && q); } From bbeebd5a1576d821a53443d4588239631c4cf06a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Pierre=20Andr=C3=A9?= Date: Fri, 17 Apr 2015 09:06:22 +0200 Subject: [PATCH 02/61] Rephrased the warning for trimming not supported (cosmetic) The initial text looked like an error message --- libntfs-3g/ioctl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libntfs-3g/ioctl.c b/libntfs-3g/ioctl.c index eb7c8e7b..3bd0c0cd 100644 --- a/libntfs-3g/ioctl.c +++ b/libntfs-3g/ioctl.c @@ -368,7 +368,7 @@ int ntfs_ioctl(ntfs_inode *ni, int cmd, void *arg __attribute__((unused)), ret = fstrim(ni->vol, data); break; #else -#warning FITRIM or BLKDISCARD not defined +#warning Trimming not supported : FITRIM or BLKDISCARD not defined #endif default : ret = -EINVAL; From 4340df770ecf5f5865124a297d35edbcb456c42d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Pierre=20Andr=C3=A9?= Date: Fri, 17 Apr 2015 09:15:25 +0200 Subject: [PATCH 03/61] Supported the directory separator on Windows in ntfsprogs (cosmetic) On Windows, when an ntfsprogs utility requests a path translation, translate the '\'s to '/'s so that only '/' have to be interpreted in libntfs. --- ntfsprogs/ntfscat.c | 15 ++++++++++++++- ntfsprogs/ntfscp.c | 25 +++++++++++++++++++++++-- ntfsprogs/ntfsfallocate.c | 9 +-------- ntfsprogs/ntfsinfo.c | 12 ++++++++++++ ntfsprogs/utils.c | 24 ++++++++++++++++++++++++ ntfsprogs/utils.h | 1 + 6 files changed, 75 insertions(+), 11 deletions(-) diff --git a/ntfsprogs/ntfscat.c b/ntfsprogs/ntfscat.c index 2ae1f9cd..cbcfb979 100644 --- a/ntfsprogs/ntfscat.c +++ b/ntfsprogs/ntfscat.c @@ -423,8 +423,21 @@ int main(int argc, char *argv[]) if (opts.inode != -1) inode = ntfs_inode_open(vol, opts.inode); - else + else { +#ifdef HAVE_WINDOWS_H + char *unix_name; + + unix_name = ntfs_utils_unix_path(opts.file); + if (unix_name) { + inode = ntfs_pathname_to_inode(vol, NULL, + unix_name); + free(unix_name); + } else + inode = (ntfs_inode*)NULL; +#else inode = ntfs_pathname_to_inode(vol, NULL, opts.file); +#endif + } if (!inode) { ntfs_log_perror("ERROR: Couldn't open inode"); diff --git a/ntfsprogs/ntfscp.c b/ntfsprogs/ntfscp.c index 87a7da30..4cf05301 100644 --- a/ntfsprogs/ntfscp.c +++ b/ntfsprogs/ntfscp.c @@ -834,6 +834,9 @@ int main(int argc, char *argv[]) s64 br, bw; ntfschar *attr_name; int attr_name_len = 0; +#ifdef HAVE_WINDOWS_H + char *unix_name; +#endif ntfs_log_set_handler(ntfs_log_handler_stderr); @@ -900,8 +903,17 @@ int main(int argc, char *argv[]) goto close_src; } out = ntfs_inode_open(vol, inode_num); - } else + } else { +#ifdef HAVE_WINDOWS_H + unix_name = ntfs_utils_unix_path(opts.dest_file); + if (unix_name) { + out = ntfs_pathname_to_inode(vol, NULL, unix_name); + } else + out = (ntfs_inode*)NULL; +#else out = ntfs_pathname_to_inode(vol, NULL, opts.dest_file); +#endif + } if (!out) { /* Copy the file if the dest_file's parent dir can be opened. */ char *parent_dirname; @@ -910,8 +922,13 @@ int main(int argc, char *argv[]) ntfs_inode *ni; char *dirname_last_whack; +#ifdef HAVE_WINDOWS_H + filename = basename(unix_name); + parent_dirname = strdup(unix_name); +#else filename = basename(opts.dest_file); parent_dirname = strdup(opts.dest_file); +#endif if (!parent_dirname) { ntfs_log_perror("strdup() failed"); goto close_src; @@ -983,8 +1000,12 @@ int main(int argc, char *argv[]) ntfs_inode_close(out); goto close_src; } +#ifdef HAVE_WINDOWS_H + strcpy(overwrite_filename, unix_name); +#else strcpy(overwrite_filename, opts.dest_file); - if (opts.dest_file[dest_dirname_len - 1] != '/') { +#endif + if (overwrite_filename[dest_dirname_len - 1] != '/') { strcat(overwrite_filename, "/"); } strcat(overwrite_filename, filename); diff --git a/ntfsprogs/ntfsfallocate.c b/ntfsprogs/ntfsfallocate.c index 1b243ae6..eeb13470 100644 --- a/ntfsprogs/ntfsfallocate.c +++ b/ntfsprogs/ntfsfallocate.c @@ -859,15 +859,8 @@ int main(int argc, char **argv) /* Open the specified inode. */ #ifdef HAVE_WINDOWS_H - unix_name = (char*)malloc(strlen(file_name) + 1); + unix_name = ntfs_utils_unix_path(file_name); if (unix_name) { - int i; - for (i=0; file_name[i]; i++) - if (file_name[i] == '\\') - unix_name[i] = '/'; - else - unix_name[i] = file_name[i]; - unix_name[i] = 0; ni = ntfs_pathname_to_inode(vol, NULL, unix_name); free(unix_name); } else diff --git a/ntfsprogs/ntfsinfo.c b/ntfsprogs/ntfsinfo.c index 817eadc4..eb477037 100644 --- a/ntfsprogs/ntfsinfo.c +++ b/ntfsprogs/ntfsinfo.c @@ -2379,8 +2379,20 @@ int main(int argc, char **argv) ntfs_inode *inode; /* obtain the inode */ if (opts.filename) { +#ifdef HAVE_WINDOWS_H + char *unix_name; + + unix_name = ntfs_utils_unix_path(opts.filename); + if (unix_name) { + inode = ntfs_pathname_to_inode(vol, NULL, + unix_name); + free(unix_name); + } else + inode = (ntfs_inode*)NULL; +#else inode = ntfs_pathname_to_inode(vol, NULL, opts.filename); +#endif } else { inode = ntfs_inode_open(vol, MK_MREF(opts.inode, 0)); } diff --git a/ntfsprogs/utils.c b/ntfsprogs/utils.c index 7ac31163..cdd556e1 100644 --- a/ntfsprogs/utils.c +++ b/ntfsprogs/utils.c @@ -1201,4 +1201,28 @@ char *ntfs_utils_reformat(char *out, int sz, const char *fmt) return (out); } +/* + * Translate paths to files submitted from Windows + * + * Translate Windows directory separators to Unix ones + * + * Returns the translated path, to be freed by caller + * NULL if there was an error, with errno set + */ + +char *ntfs_utils_unix_path(const char *in) +{ + char *out; + int i; + + out = strdup(in); + if (out) { + for (i=0; in[i]; i++) + if (in[i] == '\\') + out[i] = '/'; + } else + errno = ENOMEM; + return (out); +} + #endif diff --git a/ntfsprogs/utils.h b/ntfsprogs/utils.h index 8b6bfae5..396f698e 100644 --- a/ntfsprogs/utils.h +++ b/ntfsprogs/utils.h @@ -107,6 +107,7 @@ int mft_next_record(struct mft_search_ctx *ctx); */ #define MAX_FMT 1536 char *ntfs_utils_reformat(char *out, int sz, const char *fmt); +char *ntfs_utils_unix_path(const char *in); #define ntfs_log_redirect(fn,fi,li,le,d,fmt, args...) \ do { char buf[MAX_FMT]; ntfs_log_redirect(fn,fi,li,le,d, \ ntfs_utils_reformat(buf,MAX_FMT,fmt), args); } while (0) From 3fb1deb13bf78378ab8a90a7f8342569f1eb50b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Pierre=20Andr=C3=A9?= Date: Fri, 17 Apr 2015 09:21:58 +0200 Subject: [PATCH 04/61] Fixed getting sector size from a partition image (Windows variant) On Windows, when processing a partition image, get the sector size from the boot sector instead of the containing partition. --- libntfs-3g/win32_io.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/libntfs-3g/win32_io.c b/libntfs-3g/win32_io.c index 9c84ec67..c4c09d7d 100644 --- a/libntfs-3g/win32_io.c +++ b/libntfs-3g/win32_io.c @@ -1881,7 +1881,11 @@ static int ntfs_device_win32_ioctl(struct ntfs_device *dev, int request, #ifdef BLKSSZGET case BLKSSZGET: ntfs_log_debug("BLKSSZGET detected.\n"); - return ntfs_win32_blksszget(dev, (int *)argp); + if (fd && !fd->ntdll) { + *(int*)argp = fd->geo_sector_size; + return (0); + } else + return ntfs_win32_blksszget(dev, (int *)argp); #endif #ifdef BLKBSZSET case BLKBSZSET: From 25eb7a710b80a9f2688716ef8a4aff0730b445a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Pierre=20Andr=C3=A9?= Date: Fri, 17 Apr 2015 09:26:59 +0200 Subject: [PATCH 05/61] Added clarifications about several options to the ntfswipe manual Clarified the usage of fast wiping, info and verbose options. --- ntfsprogs/ntfswipe.8.in | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ntfsprogs/ntfswipe.8.in b/ntfsprogs/ntfswipe.8.in index 08192580..375b4703 100644 --- a/ntfsprogs/ntfswipe.8.in +++ b/ntfsprogs/ntfswipe.8.in @@ -26,7 +26,8 @@ is equivalent to Long named options can be abbreviated to any unique prefix of their name. .TP \fB\-a\fR, \fB\-\-all\fR -Wipe all unused space. This may take significant time. +Wipe all unused space. This may take significant time. If the option +\-\-unused-fast (or -U) is also present, the faster wiping method is used. .TP \fB\-b\fR, \fB\-\-bytes\fR BYTE-LIST Define the allowed replacement bytes which are drawn randomly to overwrite @@ -47,7 +48,7 @@ Use this option with caution. Show a list of options with a brief description of each one. .TP \fB\-i\fR, \fB\-\-info\fR -Display details about unused space. +Display details about unused space, without wiping anything. .TP \fB\-l\fR, \fB\-\-logfile\fR Overwrite the logfile (update journal). @@ -82,7 +83,8 @@ Overwrite the space which is currently not allocated to any file, trying not to overwrite the space not written to since the previous wiping. .TP \fB\-v\fR, \fB\-\-verbose\fR -Display more debug/warning/error messages. +Display more debug/warning/error messages. This option may be used twice +to display even more messages. .TP \fB\-V\fR, \fB\-\-version\fR Show the version number, copyright and license of From e7b71bb78d6226bae5c5e1dd061037e4b020ee4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Pierre=20Andr=C3=A9?= Date: Fri, 17 Apr 2015 09:33:04 +0200 Subject: [PATCH 06/61] Avoided mentioning deleted inodes in non-verbose output of ntfswipe In non-verbose ntfswipe, avoid displaying deleted inodes as erroneous ones. --- ntfsprogs/ntfswipe.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ntfsprogs/ntfswipe.c b/ntfsprogs/ntfswipe.c index 658aaac0..b3b8bb94 100644 --- a/ntfsprogs/ntfswipe.c +++ b/ntfsprogs/ntfswipe.c @@ -881,7 +881,10 @@ static s64 wipe_tails(ntfs_volume *vol, int byte, enum action act) ntfs_log_verbose("Inode %lld - ", (long long)inode_num); ni = ntfs_inode_open(vol, inode_num); if (!ni) { - ntfs_log_verbose("Could not open inode\n"); + if (opts.verbose) + ntfs_log_verbose("Could not open inode\n"); + else + ntfs_log_verbose("\r"); continue; } From e43f58e0e4534d101653065a65121d2ab8e4493f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Pierre=20Andr=C3=A9?= Date: Fri, 17 Apr 2015 09:37:11 +0200 Subject: [PATCH 07/61] Avoided logging meaningless fixup errors in ntfswipe As ntfswipe examines all MFT entries, it may find uninitialized ones. They should not be considered as erroneous ones even if the fixups are wrong. --- ntfsprogs/ntfswipe.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/ntfsprogs/ntfswipe.c b/ntfsprogs/ntfswipe.c index b3b8bb94..fe251cd1 100644 --- a/ntfsprogs/ntfswipe.c +++ b/ntfsprogs/ntfswipe.c @@ -873,6 +873,9 @@ static s64 wipe_tails(ntfs_volume *vol, int byte, enum action act) nr_mft_records = vol->mft_na->initialized_size >> vol->mft_record_size_bits; + /* Avoid getting fixup warnings on unitialized inodes */ + NVolSetNoFixupWarn(vol); + for (inode_num = FILE_first_user; inode_num < nr_mft_records; inode_num++) { s64 attr_wiped; @@ -923,6 +926,7 @@ close_inode: ntfs_inode_close(ni); } close_abort : + NVolClearNoFixupWarn(vol); ntfs_log_quiet("wipe_tails 0x%02x, %lld bytes\n", byte, (long long)total); return total; @@ -1236,6 +1240,9 @@ static s64 wipe_directory(ntfs_volume *vol, int byte, enum action act) nr_mft_records = vol->mft_na->initialized_size >> vol->mft_record_size_bits; + /* Avoid getting fixup warnings on unitialized inodes */ + NVolSetNoFixupWarn(vol); + for (inode_num = 5; inode_num < nr_mft_records; inode_num++) { u32 indx_record_size; s64 wiped; @@ -1336,6 +1343,7 @@ close_inode: ntfs_inode_close(ni); } + NVolClearNoFixupWarn(vol); ntfs_log_quiet("wipe_directory 0x%02x, %lld bytes\n", byte, (long long)total); return total; @@ -1723,14 +1731,18 @@ static int destroy_record(ntfs_volume *nv, const s64 record, return -2; } + /* Avoid getting fixup warnings on unitialized inodes */ + NVolSetNoFixupWarn(nv); /* Read the MFT reocrd of the i-node */ if (ntfs_attr_mst_pread(mft, nv->mft_record_size * record, 1LL, nv->mft_record_size, file->mft) < 1) { + NVolClearNoFixupWarn(nv); ntfs_attr_close(mft); free_file(file); return -3; } + NVolClearNoFixupWarn(nv); ntfs_attr_close(mft); mft = NULL; From 1e8ae121fc5783e31db2bb5654bb8e1900bc88e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Pierre=20Andr=C3=A9?= Date: Fri, 17 Apr 2015 09:42:19 +0200 Subject: [PATCH 08/61] Rephrased a misleading report message in ntfswipe When using the "--no-action" option, ntfswipe should display a count of would-be wiped bytes without suggesting any has been wiped. --- ntfsprogs/ntfswipe.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/ntfsprogs/ntfswipe.c b/ntfsprogs/ntfswipe.c index fe251cd1..0d360ee5 100644 --- a/ntfsprogs/ntfswipe.c +++ b/ntfsprogs/ntfswipe.c @@ -2261,9 +2261,14 @@ int main(int argc, char *argv[]) break; } - ntfs_log_info( - "%lld bytes were wiped (excluding undelete data)\n", - (long long)total); + if (opts.noaction || opts.info) + ntfs_log_info("%lld bytes would be wiped" + " (excluding undelete data)\n", + (long long)total); + else + ntfs_log_info("%lld bytes were wiped" + " (excluding undelete data)\n", + (long long)total); } result = 0; umount: From 9fc3730a9b659a6b729dac84951f202a181e18c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Pierre=20Andr=C3=A9?= Date: Fri, 17 Apr 2015 10:21:02 +0200 Subject: [PATCH 09/61] Fixed displaying "UserMapping" as a file name Fix the capitalization of the user mapping file name to be filled with the output of secaudit with option -u. --- src/secaudit.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/secaudit.c b/src/secaudit.c index c5aa7e3a..66e8f4b3 100644 --- a/src/secaudit.c +++ b/src/secaudit.c @@ -212,6 +212,9 @@ * - decoded more "well-known" and generic SIDs * - showed Windows ownership in verbose situations * - fixed apparent const violations + * + * Dec 2014, version 1.4.3 + * - fixed displaying "UserMapping" as a file name */ /* @@ -235,7 +238,7 @@ * General parameters which may have to be adapted to needs */ -#define AUDT_VERSION "1.4.2" +#define AUDT_VERSION "1.4.3" #define GET_FILE_SECURITY "ntfs_get_file_security" #define SET_FILE_SECURITY "ntfs_set_file_security" @@ -4834,9 +4837,9 @@ BOOL proposal(const char *name, const char *attr) printf("# and gid of the Linux owner and group of "); printname(stdout,name); printf(", then\n"); - printf("# insert the modified lines into .NTFS-3G/Usermapping, with .NTFS-3G\n"); + printf("# insert the modified lines into .NTFS-3G/UserMapping, with .NTFS-3G\n"); } else - printf("# Insert the above lines into .NTFS-3G/Usermapping, with .NTFS-3G\n"); + printf("# Insert the above lines into .NTFS-3G/UserMapping, with .NTFS-3G\n"); #ifdef WIN32 printf("# being a directory of the root of the NTFS file system.\n"); From d96ae8d7daf680a06da52f59e5376c6c1a1fbec1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Pierre=20Andr=C3=A9?= Date: Fri, 17 Apr 2015 10:27:24 +0200 Subject: [PATCH 10/61] Removed the double try to unmount partition in ntfsfix When the unmounting of the partition fails after running ntfsfix, a second try was attempted. This cannot be done and leads to more errors because some essential records have been freed, so better quit without making a second try. --- ntfsprogs/ntfsfix.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/ntfsprogs/ntfsfix.c b/ntfsprogs/ntfsfix.c index 59b8a98f..add14d06 100644 --- a/ntfsprogs/ntfsfix.c +++ b/ntfsprogs/ntfsfix.c @@ -4,7 +4,7 @@ * Copyright (c) 2000-2006 Anton Altaparmakov * Copyright (c) 2002-2006 Szabolcs Szakacsits * Copyright (c) 2007 Yura Pakhuchiy - * Copyright (c) 2011-2014 Jean-Pierre Andre + * Copyright (c) 2011-2015 Jean-Pierre Andre * * This utility fixes some common NTFS problems, resets the NTFS journal file * and schedules an NTFS consistency check for the first boot into Windows. @@ -154,7 +154,7 @@ static void version(void) "Copyright (c) 2000-2006 Anton Altaparmakov\n" "Copyright (c) 2002-2006 Szabolcs Szakacsits\n" "Copyright (c) 2007 Yura Pakhuchiy\n" - "Copyright (c) 2011-2014 Jean-Pierre Andre\n\n", + "Copyright (c) 2011-2015 Jean-Pierre Andre\n\n", EXEC_NAME, VERSION); ntfs_log_info("%s\n%s%s", ntfs_gpl, ntfs_bugs, ntfs_home); exit(0); @@ -1646,8 +1646,10 @@ int main(int argc, char **argv) /* Set return code to 0. */ ret = 0; error_exit: - if (ntfs_umount(vol, 0)) - ntfs_umount(vol, 1); + if (ntfs_umount(vol, 1)) { + ntfs_log_info("Failed to unmount partition\n"); + ret = 1; + } if (ret) exit(ret); return ret; From 8fb58de7626626248a05b4add5756c424b79c04b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Pierre=20Andr=C3=A9?= Date: Fri, 17 Apr 2015 10:35:26 +0200 Subject: [PATCH 11/61] Updated the read-only flag even when the security attribute was cached When chmod'ing a file, no new ACL has to be created if the one needed is already present in the cache. However the read-only flag may have to be updated, so that it is kept as the opposite of S_IWUSR. --- libntfs-3g/security.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/libntfs-3g/security.c b/libntfs-3g/security.c index e6d0587c..3ac4790a 100644 --- a/libntfs-3g/security.c +++ b/libntfs-3g/security.c @@ -4,7 +4,7 @@ * Copyright (c) 2004 Anton Altaparmakov * Copyright (c) 2005-2006 Szabolcs Szakacsits * Copyright (c) 2006 Yura Pakhuchiy - * Copyright (c) 2007-2014 Jean-Pierre Andre + * Copyright (c) 2007-2015 Jean-Pierre Andre * * This program/include file is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as published @@ -2916,6 +2916,14 @@ int ntfs_set_owner_mode(struct SECURITY_CONTEXT *scx, ntfs_inode *ni, if (cached) { ni->security_id = cached->securid; NInoSetDirty(ni); + /* adjust Windows read-only flag */ + if (!isdir) { + if (mode & S_IWUSR) + ni->flags &= ~FILE_ATTR_READONLY; + else + ni->flags |= FILE_ATTR_READONLY; + NInoFileNameSetDirty(ni); + } } } else cached = (struct CACHED_SECURID*)NULL; From b249246e9f00085c501c28112f6a8939a517e676 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Pierre=20Andr=C3=A9?= Date: Fri, 17 Apr 2015 10:41:04 +0200 Subject: [PATCH 12/61] Defended against reusing data from an invalid MFT record An unused MFT record may show a bad length, leading to fetch fixups from unallocated memory when allocating the record to a new file. So check the length before applying the fixups. Such records have been found after the MFT has been reallocated by a defragmenter, and they are not cleaned by chkdsk. --- libntfs-3g/mft.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/libntfs-3g/mft.c b/libntfs-3g/mft.c index ac4c610b..af10ffdd 100644 --- a/libntfs-3g/mft.c +++ b/libntfs-3g/mft.c @@ -5,7 +5,7 @@ * Copyright (c) 2004-2005 Richard Russon * Copyright (c) 2004-2008 Szabolcs Szakacsits * Copyright (c) 2005 Yura Pakhuchiy - * Copyright (c) 2014 Jean-Pierre Andre + * Copyright (c) 2014-2015 Jean-Pierre Andre * * This program/include file is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as published @@ -1628,6 +1628,7 @@ ntfs_inode *ntfs_mft_record_alloc(ntfs_volume *vol, ntfs_inode *base_ni) MFT_RECORD *m; ntfs_inode *ni = NULL; int err; + u32 usa_ofs; le16 seq_no, usn; if (base_ni) @@ -1754,7 +1755,16 @@ found_free_rec: goto retry; } seq_no = m->sequence_number; - usn = *(le16*)((u8*)m + le16_to_cpu(m->usa_ofs)); + /* + * As ntfs_mft_record_read() returns what has been read + * even when the fixups have been found bad, we have to + * check where we fetch the initial usn from. + */ + usa_ofs = le16_to_cpu(m->usa_ofs); + if (!(usa_ofs & 1) && (usa_ofs < NTFS_BLOCK_SIZE)) { + usn = *(le16*)((u8*)m + usa_ofs); + } else + usn = const_cpu_to_le16(1); if (ntfs_mft_record_layout(vol, bit, m)) { ntfs_log_error("Failed to re-format mft record.\n"); free(m); From baab4c287ebb328264889ea8665a0aaccffda0a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Pierre=20Andr=C3=A9?= Date: Fri, 17 Apr 2015 10:49:23 +0200 Subject: [PATCH 13/61] Added a sanity check to ntfswipe Unused entries in the MFT may have a bad length leading to fetch fixups from unallocated memory. Check the condition, but do not wipe, leave it to chkdsk to decide what should be fixed. --- ntfsprogs/ntfswipe.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ntfsprogs/ntfswipe.c b/ntfsprogs/ntfswipe.c index 0d360ee5..274f13f5 100644 --- a/ntfsprogs/ntfswipe.c +++ b/ntfsprogs/ntfswipe.c @@ -981,6 +981,12 @@ static s64 wipe_mft(ntfs_volume *vol, int byte, enum action act) // We know that the end marker will only take 4 bytes size = le32_to_cpu(rec->bytes_in_use) - 4; + if ((size <= 0) || (size > vol->mft_record_size)) { + ntfs_log_error("Bad mft record %lld\n", + (long long)i); + total = -1; + goto free; + } if (act == act_info) { //ntfs_log_info("mft %d\n", size); total += size; From e24ea68632c4f18abfa72dd896eb8653bacfb4f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Pierre=20Andr=C3=A9?= Date: Fri, 17 Apr 2015 10:56:34 +0200 Subject: [PATCH 14/61] Removed a temporary debug comment (cosmetic) The comment was a left over from some debugging action. --- libntfs-3g/mft.c | 1 - 1 file changed, 1 deletion(-) diff --git a/libntfs-3g/mft.c b/libntfs-3g/mft.c index af10ffdd..2a5c0472 100644 --- a/libntfs-3g/mft.c +++ b/libntfs-3g/mft.c @@ -1620,7 +1620,6 @@ err_out: * when reading the bitmap but if we are careful, we should be able to avoid * all problems. */ -//ntfs_inode *ntfs_mft_record_alloc(ntfs_volume *vol, ntfs_inode *base_ni) ntfs_inode *ntfs_mft_record_alloc(ntfs_volume *vol, ntfs_inode *base_ni) { s64 ll, bit; From 6a1c32e538c453b77cd70705bbc6078a00550665 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Pierre=20Andr=C3=A9?= Date: Fri, 17 Apr 2015 11:00:00 +0200 Subject: [PATCH 15/61] Fixed the DESX encryption/decryption in ntfsdecrypt The DESX encryption/decryption algorithm was different from the Windows one. --- ntfsprogs/ntfsdecrypt.c | 41 ++++++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/ntfsprogs/ntfsdecrypt.c b/ntfsprogs/ntfsdecrypt.c index ab58cbd9..e167ae86 100644 --- a/ntfsprogs/ntfsdecrypt.c +++ b/ntfsprogs/ntfsdecrypt.c @@ -4,7 +4,7 @@ * Copyright (c) 2005 Yuval Fledel * Copyright (c) 2005-2007 Anton Altaparmakov * Copyright (c) 2007 Yura Pakhuchiy - * Copyright (c) 2014 Jean-Pierre Andre + * Copyright (c) 2014-2015 Jean-Pierre Andre * * This utility will decrypt files and print the decrypted data on the standard * output. @@ -107,6 +107,7 @@ typedef enum { typedef struct { u64 in_whitening, out_whitening; u8 des_key[8]; + u64 prev_blk; } ntfs_desx_ctx; /** @@ -154,7 +155,7 @@ static void version(void) "standard output.\n\n", EXEC_NAME, VERSION); ntfs_log_info("Copyright (c) 2005 Yuval Fledel\n"); ntfs_log_info("Copyright (c) 2005 Anton Altaparmakov\n"); - ntfs_log_info("Copyright (c) 2014 Jean-Pierre Andre\n"); + ntfs_log_info("Copyright (c) 2014-2015 Jean-Pierre Andre\n"); ntfs_log_info("\n%s\n%s%s\n", ntfs_gpl, ntfs_bugs, ntfs_home); } @@ -885,39 +886,39 @@ out: /** * ntfs_desx_decrypt */ -static void ntfs_desx_decrypt(ntfs_fek *fek, u8 *outbuf, const u8 *inbuf) +static gcry_error_t ntfs_desx_decrypt(ntfs_fek *fek, u8 *outbuf, + const u8 *inbuf) { gcry_error_t err; + u64 curr_blk; ntfs_desx_ctx *ctx = &fek->desx_ctx; - err = gcry_cipher_reset(fek->gcry_cipher_hd); - if (err != GPG_ERR_NO_ERROR) - ntfs_log_error("Failed to reset des cipher (error 0x%x).\n", - err); - *(u64*)outbuf = *(const u64*)inbuf ^ ctx->out_whitening; + curr_blk = *(const u64*)inbuf; + *(u64*)outbuf = curr_blk ^ ctx->out_whitening; err = gcry_cipher_encrypt(fek->gcry_cipher_hd, outbuf, 8, NULL, 0); if (err != GPG_ERR_NO_ERROR) ntfs_log_error("Des decryption failed (error 0x%x).\n", err); - *(u64*)outbuf ^= ctx->in_whitening; + *(u64*)outbuf ^= ctx->in_whitening ^ ctx->prev_blk; + ctx->prev_blk = curr_blk; + return (err); } /** * ntfs_desx_encrypt */ -static void ntfs_desx_encrypt(ntfs_fek *fek, u8 *outbuf, const u8 *inbuf) +static gcry_error_t ntfs_desx_encrypt(ntfs_fek *fek, u8 *outbuf, + const u8 *inbuf) { gcry_error_t err; ntfs_desx_ctx *ctx = &fek->desx_ctx; - err = gcry_cipher_reset(fek->gcry_cipher_hd); - if (err != GPG_ERR_NO_ERROR) - ntfs_log_error("Failed to reset des cipher (error 0x%x).\n", - err); - *(u64*)outbuf = *(const u64*)inbuf ^ ctx->in_whitening; + *(u64*)outbuf = *(const u64*)inbuf ^ ctx->in_whitening ^ ctx->prev_blk; err = gcry_cipher_decrypt(fek->gcry_cipher_hd, outbuf, 8, NULL, 0); if (err != GPG_ERR_NO_ERROR) ntfs_log_error("Des decryption failed (error 0x%x).\n", err); *(u64*)outbuf ^= ctx->out_whitening; + ctx->prev_blk = *(u64*)outbuf; + return (err); } //#define DO_CRYPTO_TESTS 1 @@ -1290,8 +1291,9 @@ static int ntfs_fek_decrypt_sector(ntfs_fek *fek, u8 *data, const u64 offset) if (fek->alg_id == CALG_DESX) { int k; - for (k=0; k<512; k+=8) { - ntfs_desx_decrypt(fek, &data[k], &data[k]); + fek->desx_ctx.prev_blk = 0; + for (k=0; (k < 512) && (err == GPG_ERR_NO_ERROR); k+=8) { + err = ntfs_desx_decrypt(fek, &data[k], &data[k]); } } else err = gcry_cipher_decrypt(fek->gcry_cipher_hd, data, 512, NULL, 0); @@ -1340,8 +1342,9 @@ static int ntfs_fek_encrypt_sector(ntfs_fek *fek, u8 *data, const u64 offset) if (fek->alg_id == CALG_DESX) { int k; - for (k=0; k<512; k+=8) { - ntfs_desx_encrypt(fek, &data[k], &data[k]); + fek->desx_ctx.prev_blk = 0; + for (k=0; (k < 512) && (err == GPG_ERR_NO_ERROR); k+=8) { + err = ntfs_desx_encrypt(fek, &data[k], &data[k]); } } else err = gcry_cipher_encrypt(fek->gcry_cipher_hd, data, 512, NULL, 0); From e40b86a86ca236898ae61bc0d93b196a84a537b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Pierre=20Andr=C3=A9?= Date: Fri, 17 Apr 2015 11:03:58 +0200 Subject: [PATCH 16/61] Upgraded the upper-case table as defined by Windows 7 Newer versions of Windows use more recent definitions of upper-case table defined by the Unicode consortium. Now using the same table as Windows 7, windows 8 and Windows 10. This only has an effect on file systems newly created by mkntfs. --- include/ntfs-3g/param.h | 8 ++ libntfs-3g/unistr.c | 162 +++++++++++++++++++++++++--------------- 2 files changed, 108 insertions(+), 62 deletions(-) diff --git a/include/ntfs-3g/param.h b/include/ntfs-3g/param.h index da794abb..f67193c4 100644 --- a/include/ntfs-3g/param.h +++ b/include/ntfs-3g/param.h @@ -76,6 +76,14 @@ enum { /* only update the final extent of a runlist when appending data */ #define PARTIAL_RUNLIST_UPDATING 1 +/* + * Parameters for upper-case table + */ + + /* Create upper-case tables as defined by Windows 6.1 (Win7) */ +#define UPCASE_MAJOR 6 +#define UPCASE_MINOR 1 + /* * Parameters for user and xattr mappings */ diff --git a/libntfs-3g/unistr.c b/libntfs-3g/unistr.c index e12d21e7..94215fff 100644 --- a/libntfs-3g/unistr.c +++ b/libntfs-3g/unistr.c @@ -3,7 +3,7 @@ * * Copyright (c) 2000-2004 Anton Altaparmakov * Copyright (c) 2002-2009 Szabolcs Szakacsits - * Copyright (c) 2008-2014 Jean-Pierre Andre + * Copyright (c) 2008-2015 Jean-Pierre Andre * Copyright (c) 2008 Bernhard Kaindl * * This program/include file is free software; you can redistribute it and/or @@ -1128,66 +1128,15 @@ char *ntfs_uppercase_mbs(const char *low, */ void ntfs_upcase_table_build(ntfschar *uc, u32 uc_len) { -#if 1 /* Vista */ - /* - * This is the table as defined by Vista - */ - /* - * "Start" is inclusive and "End" is exclusive, every value has the - * value of "Add" added to it. - */ - static int uc_run_table[][3] = { /* Start, End, Add */ - {0x0061, 0x007b, -32}, {0x00e0, 0x00f7, -32}, {0x00f8, 0x00ff, -32}, - {0x0256, 0x0258, -205}, {0x028a, 0x028c, -217}, {0x037b, 0x037e, 130}, - {0x03ac, 0x03ad, -38}, {0x03ad, 0x03b0, -37}, {0x03b1, 0x03c2, -32}, - {0x03c2, 0x03c3, -31}, {0x03c3, 0x03cc, -32}, {0x03cc, 0x03cd, -64}, - {0x03cd, 0x03cf, -63}, {0x0430, 0x0450, -32}, {0x0450, 0x0460, -80}, - {0x0561, 0x0587, -48}, {0x1f00, 0x1f08, 8}, {0x1f10, 0x1f16, 8}, - {0x1f20, 0x1f28, 8}, {0x1f30, 0x1f38, 8}, {0x1f40, 0x1f46, 8}, - {0x1f51, 0x1f52, 8}, {0x1f53, 0x1f54, 8}, {0x1f55, 0x1f56, 8}, - {0x1f57, 0x1f58, 8}, {0x1f60, 0x1f68, 8}, {0x1f70, 0x1f72, 74}, - {0x1f72, 0x1f76, 86}, {0x1f76, 0x1f78, 100}, {0x1f78, 0x1f7a, 128}, - {0x1f7a, 0x1f7c, 112}, {0x1f7c, 0x1f7e, 126}, {0x1f80, 0x1f88, 8}, - {0x1f90, 0x1f98, 8}, {0x1fa0, 0x1fa8, 8}, {0x1fb0, 0x1fb2, 8}, - {0x1fb3, 0x1fb4, 9}, {0x1fcc, 0x1fcd, -9}, {0x1fd0, 0x1fd2, 8}, - {0x1fe0, 0x1fe2, 8}, {0x1fe5, 0x1fe6, 7}, {0x1ffc, 0x1ffd, -9}, - {0x2170, 0x2180, -16}, {0x24d0, 0x24ea, -26}, {0x2c30, 0x2c5f, -48}, - {0x2d00, 0x2d26, -7264}, {0xff41, 0xff5b, -32}, {0} - }; - /* - * "Start" is exclusive and "End" is inclusive, every second value is - * decremented by one. - */ - static int uc_dup_table[][2] = { /* Start, End */ - {0x0100, 0x012f}, {0x0132, 0x0137}, {0x0139, 0x0149}, {0x014a, 0x0178}, - {0x0179, 0x017e}, {0x01a0, 0x01a6}, {0x01b3, 0x01b7}, {0x01cd, 0x01dd}, - {0x01de, 0x01ef}, {0x01f4, 0x01f5}, {0x01f8, 0x01f9}, {0x01fa, 0x0220}, - {0x0222, 0x0234}, {0x023b, 0x023c}, {0x0241, 0x0242}, {0x0246, 0x024f}, - {0x03d8, 0x03ef}, {0x03f7, 0x03f8}, {0x03fa, 0x03fb}, {0x0460, 0x0481}, - {0x048a, 0x04bf}, {0x04c1, 0x04c4}, {0x04c5, 0x04c8}, {0x04c9, 0x04ce}, - {0x04ec, 0x04ed}, {0x04d0, 0x04eb}, {0x04ee, 0x04f5}, {0x04f6, 0x0513}, - {0x1e00, 0x1e95}, {0x1ea0, 0x1ef9}, {0x2183, 0x2184}, {0x2c60, 0x2c61}, - {0x2c67, 0x2c6c}, {0x2c75, 0x2c76}, {0x2c80, 0x2ce3}, {0} - }; - /* - * Set the Unicode character at offset "Offset" to "Value". Note, - * "Value" is host endian. - */ - static int uc_byte_table[][2] = { /* Offset, Value */ - {0x00ff, 0x0178}, {0x0180, 0x0243}, {0x0183, 0x0182}, {0x0185, 0x0184}, - {0x0188, 0x0187}, {0x018c, 0x018b}, {0x0192, 0x0191}, {0x0195, 0x01f6}, - {0x0199, 0x0198}, {0x019a, 0x023d}, {0x019e, 0x0220}, {0x01a8, 0x01a7}, - {0x01ad, 0x01ac}, {0x01b0, 0x01af}, {0x01b9, 0x01b8}, {0x01bd, 0x01bc}, - {0x01bf, 0x01f7}, {0x01c6, 0x01c4}, {0x01c9, 0x01c7}, {0x01cc, 0x01ca}, - {0x01dd, 0x018e}, {0x01f3, 0x01f1}, {0x023a, 0x2c65}, {0x023e, 0x2c66}, - {0x0253, 0x0181}, {0x0254, 0x0186}, {0x0259, 0x018f}, {0x025b, 0x0190}, - {0x0260, 0x0193}, {0x0263, 0x0194}, {0x0268, 0x0197}, {0x0269, 0x0196}, - {0x026b, 0x2c62}, {0x026f, 0x019c}, {0x0272, 0x019d}, {0x0275, 0x019f}, - {0x027d, 0x2c64}, {0x0280, 0x01a6}, {0x0283, 0x01a9}, {0x0288, 0x01ae}, - {0x0289, 0x0244}, {0x028c, 0x0245}, {0x0292, 0x01b7}, {0x03f2, 0x03f9}, - {0x04cf, 0x04c0}, {0x1d7d, 0x2c63}, {0x214e, 0x2132}, {0} - }; -#else /* Vista */ + struct NEWUPPERCASE { + unsigned short first; + unsigned short last; + short diff; + unsigned char step; + unsigned char osmajor; + unsigned char osminor; + } ; + /* * This is the table as defined by Windows XP */ @@ -1227,9 +1176,88 @@ void ntfs_upcase_table_build(ntfschar *uc, u32 uc_len) {0x01A8, 0x01A7}, {0x01DD, 0x018E}, {0x0268, 0x0197}, {0} }; -#endif /* Vista */ + +/* + * Changes which were applied to later Windows versions + * + * md5 for $UpCase from Winxp : 6fa3db2468275286210751e869d36373 + * Vista : 2f03b5a69d486ff3864cecbd07f24440 + * Win8 : 7ff498a44e45e77374cc7c962b1b92f2 + */ + static const struct NEWUPPERCASE newuppercase[] = { + /* from Windows 6.0 (Vista) */ + { 0x37b, 0x37d, 0x82, 1, 6, 0 }, + { 0x1f80, 0x1f87, 0x8, 1, 6, 0 }, + { 0x1f90, 0x1f97, 0x8, 1, 6, 0 }, + { 0x1fa0, 0x1fa7, 0x8, 1, 6, 0 }, + { 0x2c30, 0x2c5e, -0x30, 1, 6, 0 }, + { 0x2d00, 0x2d25, -0x1c60, 1, 6, 0 }, + { 0x2c68, 0x2c6c, -0x1, 2, 6, 0 }, + { 0x219, 0x21f, -0x1, 2, 6, 0 }, + { 0x223, 0x233, -0x1, 2, 6, 0 }, + { 0x247, 0x24f, -0x1, 2, 6, 0 }, + { 0x3d9, 0x3e1, -0x1, 2, 6, 0 }, + { 0x48b, 0x48f, -0x1, 2, 6, 0 }, + { 0x4fb, 0x513, -0x1, 2, 6, 0 }, + { 0x2c81, 0x2ce3, -0x1, 2, 6, 0 }, + { 0x3f8, 0x3fb, -0x1, 3, 6, 0 }, + { 0x4c6, 0x4ce, -0x1, 4, 6, 0 }, + { 0x23c, 0x242, -0x1, 6, 6, 0 }, + { 0x4ed, 0x4f7, -0x1, 10, 6, 0 }, + { 0x450, 0x45d, -0x50, 13, 6, 0 }, + { 0x2c61, 0x2c76, -0x1, 21, 6, 0 }, + { 0x1fcc, 0x1ffc, -0x9, 48, 6, 0 }, + { 0x180, 0x180, 0xc3, 1, 6, 0 }, + { 0x195, 0x195, 0x61, 1, 6, 0 }, + { 0x19a, 0x19a, 0xa3, 1, 6, 0 }, + { 0x19e, 0x19e, 0x82, 1, 6, 0 }, + { 0x1bf, 0x1bf, 0x38, 1, 6, 0 }, + { 0x1f9, 0x1f9, -0x1, 1, 6, 0 }, + { 0x23a, 0x23a, 0x2a2b, 1, 6, 0 }, + { 0x23e, 0x23e, 0x2a28, 1, 6, 0 }, + { 0x26b, 0x26b, 0x29f7, 1, 6, 0 }, + { 0x27d, 0x27d, 0x29e7, 1, 6, 0 }, + { 0x280, 0x280, -0xda, 1, 6, 0 }, + { 0x289, 0x289, -0x45, 1, 6, 0 }, + { 0x28c, 0x28c, -0x47, 1, 6, 0 }, + { 0x3f2, 0x3f2, 0x7, 1, 6, 0 }, + { 0x4cf, 0x4cf, -0xf, 1, 6, 0 }, + { 0x1d7d, 0x1d7d, 0xee6, 1, 6, 0 }, + { 0x1fb3, 0x1fb3, 0x9, 1, 6, 0 }, + { 0x214e, 0x214e, -0x1c, 1, 6, 0 }, + { 0x2184, 0x2184, -0x1, 1, 6, 0 }, + /* from Windows 6.1 (Win7) */ + { 0x23a, 0x23e, 0x0, 4, 6, 1 }, + { 0x250, 0x250, 0x2a1f, 2, 6, 1 }, + { 0x251, 0x251, 0x2a1c, 2, 6, 1 }, + { 0x271, 0x271, 0x29fd, 2, 6, 1 }, + { 0x371, 0x373, -0x1, 2, 6, 1 }, + { 0x377, 0x377, -0x1, 2, 6, 1 }, + { 0x3c2, 0x3c2, 0x0, 2, 6, 1 }, + { 0x3d7, 0x3d7, -0x8, 2, 6, 1 }, + { 0x515, 0x523, -0x1, 2, 6, 1 }, + { 0x1d79, 0x1d79, 0x8a04, 2, 6, 1 }, + { 0x1efb, 0x1eff, -0x1, 2, 6, 1 }, + { 0x1fc3, 0x1ff3, 0x9, 48, 6, 1 }, + { 0x1fcc, 0x1ffc, 0x0, 48, 6, 1 }, + { 0x2c65, 0x2c65, -0x2a2b, 2, 6, 1 }, + { 0x2c66, 0x2c66, -0x2a28, 2, 6, 1 }, + { 0x2c73, 0x2c73, -0x1, 2, 6, 1 }, + { 0xa641, 0xa65f, -0x1, 2, 6, 1 }, + { 0xa663, 0xa66d, -0x1, 2, 6, 1 }, + { 0xa681, 0xa697, -0x1, 2, 6, 1 }, + { 0xa723, 0xa72f, -0x1, 2, 6, 1 }, + { 0xa733, 0xa76f, -0x1, 2, 6, 1 }, + { 0xa77a, 0xa77c, -0x1, 2, 6, 1 }, + { 0xa77f, 0xa787, -0x1, 2, 6, 1 }, + { 0xa78c, 0xa78c, -0x1, 2, 6, 1 }, + /* end mark */ + { 0 } + } ; + int i, r; int k, off; + const struct NEWUPPERCASE *puc; memset((char*)uc, 0, uc_len); uc_len >>= 1; @@ -1249,6 +1277,16 @@ void ntfs_upcase_table_build(ntfschar *uc, u32 uc_len) k = uc_byte_table[r][1]; uc[uc_byte_table[r][0]] = cpu_to_le16(k); } + for (r=0; newuppercase[r].first; r++) { + puc = &newuppercase[r]; + if ((puc->osmajor < UPCASE_MAJOR) + || ((puc->osmajor == UPCASE_MAJOR) + && (puc->osminor <= UPCASE_MINOR))) { + off = puc->diff; + for (i = puc->first; i <= puc->last; i += puc->step) + uc[i] = cpu_to_le16(i + off); + } + } } /* From 3d1c87d0d926992c1fde3e135195f137ed9c8b8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Pierre=20Andr=C3=A9?= Date: Fri, 17 Apr 2015 11:14:36 +0200 Subject: [PATCH 17/61] Fixed setting SIG_DFL on fuse session exit This is a backport of a fix applied to the (external) fuse library --- libfuse-lite/fuse_signals.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/libfuse-lite/fuse_signals.c b/libfuse-lite/fuse_signals.c index bf979563..d5b3d092 100644 --- a/libfuse-lite/fuse_signals.c +++ b/libfuse-lite/fuse_signals.c @@ -22,13 +22,13 @@ static void exit_handler(int sig) fuse_session_exit(fuse_instance); } -static int set_one_signal_handler(int sig, void (*handler)(int)) +static int set_one_signal_handler(int sig, void (*handler)(int), int remove) { struct sigaction sa; struct sigaction old_sa; memset(&sa, 0, sizeof(struct sigaction)); - sa.sa_handler = handler; + sa.sa_handler = remove ? SIG_DFL : handler; sigemptyset(&(sa.sa_mask)); sa.sa_flags = 0; @@ -37,7 +37,7 @@ static int set_one_signal_handler(int sig, void (*handler)(int)) return -1; } - if (old_sa.sa_handler == SIG_DFL && + if (old_sa.sa_handler == (remove ? handler : SIG_DFL) && sigaction(sig, &sa, NULL) == -1) { perror("fuse: cannot set signal handler"); return -1; @@ -47,10 +47,10 @@ static int set_one_signal_handler(int sig, void (*handler)(int)) int fuse_set_signal_handlers(struct fuse_session *se) { - if (set_one_signal_handler(SIGHUP, exit_handler) == -1 || - set_one_signal_handler(SIGINT, exit_handler) == -1 || - set_one_signal_handler(SIGTERM, exit_handler) == -1 || - set_one_signal_handler(SIGPIPE, SIG_IGN) == -1) + if (set_one_signal_handler(SIGHUP, exit_handler, 0) == -1 || + set_one_signal_handler(SIGINT, exit_handler, 0) == -1 || + set_one_signal_handler(SIGTERM, exit_handler, 0) == -1 || + set_one_signal_handler(SIGPIPE, SIG_IGN, 0) == -1) return -1; fuse_instance = se; @@ -65,9 +65,9 @@ void fuse_remove_signal_handlers(struct fuse_session *se) else fuse_instance = NULL; - set_one_signal_handler(SIGHUP, SIG_DFL); - set_one_signal_handler(SIGINT, SIG_DFL); - set_one_signal_handler(SIGTERM, SIG_DFL); - set_one_signal_handler(SIGPIPE, SIG_DFL); + set_one_signal_handler(SIGHUP, exit_handler, 1); + set_one_signal_handler(SIGINT, exit_handler, 1); + set_one_signal_handler(SIGTERM, exit_handler, 1); + set_one_signal_handler(SIGPIPE, SIG_IGN, 1); } From 46716df5414d73e7f634e1ff3b84e457205b1c06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Pierre=20Andr=C3=A9?= Date: Fri, 17 Apr 2015 11:17:51 +0200 Subject: [PATCH 18/61] Simplified NTFS ACLs when group same as owner and same permission as all When the owner and the group of a file have the same SID, and permissions for the group is the same as permissions for other, no ACE is needed for the group. --- libntfs-3g/acls.c | 18 ++++++++++++++---- src/secaudit.c | 17 +++++++++++------ 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/libntfs-3g/acls.c b/libntfs-3g/acls.c index 51a7e7f5..925bb96d 100644 --- a/libntfs-3g/acls.c +++ b/libntfs-3g/acls.c @@ -4,7 +4,7 @@ * This module is part of ntfs-3g library, but may also be * integrated in tools running over Linux or Windows * - * Copyright (c) 2007-2014 Jean-Pierre Andre + * Copyright (c) 2007-2015 Jean-Pierre Andre * * This program/include file is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as published @@ -2314,10 +2314,21 @@ return (0); mapping,flags,pxace,pset); break; - case POSIX_ACL_GROUP : case POSIX_ACL_GROUP_OBJ : + /* denials and grants for group when needed */ + if (pset->groupowns && !pset->adminowns + && (pset->grpperms == pset->othperms) + && !pset->designates && !pset->withmask) { + ok = TRUE; + } else { + ok = build_group_denials_grant(pacl,gsid, + mapping,flags,pxace,pset); + } + break; - /* denials and grants for groups */ + case POSIX_ACL_GROUP : + + /* denials and grants for designated groups */ ok = build_group_denials_grant(pacl,gsid, mapping,flags,pxace,pset); @@ -2574,7 +2585,6 @@ static int buildacls(char *secattr, int offs, mode_t mode, int isdir, /* this ACE will be inserted after denials for group */ if (adminowns - || groupowns || (((mode >> 3) ^ mode) & 7)) { grants = WORLD_RIGHTS; if (isdir) { diff --git a/src/secaudit.c b/src/secaudit.c index 66e8f4b3..af408878 100644 --- a/src/secaudit.c +++ b/src/secaudit.c @@ -1,7 +1,7 @@ /* * Display and audit security attributes in an NTFS volume * - * Copyright (c) 2007-2014 Jean-Pierre Andre + * Copyright (c) 2007-2015 Jean-Pierre Andre * * Options : * -a auditing security data @@ -215,6 +215,9 @@ * * Dec 2014, version 1.4.3 * - fixed displaying "UserMapping" as a file name + * + * Mar 2015, version 1.4.5 + * - adapted to new NTFS ACLs when owner is same as group */ /* @@ -238,7 +241,7 @@ * General parameters which may have to be adapted to needs */ -#define AUDT_VERSION "1.4.3" +#define AUDT_VERSION "1.4.5" #define GET_FILE_SECURITY "ntfs_get_file_security" #define SET_FILE_SECURITY "ntfs_set_file_security" @@ -3732,14 +3735,14 @@ void basictest(int kind, BOOL isdir, const SID *owner, const SID *group) 24064, 28160, 24064, 28160, 24064, 28160, - 25416, 29512 + 24904, 29000 } ; u32 expecthash[] = { 0x8f80865b, 0x7bc7960, 0x8fd9ecfe, 0xddd4db0, 0xa8b07400, 0xa189c20, 0xc5689a00, 0xb6c09000, - 0x94bfb419, 0xa4311791 + 0xb040e509, 0x4f4db7f7 } ; #if POSIXACLS struct POSIX_SECURITY *pxdesc; @@ -3881,7 +3884,8 @@ void basictest(int kind, BOOL isdir, const SID *owner, const SID *group) (unsigned long)count,(unsigned long)acecount, (unsigned long)acecount/count,acecount*100L/count%100L); if (acecount != expectcnt[kind]) { - printf("** Error : expected ACE count %lu\n", + printf("** Error : ACE count %lu instead of %lu\n", + (unsigned long)acecount, (unsigned long)expectcnt[kind]); errors++; } @@ -3895,7 +3899,8 @@ void basictest(int kind, BOOL isdir, const SID *owner, const SID *group) (unsigned long)pxcount,(unsigned long)pxacecount, (unsigned long)pxacecount/pxcount,pxacecount*100L/pxcount%100L); if (pxacecount != expectcnt[kind]) { - printf("** Error : expected ACE count %lu\n", + printf("** Error : ACE count %lu instead of %lu\n", + (unsigned long)pxacecount, (unsigned long)expectcnt[kind]); errors++; } From bd2c91d4a3c37b8cee018dd600459d58228ddd0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Pierre=20Andr=C3=A9?= Date: Fri, 17 Apr 2015 11:22:49 +0200 Subject: [PATCH 19/61] Packed/unpacked st_rdev transported as 32-bits on OpenIndiana 64-bits On OpenIndiana 64-bits, st_rdev has major and minor as 32-bits wide each, but the fuse protocol (common to 32-bit and 64-bit) has an st_rdev field limited to 32-bit. For now, pack major and minor the same way as in the 32-bit variant (14 and 18 bits). --- libfuse-lite/fuse_lowlevel.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/libfuse-lite/fuse_lowlevel.c b/libfuse-lite/fuse_lowlevel.c index ee01c7c1..496800e7 100644 --- a/libfuse-lite/fuse_lowlevel.c +++ b/libfuse-lite/fuse_lowlevel.c @@ -69,7 +69,13 @@ static void convert_stat(const struct stat *stbuf, struct fuse_attr *attr) attr->nlink = stbuf->st_nlink; attr->uid = stbuf->st_uid; attr->gid = stbuf->st_gid; +#if defined(__SOLARIS__) && defined(_LP64) + /* Must pack the device the old way (attr->rdev limited to 32 bits) */ + attr->rdev = ((major(stbuf->st_rdev) & 0x3fff) << 18) + | (minor(stbuf->st_rdev) & 0x3ffff); +#else attr->rdev = stbuf->st_rdev; +#endif attr->size = stbuf->st_size; attr->blocks = stbuf->st_blocks; attr->atime = stbuf->st_atime; @@ -553,9 +559,16 @@ static void do_mknod(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) else name = (const char *) inarg + FUSE_COMPAT_MKNOD_IN_SIZE; - if (req->f->op.mknod) + if (req->f->op.mknod) { +#if defined(__SOLARIS__) && defined(_LP64) + /* Must unpack the device, as arg->rdev is limited to 32 bits */ + req->f->op.mknod(req, nodeid, name, arg->mode, + makedev((arg->rdev >> 18) & 0x3ffff, + arg->rdev & 0x3fff)); +#else req->f->op.mknod(req, nodeid, name, arg->mode, arg->rdev); - else +#endif + } else fuse_reply_err(req, ENOSYS); } From 5741f54529382b5a14d6ca356c05693ae5f9556e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Pierre=20Andr=C3=A9?= Date: Fri, 17 Apr 2015 11:31:47 +0200 Subject: [PATCH 20/61] Zeroed uninitialized bytes before writing compressed data Compressed records may be written as full clusters even though cluster tails are meaningless. This is to avoid the lower levels doing a read- modify-write cycle. Be sure to zero the meaningless bytes to avoid leaking information. Contributed by Eric Biggers --- libntfs-3g/compress.c | 1 + 1 file changed, 1 insertion(+) diff --git a/libntfs-3g/compress.c b/libntfs-3g/compress.c index f1070aa2..a8d37e8b 100644 --- a/libntfs-3g/compress.c +++ b/libntfs-3g/compress.c @@ -1131,6 +1131,7 @@ static s32 ntfs_comp_set(ntfs_attr *na, runlist_element *rl, outbuf[compsz++] = 0; /* write a full cluster, to avoid partial reading */ rounded = ((compsz - 1) | (clsz - 1)) + 1; + memset(&outbuf[compsz], 0, rounded - compsz); written = write_clusters(vol, rl, offs, rounded, outbuf); if (written != rounded) { /* From 0d01fcabfcb5a5cda569397295b05882df5763b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Pierre=20Andr=C3=A9?= Date: Fri, 17 Apr 2015 11:38:33 +0200 Subject: [PATCH 21/61] Implemented rewindind a directory in lowntfs-3g Rewinding a directory is done by freeing the current list of files and rebuilding it. Only seeking to the beginning of the list is implemented. --- src/lowntfs-3g.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/lowntfs-3g.c b/src/lowntfs-3g.c index 3ff8b9de..9a894df2 100644 --- a/src/lowntfs-3g.c +++ b/src/lowntfs-3g.c @@ -1171,6 +1171,16 @@ static void ntfs_fuse_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, fill = (ntfs_fuse_fill_context_t*)(long)fi->fh; if (fill && (fill->ino == ino)) { + if (fill->filled && !off) { + /* Rewinding : make sure to clear existing results */ + current = fill->first; + while (current) { + current = current->next; + free(fill->first); + fill->first = current; + } + fill->filled = FALSE; + } if (!fill->filled) { /* initial call : build the full list */ first = (ntfs_fuse_fill_item_t*)ntfs_malloc From 99cb156ae5307c20df842949703adbd4b80c32fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Pierre=20Andr=C3=A9?= Date: Mon, 1 Jun 2015 12:48:43 +0200 Subject: [PATCH 22/61] Ported clearing the environment when starting mount or umount When starting mount or umount, the environment was not cleared and could be used for privilege escalation (CVE-2015-3202). This is a port of the fix to full fuse by using execle(3) instead of execl(3) --- libfuse-lite/mount_util.c | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/libfuse-lite/mount_util.c b/libfuse-lite/mount_util.c index 1a7ac3c7..8ea5e088 100644 --- a/libfuse-lite/mount_util.c +++ b/libfuse-lite/mount_util.c @@ -66,6 +66,7 @@ int fuse_mnt_add_mount(const char *progname, const char *fsname, return -1; } if (res == 0) { + char *env = NULL; char templ[] = "/tmp/fusermountXXXXXX"; char *tmp; @@ -87,8 +88,8 @@ int fuse_mnt_add_mount(const char *progname, const char *fsname, exit(1); } rmdir(tmp); - execl("/sbin/mount", "/sbin/mount", "-F", type, "-o", opts, - fsname, mnt, NULL); + execle("/sbin/mount", "/sbin/mount", "-F", type, "-o", opts, + fsname, mnt, NULL, &env); fprintf(stderr, "%s: failed to execute /sbin/mount: %s\n", progname, strerror(errno)); exit(1); @@ -120,9 +121,16 @@ int fuse_mnt_umount(const char *progname, const char *mnt, int lazy) return -1; } if (res == 0) { + char *env = NULL; + setuid(geteuid()); - execl("/sbin/umount", "/sbin/umount", !lazy ? "-f" : NULL, mnt, - NULL); + if (lazy) { + execle("/sbin/umount", "/sbin/umount", mnt, + NULL, &env); + } else { + execle("/sbin/umount", "/sbin/umount", "-f", mnt, + NULL, &env); + } fprintf(stderr, "%s: failed to execute /sbin/umount: %s\n", progname, strerror(errno)); exit(1); @@ -302,6 +310,7 @@ int fuse_mnt_add_mount(const char *progname, const char *fsname, return 0; } if (res == 0) { + char *env = NULL; char templ[] = "/tmp/fusermountXXXXXX"; char *tmp; @@ -325,8 +334,8 @@ int fuse_mnt_add_mount(const char *progname, const char *fsname, exit(1); } rmdir(tmp); - execl("/bin/mount", "/bin/mount", "-i", "-f", "-t", type, "-o", opts, - fsname, mnt, NULL); + execle("/bin/mount", "/bin/mount", "-i", "-f", "-t", type, "-o", opts, + fsname, mnt, NULL, &env); fprintf(stderr, "%s: failed to execute /bin/mount: %s\n", progname, strerror(errno)); exit(1); @@ -353,11 +362,18 @@ int fuse_mnt_umount(const char *progname, const char *mnt, int lazy) return -1; } if (res == 0) { + char *env = NULL; + if (setuid(geteuid())) fprintf(stderr, "%s: failed to setuid : %s\n", progname, strerror(errno)); - execl("/bin/umount", "/bin/umount", "-i", mnt, lazy ? "-l" : NULL, - NULL); + if (lazy) { + execle("/bin/umount", "/bin/umount", "-i", mnt, "-l", + NULL, &env); + } else { + execle("/bin/umount", "/bin/umount", "-i", mnt, + NULL, &env); + } fprintf(stderr, "%s: failed to execute /bin/umount: %s\n", progname, strerror(errno)); exit(1); From 78ad037c662802ce8fb5bf297407f1ce8682be94 Mon Sep 17 00:00:00 2001 From: Erik Larsson Date: Tue, 23 Jun 2015 06:26:52 +0200 Subject: [PATCH 23/61] Make installing mkntfs /sbin symlinks dependent on ENABLE_MOUNT_HELPER. We shouldn't install anything into /sbin unless ENABLE_MOUNT_HELPER is on, which is only true for Linux (these are Linux-specific symlinks). --- ntfsprogs/Makefile.am | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ntfsprogs/Makefile.am b/ntfsprogs/Makefile.am index cbc7ce50..6b2b8aa5 100644 --- a/ntfsprogs/Makefile.am +++ b/ntfsprogs/Makefile.am @@ -149,6 +149,7 @@ extras: libs $(EXTRA_PROGRAMS) # mkfs.ntfs[.8] hard link +if ENABLE_MOUNT_HELPER install-exec-hook: $(INSTALL) -d $(DESTDIR)/sbin $(LN_S) -f $(sbindir)/mkntfs $(DESTDIR)/sbin/mkfs.ntfs @@ -160,5 +161,6 @@ install-data-hook: uninstall-local: $(RM) -f $(DESTDIR)/sbin/mkfs.ntfs $(RM) -f $(DESTDIR)$(man8dir)/mkfs.ntfs.8 +endif endif From 9a7bd25181b09281b220b23117d1cd9bac7788e2 Mon Sep 17 00:00:00 2001 From: Erik Larsson Date: Tue, 23 Jun 2015 06:27:33 +0200 Subject: [PATCH 24/61] Skip installing manpages for mount helpers when they aren't available. --- src/Makefile.am | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Makefile.am b/src/Makefile.am index 89ac5ce0..7fd4af45 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -71,7 +71,6 @@ install-exec-local: install-rootbinPROGRAMS $(MKDIR_P) "$(DESTDIR)/sbin" $(LN_S) -f "$(rootbindir)/ntfs-3g" "$(DESTDIR)/sbin/mount.ntfs-3g" $(LN_S) -f "$(rootbindir)/lowntfs-3g" "$(DESTDIR)/sbin/mount.lowntfs-3g" -endif install-data-local: install-man8 $(LN_S) -f ntfs-3g.8 "$(DESTDIR)$(man8dir)/mount.ntfs-3g.8" @@ -79,7 +78,6 @@ install-data-local: install-man8 uninstall-local: $(RM) -f "$(DESTDIR)$(man8dir)/mount.ntfs-3g.8" -if ENABLE_MOUNT_HELPER $(RM) -f "$(DESTDIR)/sbin/mount.ntfs-3g" "$(DESTDIR)/sbin/mount.lowntfs-3g" endif From c9771d05093fc188d90283fd2d27f629d160d706 Mon Sep 17 00:00:00 2001 From: Erik Larsson Date: Tue, 23 Jun 2015 06:43:17 +0200 Subject: [PATCH 25/61] unistr.c: Cleanup of OS X Unicode normalization code. Normalize coding conventions to fit in with the rest of NTFS-3G, including line breaks at column 80. --- libntfs-3g/unistr.c | 78 ++++++++++++++++++++++++++++----------------- 1 file changed, 49 insertions(+), 29 deletions(-) diff --git a/libntfs-3g/unistr.c b/libntfs-3g/unistr.c index 94215fff..67af9030 100644 --- a/libntfs-3g/unistr.c +++ b/libntfs-3g/unistr.c @@ -1571,21 +1571,24 @@ int ntfs_set_char_encoding(const char *locale) int ntfs_macosx_normalize_filenames(int normalize) { #ifdef ENABLE_NFCONV - if(normalize == 0 || normalize == 1) { + if (normalize == 0 || normalize == 1) { nfconvert_utf8 = normalize; return 0; } - else + else { return -1; + } #else return -1; #endif /* ENABLE_NFCONV */ } int ntfs_macosx_normalize_utf8(const char *utf8_string, char **target, - int composed) { + int composed) +{ #ifdef ENABLE_NFCONV - /* For this code to compile, the CoreFoundation framework must be fed to the linker. */ + /* For this code to compile, the CoreFoundation framework must be fed to + * the linker. */ CFStringRef cfSourceString; CFMutableStringRef cfMutableString; CFRange rangeToProcess; @@ -1594,52 +1597,69 @@ int ntfs_macosx_normalize_utf8(const char *utf8_string, char **target, int resultLength = -1; /* Convert the UTF-8 string to a CFString. */ - cfSourceString = CFStringCreateWithCString(kCFAllocatorDefault, utf8_string, kCFStringEncodingUTF8); - if(cfSourceString == NULL) { + cfSourceString = CFStringCreateWithCString(kCFAllocatorDefault, + utf8_string, kCFStringEncodingUTF8); + if (cfSourceString == NULL) { ntfs_log_error("CFStringCreateWithCString failed!\n"); return -2; } - - /* Create a mutable string from cfSourceString that we are free to modify. */ - cfMutableString = CFStringCreateMutableCopy(kCFAllocatorDefault, 0, cfSourceString); + + /* Create a mutable string from cfSourceString that we are free to + * modify. */ + cfMutableString = CFStringCreateMutableCopy(kCFAllocatorDefault, 0, + cfSourceString); CFRelease(cfSourceString); /* End-of-life. */ - if(cfMutableString == NULL) { + if (cfMutableString == NULL) { ntfs_log_error("CFStringCreateMutableCopy failed!\n"); return -3; } - + /* Normalize the mutable string to the desired normalization form. */ - CFStringNormalize(cfMutableString, (composed != 0 ? kCFStringNormalizationFormC : kCFStringNormalizationFormD)); - - /* Store the resulting string in a '\0'-terminated UTF-8 encoded char* buffer. */ + CFStringNormalize(cfMutableString, (composed != 0 ? + kCFStringNormalizationFormC : kCFStringNormalizationFormD)); + + /* Store the resulting string in a '\0'-terminated UTF-8 encoded char* + * buffer. */ rangeToProcess = CFRangeMake(0, CFStringGetLength(cfMutableString)); - if(CFStringGetBytes(cfMutableString, rangeToProcess, kCFStringEncodingUTF8, 0, false, NULL, 0, &requiredBufferLength) > 0) { - resultLength = sizeof(char)*(requiredBufferLength + 1); + if (CFStringGetBytes(cfMutableString, rangeToProcess, + kCFStringEncodingUTF8, 0, false, NULL, 0, + &requiredBufferLength) > 0) + { + resultLength = sizeof(char) * (requiredBufferLength + 1); result = ntfs_calloc(resultLength); - - if(result != NULL) { - if(CFStringGetBytes(cfMutableString, rangeToProcess, kCFStringEncodingUTF8, - 0, false, (UInt8*)result, resultLength-1, &requiredBufferLength) <= 0) { - ntfs_log_error("Could not perform UTF-8 conversion of normalized CFMutableString.\n"); + + if (result != NULL) { + if (CFStringGetBytes(cfMutableString, rangeToProcess, + kCFStringEncodingUTF8, 0, false, + (UInt8*) result, resultLength - 1, + &requiredBufferLength) <= 0) + { + ntfs_log_error("Could not perform UTF-8 " + "conversion of normalized " + "CFMutableString.\n"); free(result); result = NULL; } } - else - ntfs_log_error("Could not perform a ntfs_calloc of %d bytes for char *result.\n", resultLength); + else { + ntfs_log_error("Could not perform a ntfs_calloc of %d " + "bytes for char *result.\n", resultLength); + } + } + else { + ntfs_log_error("Could not perform check for required length of " + "UTF-8 conversion of normalized CFMutableString.\n"); } - else - ntfs_log_error("Could not perform check for required length of UTF-8 conversion of normalized CFMutableString.\n"); - CFRelease(cfMutableString); - - if(result != NULL) { + + if (result != NULL) { *target = result; return resultLength - 1; } - else + else { return -1; + } #else return -1; #endif /* ENABLE_NFCONV */ From 2c11aaa2aa9900f24f09c6903c3b9fdb79e38689 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Pierre=20Andr=C3=A9?= Date: Tue, 14 Jul 2015 08:37:01 +0200 Subject: [PATCH 26/61] Fixed the range of valid subauthority counts in a SID ntfs_valid_sid() required that the subauthority count be between 1 and 8 inclusively. However, Windows permits more than 8 subauthorities as well as 0 subauthorities: - The install.wim file for the latest Windows 10 build contains a file whose DACL contains a SID with 10 subauthorities. ntfs_set_ntfs_acl() was failing on this file. - The IsValidSid() function on Windows returns true for subauthority less than or equal to 15, including 0. There was actually already a another SID validation function that had the Windows-compatible behavior, so I merged the two together. Contributed by Eric Biggers --- include/ntfs-3g/security.h | 16 ---------------- libntfs-3g/acls.c | 16 +++++++++------- libntfs-3g/security.c | 4 ++-- 3 files changed, 11 insertions(+), 25 deletions(-) diff --git a/include/ntfs-3g/security.h b/include/ntfs-3g/security.h index 8875c9c1..91671552 100644 --- a/include/ntfs-3g/security.h +++ b/include/ntfs-3g/security.h @@ -222,22 +222,6 @@ enum { extern BOOL ntfs_guid_is_zero(const GUID *guid); extern char *ntfs_guid_to_mbs(const GUID *guid, char *guid_str); -/** - * ntfs_sid_is_valid - determine if a SID is valid - * @sid: SID for which to determine if it is valid - * - * Determine if the SID pointed to by @sid is valid. - * - * Return TRUE if it is valid and FALSE otherwise. - */ -static __inline__ BOOL ntfs_sid_is_valid(const SID *sid) -{ - if (!sid || sid->revision != SID_REVISION || - sid->sub_authority_count > SID_MAX_SUB_AUTHORITIES) - return FALSE; - return TRUE; -} - extern int ntfs_sid_to_mbs_size(const SID *sid); extern char *ntfs_sid_to_mbs(const SID *sid, char *sid_str, size_t sid_str_size); diff --git a/libntfs-3g/acls.c b/libntfs-3g/acls.c index 925bb96d..500d60f5 100644 --- a/libntfs-3g/acls.c +++ b/libntfs-3g/acls.c @@ -362,16 +362,18 @@ unsigned int ntfs_attr_size(const char *attr) return (attrsz); } -/* - * Do sanity checks on a SID read from storage - * (just check revision and number of authorities) +/** + * ntfs_valid_sid - determine if a SID is valid + * @sid: SID for which to determine if it is valid + * + * Determine if the SID pointed to by @sid is valid. + * + * Return TRUE if it is valid and FALSE otherwise. */ - BOOL ntfs_valid_sid(const SID *sid) { - return ((sid->revision == SID_REVISION) - && (sid->sub_authority_count >= 1) - && (sid->sub_authority_count <= 8)); + return sid && sid->revision == SID_REVISION && + sid->sub_authority_count <= SID_MAX_SUB_AUTHORITIES; } /* diff --git a/libntfs-3g/security.c b/libntfs-3g/security.c index 3ac4790a..e00bcf95 100644 --- a/libntfs-3g/security.c +++ b/libntfs-3g/security.c @@ -224,7 +224,7 @@ int ntfs_sid_to_mbs_size(const SID *sid) { int size, i; - if (!ntfs_sid_is_valid(sid)) { + if (!ntfs_valid_sid(sid)) { errno = EINVAL; return -1; } @@ -298,7 +298,7 @@ char *ntfs_sid_to_mbs(const SID *sid, char *sid_str, size_t sid_str_size) * No need to check @sid if !@sid_str since ntfs_sid_to_mbs_size() will * check @sid, too. 8 is the minimum SID string size. */ - if (sid_str && (sid_str_size < 8 || !ntfs_sid_is_valid(sid))) { + if (sid_str && (sid_str_size < 8 || !ntfs_valid_sid(sid))) { errno = EINVAL; return NULL; } From 8a3c3c477cd728aa46f59fa4d85402aae5b06fc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Pierre=20Andr=C3=A9?= Date: Wed, 30 Sep 2015 08:18:13 +0200 Subject: [PATCH 27/61] Used incremental offsets when reading a directory in lowntfs-3g Using incremental offsets avoids misinterpreting readdir() as rewinddir(). It also makes possible to implement seekdir() [not done yet] --- src/lowntfs-3g.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/lowntfs-3g.c b/src/lowntfs-3g.c index 9a894df2..40004a41 100644 --- a/src/lowntfs-3g.c +++ b/src/lowntfs-3g.c @@ -170,6 +170,7 @@ typedef struct fill_item { typedef struct fill_context { struct fill_item *first; struct fill_item *last; + off_t off; fuse_req_t req; fuse_ino_t ino; BOOL filled; @@ -1052,7 +1053,7 @@ static int ntfs_fuse_filler(ntfs_fuse_fill_context_t *fill_ctx, sz = fuse_add_direntry(fill_ctx->req, ¤t->buf[current->off], current->bufsize - current->off, - filename, &st, current->off); + filename, &st, current->off + fill_ctx->off); if (!sz || ((current->off + sz) > current->bufsize)) { newone = (ntfs_fuse_fill_item_t*)ntfs_malloc (sizeof(ntfs_fuse_fill_item_t) @@ -1063,11 +1064,12 @@ static int ntfs_fuse_filler(ntfs_fuse_fill_context_t *fill_ctx, newone->next = (ntfs_fuse_fill_item_t*)NULL; current->next = newone; fill_ctx->last = newone; + fill_ctx->off += current->off; current = newone; sz = fuse_add_direntry(fill_ctx->req, current->buf, current->bufsize - current->off, - filename, &st, current->off); + filename, &st, fill_ctx->off); if (!sz) { errno = EIO; ntfs_log_error("Could not add a" @@ -1124,6 +1126,7 @@ static void ntfs_fuse_opendir(fuse_req_t req, fuse_ino_t ino, = (ntfs_fuse_fill_item_t*)NULL; fill->filled = FALSE; fill->ino = ino; + fill->off = 0; } fi->fh = (long)fill; } @@ -1192,6 +1195,7 @@ static void ntfs_fuse_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, fill->req = req; fill->first = first; fill->last = first; + fill->off = 0; ni = ntfs_inode_open(ctx->vol,INODE(ino)); if (!ni) err = -errno; From 5ebe36f10ab6aa30e1f6d1c0b285e8218705b43d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Pierre=20Andr=C3=A9?= Date: Wed, 30 Sep 2015 08:25:25 +0200 Subject: [PATCH 28/61] Mentioned the starting sector when it overflows in mkntfs In an NTFS boot sector, the first sector of the partition is limited to 32 bits and it may overflow on large disks. This field is only used for booting on the partition and it is ignored by ntfs-3g, but the warning in mkntfs mislead users, so improve it. --- ntfsprogs/mkntfs.8.in | 4 +++- ntfsprogs/mkntfs.c | 10 ++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/ntfsprogs/mkntfs.8.in b/ntfsprogs/mkntfs.8.in index ce687f7d..a83cab9c 100644 --- a/ntfsprogs/mkntfs.8.in +++ b/ntfsprogs/mkntfs.8.in @@ -156,7 +156,9 @@ omitted, .B mkntfs attempts to determine .I part\-start\-sect -automatically and if that fails a default of 0 is used. Note that +automatically and if that fails or the value is oversized, a +default of 0 is used. The partition is usable despite a wrong value, +however note that a correct .I part\-start\-sect is required for Windows to be able to boot from the created volume. .TP diff --git a/ntfsprogs/mkntfs.c b/ntfsprogs/mkntfs.c index a7b1fd23..c49cae47 100644 --- a/ntfsprogs/mkntfs.c +++ b/ntfsprogs/mkntfs.c @@ -3635,10 +3635,12 @@ static BOOL mkntfs_override_vol_params(ntfs_volume *vol) opts.part_start_sect = 0; winboot = FALSE; } else if (opts.part_start_sect >> 32) { - ntfs_log_warning("The partition start sector specified " - "for %s and the automatically determined value " - "is too large. It has been set to 0.\n", - vol->dev->d_name); + ntfs_log_warning("The partition start sector was not " + "specified for %s and the automatically " + "determined value is too large (%lld). " + "It has been set to 0.\n", + vol->dev->d_name, + (long long)opts.part_start_sect); opts.part_start_sect = 0; winboot = FALSE; } From 9c2657b4ae0a90b262a2d8a90b892538ea080d8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Pierre=20Andr=C3=A9?= Date: Wed, 30 Sep 2015 08:36:37 +0200 Subject: [PATCH 29/61] Made use of fuse module cache on OpenIndiana The fuse cache does not handle properly hard-linked files, so ntfs-3g traditionally disables it by using a null time-out. With an upgrade of the fuse kernel module on OpenIndiana, cacheing of non hard-linked files is now possible, so use it. --- include/ntfs-3g/param.h | 13 ++++++++++--- src/lowntfs-3g.c | 12 ++++++++---- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/include/ntfs-3g/param.h b/include/ntfs-3g/param.h index f67193c4..3210fab9 100644 --- a/include/ntfs-3g/param.h +++ b/include/ntfs-3g/param.h @@ -110,12 +110,14 @@ enum { * Possible values for high level : * 1 : no cache, kernel control (recommended) * 4 : no cache, file system control + * 6 : kernel/fuse cache, file system control (OpenIndiana only) * 7 : no cache, kernel control for ACLs * * Possible values for low level : * 2 : no cache, kernel control * 3 : use kernel/fuse cache, kernel control (external fuse >= 2.8) - * 5 : no cache, file system control (recommended) + * 5 : no cache, file system control (recommended on Linux) + * 6 : kernel/fuse cache, file system control (OpenIndiana only) * 8 : no cache, kernel control for ACLs * * Use of options 7 and 8 requires a patch to fuse @@ -124,14 +126,19 @@ enum { */ #if defined(__sun) && defined(__SVR4) -#define HPERMSCONFIG 4 /* access control by kernel is broken on OpenIndiana */ +/* + * Access control by kernel is not implemented on OpenIndiana, + * however care is taken of cacheing hard-linked files. + */ +#define HPERMSCONFIG 6 +#define LPERMSCONFIG 6 #else #define HPERMSCONFIG 1 -#endif #if defined(FUSE_INTERNAL) || !defined(FUSE_VERSION) || (FUSE_VERSION < 28) #define LPERMSCONFIG 5 #else #define LPERMSCONFIG 3 #endif +#endif /* defined(__sun) && defined(__SVR4) */ #endif /* defined _NTFS_PARAM_H */ diff --git a/src/lowntfs-3g.c b/src/lowntfs-3g.c index 40004a41..c68a49b7 100644 --- a/src/lowntfs-3g.c +++ b/src/lowntfs-3g.c @@ -124,20 +124,24 @@ #error "Incompatible options KERNELACLS and KERNELPERMS" #endif -#if CACHEING & (KERNELACLS | !KERNELPERMS) -#warning "Fuse cacheing is only usable with basic permissions checked by kernel" -#endif - #if !CACHEING #define ATTR_TIMEOUT 0.0 #define ENTRY_TIMEOUT 0.0 #else +#if defined(__sun) && defined (__SVR4) +#define ATTR_TIMEOUT 10.0 +#define ENTRY_TIMEOUT 10.0 +#else /* defined(__sun) && defined (__SVR4) */ /* * FUSE cacheing is only usable with basic permissions * checked by the kernel with external fuse >= 2.8 */ +#if KERNELACLS | !KERNELPERMS +#warning "Fuse cacheing is only usable with basic permissions checked by kernel" +#endif #define ATTR_TIMEOUT (ctx->vol->secure_flags & (1 << SECURITY_DEFAULT) ? 1.0 : 0.0) #define ENTRY_TIMEOUT (ctx->vol->secure_flags & (1 << SECURITY_DEFAULT) ? 1.0 : 0.0) +#endif /* defined(__sun) && defined (__SVR4) */ #endif #define GHOSTLTH 40 /* max length of a ghost file name - see ghostformat */ From 267357899ffef3bbe75f4598916bd4d967a99c67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Pierre=20Andr=C3=A9?= Date: Wed, 30 Sep 2015 08:44:56 +0200 Subject: [PATCH 30/61] Made secaudit to load the library based on its generic name When compiled autonomously without the automatically generated dynamic link stubs, use a generic library name instead of a version dependent one. (obsolete compile mode rarely used). --- src/secaudit.c | 5 ++++- src/secaudit.h | 9 +++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/secaudit.c b/src/secaudit.c index af408878..dbde0ca4 100644 --- a/src/secaudit.c +++ b/src/secaudit.c @@ -218,6 +218,9 @@ * * Mar 2015, version 1.4.5 * - adapted to new NTFS ACLs when owner is same as group + * + * May 2015, version 1.4.6 + * - made to load shared library based on generic name */ /* @@ -241,7 +244,7 @@ * General parameters which may have to be adapted to needs */ -#define AUDT_VERSION "1.4.5" +#define AUDT_VERSION "1.4.6" #define GET_FILE_SECURITY "ntfs_get_file_security" #define SET_FILE_SECURITY "ntfs_set_file_security" diff --git a/src/secaudit.h b/src/secaudit.h index a8ad163f..a5cb91b3 100644 --- a/src/secaudit.h +++ b/src/secaudit.h @@ -56,8 +56,13 @@ #else #define USESTUBS 0 /* direct calls to API, based on following definitions */ #define ENVNTFS3G "NTFS3G" -#define LIBFILE64 "/lib64/libntfs-3g.so.4921" -#define LIBFILE "/lib/libntfs-3g.so.4921" +#if defined(__SVR4) +#define LIBFILE64 "/usr/lib/amd64/libntfs-3g.so" +#define LIBFILE "/usr/lib/libntfs-3g.so" +#else +#define LIBFILE64 "/lib64/libntfs-3g.so" +#define LIBFILE "/lib/libntfs-3g.so" +#endif #endif #define MAPDIR ".NTFS-3G" From 4a4ec8c1c6aef9a30d9df1455afb5614a6d398f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Pierre=20Andr=C3=A9?= Date: Wed, 30 Sep 2015 08:51:20 +0200 Subject: [PATCH 31/61] Fixed a bad sanity check in ntfsfix Fix misordered checks to avoid potential segfaults on badly damaged partitions. --- ntfsprogs/ntfsfix.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ntfsprogs/ntfsfix.c b/ntfsprogs/ntfsfix.c index add14d06..d5cbf720 100644 --- a/ntfsprogs/ntfsfix.c +++ b/ntfsprogs/ntfsfix.c @@ -739,13 +739,14 @@ static ATTR_RECORD *find_unnamed_attr(MFT_RECORD *mrec, ATTR_TYPES type) /* fetch the requested attribute */ offset = le16_to_cpu(mrec->attrs_offset); a = (ATTR_RECORD*)((char*)mrec + offset); - while ((a->type != AT_END) - && ((a->type != type) || a->name_length) - && (offset < le32_to_cpu(mrec->bytes_in_use))) { + while ((offset < le32_to_cpu(mrec->bytes_in_use)) + && (a->type != AT_END) + && ((a->type != type) || a->name_length)) { offset += le32_to_cpu(a->length); a = (ATTR_RECORD*)((char*)mrec + offset); } - if ((a->type != type) + if ((offset >= le32_to_cpu(mrec->bytes_in_use)) + || (a->type != type) || a->name_length) a = (ATTR_RECORD*)NULL; return (a); From 94f8d2128e2d423763198df4ca391781bb447655 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Pierre=20Andr=C3=A9?= Date: Wed, 30 Sep 2015 08:54:27 +0200 Subject: [PATCH 32/61] Fixed a wrong test report in ntfsfix When used with the "no-action" option, the test for self-located MFT still reported the partition to have been repaired. Adapt the report to only tell repairing is possible. --- ntfsprogs/ntfsfix.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/ntfsprogs/ntfsfix.c b/ntfsprogs/ntfsfix.c index d5cbf720..fb283e5e 100644 --- a/ntfsprogs/ntfsfix.c +++ b/ntfsprogs/ntfsfix.c @@ -1118,9 +1118,10 @@ static int fix_selfloc_conditions(struct MFT_SELF_LOCATED *selfloc) * * Only low-level library functions can be used. * - * Returns 0 if the conditions for the error were not met or - * the error could be fixed, - * -1 if some error was encountered + * Returns 0 if the conditions for the error was met and + * this error could be fixed, + * -1 if the condition was not met or some error + * which could not be fixed was encountered. */ static int fix_self_located_mft(ntfs_volume *vol) @@ -1147,7 +1148,7 @@ static int fix_self_located_mft(ntfs_volume *vol) ntfs_log_info(res ? FAILED : OK); } else { ntfs_log_info(OK); - res = 0; + res = -1; } free(selfloc.mft0); free(selfloc.mft1); @@ -1378,6 +1379,8 @@ error_exit : * * This is a replay of the normal start up sequence with fixes when * some problem arise. + * + * Returns 0 if there was an error and a fix is available */ static int fix_startup(struct ntfs_device *dev, unsigned long flags) From 9f22e17a167f26cb2fbc5e85351793642fc8f22c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Pierre=20Andr=C3=A9?= Date: Wed, 30 Sep 2015 09:02:15 +0200 Subject: [PATCH 33/61] Improved the check for a valid $MFTMirr The test for a valid $MFTMirr could segfault on a badly damaged partition. Add safety checks. --- libntfs-3g/volume.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/libntfs-3g/volume.c b/libntfs-3g/volume.c index edd76979..9ae4c220 100644 --- a/libntfs-3g/volume.c +++ b/libntfs-3g/volume.c @@ -910,6 +910,7 @@ ntfs_volume *ntfs_device_mount(struct ntfs_device *dev, ntfs_mount_flags flags) ATTR_RECORD *a; VOLUME_INFORMATION *vinf; ntfschar *vname; + u32 record_size; int i, j, eo; unsigned int k; u32 u; @@ -989,7 +990,10 @@ ntfs_volume *ntfs_device_mount(struct ntfs_device *dev, ntfs_mount_flags flags) goto io_error_exit; } } - if (memcmp(mrec, mrec2, ntfs_mft_record_get_data_size(mrec))) { + record_size = ntfs_mft_record_get_data_size(mrec); + if ((record_size <= sizeof(MFT_RECORD)) + || (record_size > vol->mft_record_size) + || memcmp(mrec, mrec2, record_size)) { ntfs_log_error("$MFTMirr does not match $MFT (record " "%d).\n", i); goto io_error_exit; From 800660f728375e5e29faa0be7dd829b10b4ebf23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Pierre=20Andr=C3=A9?= Date: Wed, 30 Sep 2015 09:05:47 +0200 Subject: [PATCH 34/61] Displayed reparse point information in ntfsinfo Added displaying of reparse point data and decode known formats. --- ntfsprogs/ntfsinfo.c | 84 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 74 insertions(+), 10 deletions(-) diff --git a/ntfsprogs/ntfsinfo.c b/ntfsprogs/ntfsinfo.c index eb477037..1db412a6 100644 --- a/ntfsprogs/ntfsinfo.c +++ b/ntfsprogs/ntfsinfo.c @@ -8,7 +8,7 @@ * Copyright (c) 2004-2005 Yuval Fledel * Copyright (c) 2004-2007 Yura Pakhuchiy * Copyright (c) 2005 Cristian Klein - * Copyright (c) 2011-2014 Jean-Pierre Andre + * Copyright (c) 2011-2015 Jean-Pierre Andre * * This utility will dump a file's attributes. * @@ -408,6 +408,23 @@ static char *ntfs_attr_get_name_mbs(ATTR_RECORD *attr) return NULL; } +static const char *reparse_type_name(le32 tag) +{ + const char *name; + le32 IO_REPARSE_TAG_WOF = const_cpu_to_le32(0x80000017); /* temporary */ + + if (tag == IO_REPARSE_TAG_MOUNT_POINT) + name = " (mount point)"; + else + if (tag == IO_REPARSE_TAG_SYMLINK) + name = " (symlink)"; + else + if (tag == IO_REPARSE_TAG_WOF) + name = " (Wof compressed)"; + else + name = ""; + return (name); +} /* *************** functions for dumping global info ******************** */ /** @@ -776,6 +793,8 @@ static void ntfs_dump_attr_list(ATTR_RECORD *attr, ntfs_volume *vol) static void ntfs_dump_filename(const char *indent, FILE_NAME_ATTR *file_name_attr) { + le32 tag; + printf("%sParent directory:\t %lld (0x%llx)\n", indent, (long long)MREF_LE(file_name_attr->parent_directory), (long long)MREF_LE(file_name_attr->parent_directory)); @@ -813,10 +832,12 @@ static void ntfs_dump_filename(const char *indent, (unsigned)file_name_attr->file_name_length); ntfs_dump_flags(indent, AT_FILE_NAME, file_name_attr->file_attributes); if (file_name_attr->file_attributes & FILE_ATTR_REPARSE_POINT && - file_name_attr->reparse_point_tag) - printf("%sReparse point tag:\t 0x%x\n", indent, (unsigned) - le32_to_cpu(file_name_attr->reparse_point_tag)); - else if (file_name_attr->reparse_point_tag) { + file_name_attr->reparse_point_tag) { + tag = file_name_attr->reparse_point_tag; + printf("%sReparse point tag:\t 0x%08lx%s\n", indent, + (long)le32_to_cpu(tag), + reparse_type_name(tag)); + } else if (file_name_attr->reparse_point_tag) { printf("%sEA Length:\t\t %d (0x%x)\n", indent, (unsigned) le16_to_cpu(file_name_attr->packed_ea_size), (unsigned) @@ -1431,6 +1452,7 @@ static void ntfs_dump_index_key(INDEX_ENTRY *entry, INDEX_ATTR_TYPE type) { char *sid; char printable_GUID[37]; + le32 tag; switch (type) { case INDEX_ATTR_SECURE_SII: @@ -1454,8 +1476,10 @@ static void ntfs_dump_index_key(INDEX_ENTRY *entry, INDEX_ATTR_TYPE type) ntfs_log_verbose("\t\tKey GUID:\t\t %s\n", printable_GUID); break; case INDEX_ATTR_REPARSE_R: - ntfs_log_verbose("\t\tKey reparse tag:\t 0x%08x\n", (unsigned) - le32_to_cpu(entry->key.reparse.reparse_tag)); + tag = entry->key.reparse.reparse_tag; + ntfs_log_verbose("\t\tKey reparse tag:\t 0x%08lx%s\n", + (long)le32_to_cpu(tag), + reparse_type_name(tag)); ntfs_log_verbose("\t\tKey file id:\t\t %llu (0x%llx)\n", (unsigned long long) le64_to_cpu(entry->key.reparse.file_id), @@ -1946,9 +1970,49 @@ static void ntfs_dump_attr_bitmap(ATTR_RECORD *attr __attribute__((unused))) * * of ntfs 3.x dumps the reparse_point attribute */ -static void ntfs_dump_attr_reparse_point(ATTR_RECORD *attr __attribute__((unused))) +static void ntfs_dump_attr_reparse_point(ATTR_RECORD *attr + __attribute__((unused)), ntfs_inode *inode) { - /* TODO */ + REPARSE_POINT *reparse; + le32 tag; + const char *name; + u8 *pvalue; + s64 size; + unsigned int length; + unsigned int cnt; + + if (attr->non_resident) { + reparse = ntfs_attr_readall(inode, AT_REPARSE_POINT, + (ntfschar*)NULL, 0, &size); + } else { + reparse = (REPARSE_POINT*)((u8*)attr + + le16_to_cpu(attr->value_offset)); + } + if (reparse) { + tag = reparse->reparse_tag; + name = reparse_type_name(tag); + printf("\tReparse tag:\t\t 0x%08lx%s\n", + (long)le32_to_cpu(tag),name); + length = le16_to_cpu(reparse->reparse_data_length); + printf("\tData length:\t\t %u (0x%x)\n", + (unsigned int)length,(unsigned int)length); + cnt = length; + pvalue = reparse->reparse_data; + printf("\tData:\t\t\t"); + printf(cnt ? " 0x" : "(NONE)"); + if (cnt > 32) + cnt = 32; + while (cnt-- > 0) + printf("%02x",*pvalue++); + if (length > 32) + printf("...\n"); + else + printf("\n"); + if (attr->non_resident) + free(reparse); + } else { + ntfs_log_perror("Failed to get the reparse data"); + } } /** @@ -2300,7 +2364,7 @@ static void ntfs_dump_file_attributes(ntfs_inode *inode) ntfs_dump_attr_bitmap(ctx->attr); break; case AT_REPARSE_POINT: - ntfs_dump_attr_reparse_point(ctx->attr); + ntfs_dump_attr_reparse_point(ctx->attr, inode); break; case AT_EA_INFORMATION: ntfs_dump_attr_ea_information(ctx->attr); From 2f373dee564b1217d73716f2de07e79cccc314bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Pierre=20Andr=C3=A9?= Date: Wed, 30 Sep 2015 09:08:35 +0200 Subject: [PATCH 35/61] ntfsprogs Builds on Windows use macroes to translate printing formats, and these macroes redefined "buf" in a way which causes trouble when "buf" has another meaning. Use "_b" instead. --- ntfsprogs/utils.h | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/ntfsprogs/utils.h b/ntfsprogs/utils.h index 396f698e..6335924e 100644 --- a/ntfsprogs/utils.h +++ b/ntfsprogs/utils.h @@ -109,17 +109,17 @@ int mft_next_record(struct mft_search_ctx *ctx); char *ntfs_utils_reformat(char *out, int sz, const char *fmt); char *ntfs_utils_unix_path(const char *in); #define ntfs_log_redirect(fn,fi,li,le,d,fmt, args...) \ - do { char buf[MAX_FMT]; ntfs_log_redirect(fn,fi,li,le,d, \ - ntfs_utils_reformat(buf,MAX_FMT,fmt), args); } while (0) + do { char _b[MAX_FMT]; ntfs_log_redirect(fn,fi,li,le,d, \ + ntfs_utils_reformat(_b,MAX_FMT,fmt), args); } while (0) #define printf(fmt, args...) \ - do { char buf[MAX_FMT]; \ - printf(ntfs_utils_reformat(buf,MAX_FMT,fmt), args); } while (0) + do { char _b[MAX_FMT]; \ + printf(ntfs_utils_reformat(_b,MAX_FMT,fmt), args); } while (0) #define fprintf(str, fmt, args...) \ - do { char buf[MAX_FMT]; \ - fprintf(str, ntfs_utils_reformat(buf,MAX_FMT,fmt), args); } while (0) + do { char _b[MAX_FMT]; \ + fprintf(str, ntfs_utils_reformat(_b,MAX_FMT,fmt), args); } while (0) #define vfprintf(file, fmt, args) \ - do { char buf[MAX_FMT]; vfprintf(file, \ - ntfs_utils_reformat(buf,MAX_FMT,fmt), args); } while (0) + do { char _b[MAX_FMT]; vfprintf(file, \ + ntfs_utils_reformat(_b,MAX_FMT,fmt), args); } while (0) #endif /** From 80e500c6efaf6ecb0b89d2c182cfad8925e7b680 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Pierre=20Andr=C3=A9?= Date: Wed, 30 Sep 2015 09:35:17 +0200 Subject: [PATCH 36/61] Protected against opendir reinitialization in lownfs-3g Under some condition (probably interference with another process), the directory list gets reinitialized by releasedir() and opendir() at the beginning of a partial buffer. So in readdir() skip to the requested offset. This is a step towards implementing seekdir(). --- src/lowntfs-3g.c | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/lowntfs-3g.c b/src/lowntfs-3g.c index c68a49b7..ec94a33c 100644 --- a/src/lowntfs-3g.c +++ b/src/lowntfs-3g.c @@ -1190,6 +1190,7 @@ static void ntfs_fuse_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, } if (!fill->filled) { /* initial call : build the full list */ + current = (ntfs_fuse_fill_item_t*)NULL; first = (ntfs_fuse_fill_item_t*)ntfs_malloc (sizeof(ntfs_fuse_fill_item_t) + size); if (first) { @@ -1214,12 +1215,23 @@ static void ntfs_fuse_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, if (ntfs_inode_close(ni)) set_fuse_error(&err); } - if (!err) - fuse_reply_buf(req, first->buf, - first->off); - /* reply sent, now must exit with no error */ - fill->first = first->next; - free(first); + if (!err) { + off_t loc = 0; + /* + * In some circumstances, the queue gets + * reinitialized by releasedir() + opendir(), + * apparently always on end of partial buffer. + * Files may be missing or duplicated. + */ + while (first + && ((loc < off) || !first->off)) { + loc += first->off; + fill->first = first->next; + free(first); + first = fill->first; + } + current = first; + } } else err = -errno; } else { @@ -1230,6 +1242,8 @@ static void ntfs_fuse_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, free(fill->first); fill->first = current; } + } + if (!err) { if (current) { fuse_reply_buf(req, current->buf, current->off); fill->first = current->next; From 0cb0173bbcf3d528a52f878ece26f60ce0c24e90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Pierre=20Andr=C3=A9?= Date: Mon, 9 Nov 2015 13:18:58 +0100 Subject: [PATCH 37/61] Implemented recovery of updates committed by Windows ntfsrecover applies to the metadata the updates which were requested on Windows but could not be completed because they were interrupted by some event such as a power failure, a hardware crash, a software crash or the device being unplugged. Doing so, the file system is restored to the latest consistent state. No update to libntfs-3g is required by this implementation. --- configure.ac | 1 + ntfsprogs/Makefile.am | 8 +- ntfsprogs/ntfsrecover.8.in | 165 ++ ntfsprogs/ntfsrecover.c | 4134 ++++++++++++++++++++++++++++++ ntfsprogs/ntfsrecover.h | 339 +++ ntfsprogs/playlog.c | 4826 ++++++++++++++++++++++++++++++++++++ 6 files changed, 9471 insertions(+), 2 deletions(-) create mode 100644 ntfsprogs/ntfsrecover.8.in create mode 100644 ntfsprogs/ntfsrecover.c create mode 100644 ntfsprogs/ntfsrecover.h create mode 100644 ntfsprogs/playlog.c diff --git a/configure.ac b/configure.ac index 6f293784..c2cf8cd1 100644 --- a/configure.ac +++ b/configure.ac @@ -657,6 +657,7 @@ AC_CONFIG_FILES([ ntfsprogs/ntfswipe.8 ntfsprogs/ntfstruncate.8 ntfsprogs/ntfsfallocate.8 + ntfsprogs/ntfsrecover.8 src/Makefile src/ntfs-3g.8 src/ntfs-3g.probe.8 diff --git a/ntfsprogs/Makefile.am b/ntfsprogs/Makefile.am index 6b2b8aa5..0bbc70c5 100644 --- a/ntfsprogs/Makefile.am +++ b/ntfsprogs/Makefile.am @@ -17,7 +17,7 @@ if ENABLE_NTFSPROGS bin_PROGRAMS = ntfsfix ntfsinfo ntfscluster ntfsls ntfscat ntfscmp sbin_PROGRAMS = mkntfs ntfslabel ntfsundelete ntfsresize ntfsclone \ ntfscp -EXTRA_PROGRAM_NAMES = ntfswipe ntfstruncate +EXTRA_PROGRAM_NAMES = ntfswipe ntfstruncate ntfsrecover QUARANTINED_PROGRAM_NAMES = ntfsdump_logfile ntfsmftalloc ntfsmove ntfsck \ ntfsfallocate @@ -26,7 +26,7 @@ man_MANS = mkntfs.8 ntfsfix.8 ntfslabel.8 ntfsinfo.8 \ ntfsundelete.8 ntfsresize.8 ntfsprogs.8 ntfsls.8 \ ntfsclone.8 ntfscluster.8 ntfscat.8 ntfscp.8 \ ntfscmp.8 ntfswipe.8 ntfstruncate.8 \ - ntfsdecrypt.8 ntfsfallocate.8 + ntfsdecrypt.8 ntfsfallocate.8 ntfsrecover.8 EXTRA_MANS = CLEANFILES = $(EXTRA_PROGRAMS) @@ -102,6 +102,10 @@ ntfscmp_SOURCES = ntfscmp.c utils.c utils.h ntfscmp_LDADD = $(AM_LIBS) ntfscmp_LDFLAGS = $(AM_LFLAGS) +ntfsrecover_SOURCES = playlog.c ntfsrecover.c utils.c utils.h ntfsrecover.h +ntfsrecover_LDADD = $(AM_LIBS) $(NTFSRECOVER_LIBS) +ntfsrecover_LDFLAGS = $(AM_LFLAGS) + # We don't distribute these ntfstruncate_SOURCES = attrdef.c ntfstruncate.c utils.c utils.h diff --git a/ntfsprogs/ntfsrecover.8.in b/ntfsprogs/ntfsrecover.8.in new file mode 100644 index 00000000..1fe2e449 --- /dev/null +++ b/ntfsprogs/ntfsrecover.8.in @@ -0,0 +1,165 @@ +.\" Copyright (c) 2015 Jean-Pierre Andre +.\" This file may be copied under the terms of the GNU Public License. +.\" +.TH NTFSRECOVER 8 "September 2015" "ntfs-3g @VERSION@" +.SH NAME +ntfsrecover \- Recover updates committed by Windows on an NTFS volume +.SH SYNOPSIS +\fBntfsrecover\fR [\fIoptions\fR] \fIdevice\fR +.SH DESCRIPTION +.B ntfsrecover +applies to the metadata the updates which were requested on Windows but could +not be completed because they were interrupted by some event such as a power +failure, a hardware crash, a software crash or the device being unplugged. +Doing so, the file system is restored to a consistent state, however updates +to user data may still be lost. + +Updating the file system generally requires updating several records which +should all be made for the file system to be kept consistent. For instance, +creating a new file requires reserving an inode number (set a bit in a bit +map), creating a file record (store the file name and file attributes), and +registering the file in a directory (locate the file from some path). When an +unfortunate event occurs, and one of these updates could be done but not all +of them, the file system is left inconsistent. + +A group of updates which have all to be done to preserve consistency is +called a transaction, and the end of updates within a transaction is called +the commitment of the transaction. + +To protect from unfortunate events, Windows first logs in a special file all +the metadata update requests without applying any, until the commitment is +known. If the event occurs before the commitment, no update has been made and +the file system is consistent. If the event occurs after the update, the log +file can be analyzed later and the transactions which were committed can be +executed again, thus restoring the integrity of the file system. + +.B ntfsrecover +similarly examines the log file and applies the updates within committed +transactions which could not be done by Windows. + +Currently, ntfs-3g does not log updates, so +.B ntfsrecover +cannot be used to restore consistency after an unfortunate event occurred +while the file system was updated by Linux. + +.SH OPTIONS +Below is a summary of all the options that +.B ntfsrecover +accepts. The normal usage is to use no option at all, as most of these +options are oriented towards developers needs. + +Nearly all options have two equivalent names. The short name is +preceded by +.B \- +and the long name is preceded by +.BR \-\- . +Any single letter options, that don't take an argument, can be combined into a +single command, e.g. +.B \-bv +is equivalent to +.BR "\-b \-v" . +Long named options can be abbreviated to any unique prefix of their name. +.TP +\fB\-b\fR, \fB\-\-backward\fR +Examine the actions described in the logfile backward from the latest one to +the earliest one without applying any update. This may encompass records +generated during several sessions, and when Windows is restarted, it often +does not restart writing where it ended the previous session, so this leads +to errors and bad sequencing when examining the full log file. +.TP +\fB\-c\fR, \fB\-\-clusters\fR \fBCLUSTER-RANGE\fR +Restrict the output generated when using options -b -f -u -p +to the actions operating on a cluster within the given cluster range. +CLUSTER-RANGE is defined by the first and last cluster numbers separated +by a hyphen, for instance 100-109 or 0x3e8-0x3ff. A single number means +restricting to a single cluster. The first four log blocks have a special +role and they are always shown. +.TP +\fB\-f\fR, \fB\-\-forward\fR \fBNUM\fR +Examine the actions described in the logfile forward from the first one to +the last one without applying any update. As the log file is reused +circularly, the first one is generally not the earliest. Moreover when +Windows is restarted, it often does not restart writing where it ended the +previous sessions, and this leads to errors when examining a log file +generated during several sessions. +.TP +\fB\-h\fR, \fB\-\-help\fR +Show some help information. +.TP +\fB\-n\fR, \fB\-\-no-action\fR +Do not apply any modification, useful when using the options -p, -s or -u. +.TP +\fB\-p\fR, \fB\-\-play\fR \fBCOUNT\fR +Undo COUNT transaction sets and redo a single one, a transaction set being +all transactions between two consecutive checkpoints. This is useful for +replaying some transaction in the past. As a few actions are not undoable, +this is not always possible. +.TP +\fB\-r\fR, \fB\-\-range\fR \fBBLOCK-RANGE\fR +Examine the actions described in the logfile forward restricted to the +requested log file block range without applying any update. The first four +log blocks have a special role and they are always examined. +.TP +\fB\-s\fR, \fB\-\-sync\fR +Sync the file system by applying the committed actions which have not +been synced previously. This is the default option, used when none of +the options -n, -f, -r, -p and -u are present. + +The option -s can be repeated to request applying the committed actions +mentioned in the obsolete restart page. This is useful for testing the +situations where the latest restart page cannot be read though it can +actually be read. +.TP +\fB\-t\fR, \fB\-\-transactions\fR \fBCOUNT\fR +Display the transaction parameters when examining the log file with one +of the options --forward, --backward or --range. +.TP +\fB\-u\fR, \fB\-\-undo\fR \fBCOUNT\fR +Undo COUNT transaction sets, thus resetting the file system to some +checkpoint in the past, a transaction set being all transactions between +two consecutive checkpoints. As a few actions are not undoable, this is +not always possible. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +Display more debug/warning/error messages. This option may be used twice +to display even more information. +.TP +\fB\-V\fR, \fB\-\-version\fR +Show the version number, copyright and license of +.BR ntfsrecover . +.SH EXAMPLES +Sync an NTFS volume on /dev/sda1. +.RS +.sp +.B ntfsrecover -s /dev/sda1 +.sp +.RE +Display all actions which updated a cluster in range 100 to 119 : +.RS +.sp +.B ntfsrecover --verbose --backward --clusters=100-119 /dev/sda1 +.sp +.RE +.SH BUGS +If you find a bug please send an email describing the problem to the +development team: +.br +.nh +ntfs\-3g\-devel@lists.sf.net +.hy +.SH AUTHORS +.B ntfsrecover +was written by Jean-Pierre Andre +.SH AVAILABILITY +.B ntfsrecover +is part of the +.B ntfs-3g +package and is available from: +.br +.nh +http://www.tuxera.com/community/ +.hy +.SH SEE ALSO +.BR ntfs-3g (8), +.BR ntfsfix (8), +.BR ntfsprogs (8) diff --git a/ntfsprogs/ntfsrecover.c b/ntfsprogs/ntfsrecover.c new file mode 100644 index 00000000..ae92c896 --- /dev/null +++ b/ntfsprogs/ntfsrecover.c @@ -0,0 +1,4134 @@ +/* + * Process log data from an NTFS partition + * + * Copyright (c) 2012-2015 Jean-Pierre Andre + * + * This program examines the Windows log file of an ntfs partition + * and plays the committed transactions in order to restore the + * integrity of metadata. + * + * It can also display the contents of the log file in human-readable + * text, either from a full partition or from the log file itself. + * + * + * History + * + * Sep 2012 + * - displayed textual logfile contents forward + * + * Nov 2014 + * - decoded multi-page log records + * - displayed textual logfile contents backward + * + * Nov 2015 + * - made a general cleaning and redesigned as an ntfsprogs + * - applied committed actions from logfile + */ + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program (in the main directory of the NTFS-3G + * distribution in the file COPYING); if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define BASEBLKS 4 /* number of special blocks (always shown) */ +#define RSTBLKS 2 /* number of restart blocks */ +#define BUFFERCNT 64 /* number of block buffers - a power of 2 */ +#define NTFSBLKLTH 512 /* usa block size */ +#define SHOWATTRS 20 /* max attrs shown in a dump */ +#define SHOWLISTS 10 /* max lcn or lsn shown in a list */ +#define BLOCKBITS 9 /* This is only used to read the restart page */ +#define MAXEXCEPTION 10 /* Max number of exceptions (option -x) */ +#define MINRECSIZE 48 /* Minimal log record size */ +#define MAXRECSIZE 65536 /* Maximal log record size (seen > 56000) */ + +#include "config.h" + +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STDIO_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif +#ifdef HAVE_FCNTL_H +#include +#endif +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif +#ifdef HAVE_GETOPT_H +#include +#endif +#ifdef HAVE_MALLOC_H +#include +#endif +#ifdef HAVE_TIME_H +#include +#endif + +#include "types.h" +#include "endians.h" +#include "support.h" +#include "layout.h" +#include "param.h" +#include "ntfstime.h" +#include "device_io.h" +#include "device.h" +#include "logging.h" +#include "runlist.h" +#include "mft.h" +#include "inode.h" +#include "attrib.h" +#include "bitmap.h" +#include "index.h" +#include "volume.h" +#include "unistr.h" +#include "mst.h" +#include "ntfsrecover.h" +#include "utils.h" +#include "misc.h" + +typedef struct { + ntfs_volume *vol; + FILE *file; + struct ACTION_RECORD *firstaction; + struct ACTION_RECORD *lastaction; +} CONTEXT; + +typedef enum { T_OK, T_ERR, T_DONE } TRISTATE; + +struct RESTART_PAGE_HEADER log_header; +struct RESTART_AREA restart; +struct RESTART_CLIENT client; +u32 clustersz = 0; +int clusterbits; +u32 blocksz; +int blockbits; +u16 bytespersect; +u64 mftlcn; +u32 mftrecsz; +int mftrecbits; +u32 mftcnt; /* number of entries */ +ntfs_inode *log_ni; +ntfs_attr *log_na; +u64 logfilelcn; +u32 logfilesz; /* bytes */ +u64 redos_met; +u64 committed_lsn; +u64 synced_lsn; +u64 latest_lsn; +u64 restart_lsn; +unsigned long firstblk; /* first block to dump (option -r) */ +unsigned long lastblk; /* last block to dump (option -r) */ +u64 firstlcn; /* first block to dump (option -c) */ +u64 lastlcn; /* last block to dump (option -c) */ +BOOL optb; /* show the log backward */ +BOOL optc; /* restrict to cluster range */ +BOOL optd; /* device argument present*/ +BOOL opth; /* show help */ +BOOL opti; /* show invalid (stale) records */ +BOOL optf; /* show full log */ +BOOL optn; /* do not apply modifications */ +BOOL optp; /* count of transaction sets to play */ +BOOL optr; /* show a range of blocks */ +int opts; /* sync the file system */ +BOOL optt; /* show transactions */ +BOOL optu; /* count of transaction sets to undo */ +int optv; /* verbose */ +int optV; /* version */ +int optx[MAXEXCEPTION + 1]; +struct ATTR **attrtable; +unsigned int actionnum; +unsigned int attrcount; +unsigned int playcount; +unsigned int playedactions; // change the name +unsigned int redocount; +unsigned int undocount; +struct BUFFER *buffer_table[BASEBLKS + BUFFERCNT]; + +static const le16 SDS[4] = { + const_cpu_to_le16('$'), const_cpu_to_le16('S'), + const_cpu_to_le16('D'), const_cpu_to_le16('S') +} ; + +static const le16 I30[4] = { + const_cpu_to_le16('$'), const_cpu_to_le16('I'), + const_cpu_to_le16('3'), const_cpu_to_le16('0') +} ; + +/* + * Byte address of a log block + */ + +static s64 loclogblk(CONTEXT *ctx, unsigned int blk) +{ + s64 loc; + LCN lcn; + + if (ctx->vol) { + lcn = ntfs_attr_vcn_to_lcn(log_na, + ((s64)blk << blockbits) >> clusterbits); + loc = lcn << clusterbits; + } else { + if (((s64)blk << blockbits) >= logfilesz) + loc = -1; + else + loc = (logfilelcn << clusterbits) + + ((s64)blk << blockbits); + } + return (loc); +} + +/* + * Deprotect a block + * Only to be used for log buffers + * + * Returns 0 if block was found correct + */ + +static int replaceusa(struct BUFFER *buffer, unsigned int lth) +{ + char *buf; + struct RECORD_PAGE_HEADER *record; + unsigned int j; + BOOL err; + unsigned int used; + unsigned int xusa, nusa; + + err = FALSE; + /* Restart blocks have no protection */ + if (buffer->num >= RSTBLKS) { + /* Do not check beyond used sectors */ + record = &buffer->block.record; + used = blocksz; + xusa = le16_to_cpu(record->head.usa_ofs); + nusa = le16_to_cpu(record->head.usa_count); + if (xusa && nusa + && ((xusa + 1) < lth) + && ((nusa - 1)*NTFSBLKLTH == lth)) { + buf = buffer->block.data; + for (j=1; (j> 1; + if (key < attrtable[mid]->key) + high = mid; + else + if (key > attrtable[mid]->key) + low = mid; + else { + low = mid; + high = mid + 1; + } + } + } + if ((low < attrcount) && (attrtable[low]->key == key)) { + pa = attrtable[low]; + if (pa->namelen < lth) { + pa = (struct ATTR*)realloc(pa, + sizeof(struct ATTR) + lth); + attrtable[low] = pa; + } + } else { + mid = low + 1; + if (!low && attrcount && (attrtable[0]->key > key)) + mid = 0; + pa = (struct ATTR*)malloc(sizeof(struct ATTR) + lth); + if (pa) { + if (attrcount++) { + old = attrtable; + attrtable = (struct ATTR**)realloc(attrtable, + attrcount*sizeof(struct ATTR*)); + if (attrtable) { + high = attrcount; + while (--high > mid) + attrtable[high] + = attrtable[high - 1]; + attrtable[mid] = pa; + } else + attrtable = old; + } else { + attrtable = (struct ATTR**) + malloc(sizeof(struct ATTR*)); + attrtable[0] = pa; + } + pa->key = key; + pa->namelen = 0; + pa->type = const_cpu_to_le32(0); + pa->inode = 0; + } + } + return (pa); +} + +/* + * Read blocks in a circular buffer + * + * returns NULL if block cannot be read or it is found bad + * otherwise returns the full unprotected block data + */ + +static const struct BUFFER *read_buffer(CONTEXT *ctx, unsigned int num) +{ + struct BUFFER *buffer; + BOOL got; + + /* + * The first four blocks are stored apart, to make + * sure pages 2 and 3 and the page which is logically + * before them can be accessed at the same time. + * Also, block 0 is smaller because it has to be read + * before the block size is known. + * Note : the last block is supposed to have an odd + * number, and cannot be overwritten by block 4 which + * follows logically. + */ + if (num < BASEBLKS) + buffer = buffer_table[num + BUFFERCNT]; + else + buffer = buffer_table[num & (BUFFERCNT - 1)]; + if (buffer && (buffer->size < blocksz)) { + free(buffer); + buffer = (struct BUFFER*)NULL; + } + if (!buffer) { + buffer = (struct BUFFER*) + malloc(sizeof(struct BUFFER) + blocksz); + buffer->size = blocksz; + buffer->num = num + 1; /* forced to being read */ + buffer->safe = FALSE; + if (num < BASEBLKS) + buffer_table[num + BUFFERCNT] = buffer; + else + buffer_table[num & (BUFFERCNT - 1)] = buffer; + } + if (buffer && (buffer->num != num)) { + buffer->num = num; + if (ctx->vol) + got = (ntfs_attr_pread(log_na,(u64)num << blockbits, + blocksz, buffer->block.data) == blocksz); + else + got = !fseek(ctx->file, loclogblk(ctx, num), 0) + && (fread(buffer->block.data, blocksz, + 1, ctx->file) == 1); + if (got) { + char *data = buffer->block.data; + buffer->headsz = sizeof(struct RECORD_PAGE_HEADER) + + ((2*getle16(data,6) - 1) | 7) + 1; + buffer->safe = !replaceusa(buffer, blocksz); + } else { + buffer->safe = FALSE; + fprintf(stderr,"** Could not read block %d\n", num); + } + } + return (buffer && buffer->safe ? buffer : (const struct BUFFER*)NULL); +} + +void hexdump(const char *buf, unsigned int lth) +{ + unsigned int i,j,k; + + for (i=0; i 0x20) && (buf[j] < 0x7f)) + printf("%c",buf[j]); + else + printf("."); + printf("\n"); + } +} + +/* + * Display a date + */ + +static void showdate(const char *text, le64 lestamp) +{ + time_t utime; + struct tm *ptm; + s64 stamp; + const char *months[] + = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" } ; + + stamp = le64_to_cpu(lestamp); + if ((stamp < ((2147000000 + 134774*86400LL)*10000000LL)) + && (stamp > ((-2147000000 + 134774*86400LL)*10000000LL))) { + /* date within traditional Unix limits */ + utime = stamp/10000000 - 134774*86400LL; + ptm = gmtime(&utime); + printf("%s %02d %3s %4d %2d:%02d:%02d UTC\n", + text, + ptm->tm_mday,months[ptm->tm_mon],ptm->tm_year+1900, + ptm->tm_hour,ptm->tm_min,ptm->tm_sec); + } else { + u32 days; + unsigned int year; + int mon; + int cnt; + + days = stamp/(86400*10000000LL); + year = 1601; + /* periods of 400 years */ + cnt = days/146097; + days -= 146097*cnt; + year += 400*cnt; + /* periods of 100 years */ + cnt = (3*days + 3)/109573; + days -= 36524*cnt; + year += 100*cnt; + /* periods of 4 years */ + cnt = days/1461; + days -= 1461L*cnt; + year += 4*cnt; + /* periods of a single year */ + cnt = (3*days + 3)/1096; + days -= 365*cnt; + year += cnt; + + if ((!(year % 100) ? (year % 400) : (year % 4)) + && (days > 58)) days++; + if (days > 59) { + mon = (5*days + 161)/153; + days -= (153*mon - 162)/5; + } else { + mon = days/31 + 1; + days -= 31*(mon - 1) - 1; + } +if (mon > 12) +{ +printf("** Bad day stamp %lld days %lu mon %d year %u\n", +(long long)stamp,(unsigned long)days,mon,year); +} + printf("%s %02u %3s %4u\n",text, + (unsigned int)days,months[mon-1],(unsigned int)year); + } +} + +void showname(const char *prefix, const char *name, int cnt) +{ + const le16 *n; + int i; + int c; + + printf("%s",prefix); + n = (const le16*)name; + for (i=0; (i> 6) + 0xc0, + (c & 63) + 0x80); + else + printf("%c%c%c", + (c >> 12) + 0xe0, + ((c >> 6) & 63) + 0x80, + (c & 63) + 0x80); + } + printf("\n"); +} + +static const char *commitment(u64 lsn) +{ + const char *commit; + s64 diff; + + /* Computations assume lsn could wraparound, they probably never do */ + diff = lsn - synced_lsn; + if (diff <= 0) + commit = "synced"; + else { + diff = lsn - committed_lsn; + if (diff <= 0) + commit = "committed"; + else { + /* may find lsn from older session */ + diff = lsn - latest_lsn; + if (diff <= 0) + commit = "*uncommitted*"; + else + commit = "*stale*"; + } + } + return (commit); +} + +const char *actionname(int op) +{ + static char buffer[24]; + const char *p; + + switch (op) { + case Noop : + p = "Noop"; + break; + case CompensationlogRecord : + p = "CompensationlogRecord"; + break; + case InitializeFileRecordSegment : + p = "InitializeFileRecordSegment"; + break; + case DeallocateFileRecordSegment : + p = "DeallocateFileRecordSegment"; + break; + case WriteEndofFileRecordSegment : + p = "WriteEndofFileRecordSegment"; + break; + case CreateAttribute : + p = "CreateAttribute"; + break; + case DeleteAttribute : + p = "DeleteAttribute"; + break; + case UpdateResidentValue : + p = "UpdateResidentValue"; + break; + case UpdateNonResidentValue : + p = "UpdateNonResidentValue"; + break; + case UpdateMappingPairs : + p = "UpdateMappingPairs"; + break; + case DeleteDirtyClusters : + p = "DeleteDirtyClusters"; + break; + case SetNewAttributeSizes : + p = "SetNewAttributeSizes"; + break; + case AddIndexEntryRoot : + p = "AddIndexEntryRoot"; + break; + case DeleteIndexEntryRoot : + p = "DeleteIndexEntryRoot"; + break; + case AddIndexEntryAllocation : + p = "AddIndexEntryAllocation"; + break; + case DeleteIndexEntryAllocation : + p = "DeleteIndexEntryAllocation"; + break; + case WriteEndOfIndexBuffer : + p = "WriteEndOfIndexBuffer"; + break; + case SetIndexEntryVcnRoot : + p = "SetIndexEntryVcnRoot"; + break; + case SetIndexEntryVcnAllocation : + p = "SetIndexEntryVcnAllocation"; + break; + case UpdateFileNameRoot : + p = "UpdateFileNameRoot"; + break; + case UpdateFileNameAllocation : + p = "UpdateFileNameAllocation"; + break; + case SetBitsInNonResidentBitMap : + p = "SetBitsInNonResidentBitMap"; + break; + case ClearBitsInNonResidentBitMap : + p = "ClearBitsInNonResidentBitMap"; + break; + case HotFix : + p = "HotFix"; + break; + case EndTopLevelAction : + p = "EndTopLevelAction"; + break; + case PrepareTransaction : + p = "PrepareTransaction"; + break; + case CommitTransaction : + p = "CommitTransaction"; + break; + case ForgetTransaction : + p = "ForgetTransaction"; + break; + case OpenNonResidentAttribute : + p = "OpenNonResidentAttribute"; + break; + case OpenAttributeTableDump : + p = "OpenAttributeTableDump"; + break; + case AttributeNamesDump : + p = "AttributeNamesDump"; + break; + case DirtyPageTableDump : + p = "DirtyPageTableDump"; + break; + case TransactionTableDump : + p = "TransactionTableDump"; + break; + case UpdateRecordDataRoot : + p = "UpdateRecordDataRoot"; + break; + case UpdateRecordDataAllocation : + p = "UpdateRecordDataAllocation"; + break; + case Win10Action35 : + p = "Win10Action35"; + break; + case Win10Action36 : + p = "Win10Action36"; + break; + case Win10Action37 : + p = "Win10Action37"; + break; + default : + sprintf(buffer,"*Unknown-Action-%d*",op); + p = buffer; + break; + } + return (p); +} + +static const char *attrname(unsigned int key) +{ + static char name[256]; + const char *p; + struct ATTR *pa; + unsigned int i; + + if ((key <= 65535) && !(key & 3)) { + pa = getattrentry(key,0); + if (pa) { + if (!pa->namelen) + p = "Unnamed"; + else { + p = name; + /* Assume ascii for now */ + for (i=0; 2*inamelen; i++) + name[i] = le16_to_cpu(pa->name[i]); + name[i] = 0; + } + } else + p = "Undefined"; + } else + p = "Invalid"; + return (p); +} + +int fixnamelen(const char *name, int len) +{ + int i; + + i = 0; + while ((i < len) && (name[i] || name[i + 1])) + i += 2; + return (i); +} + +const char *mftattrname(ATTR_TYPES attr) +{ + static char badattr[24]; + const char *p; + + switch (attr) { + case AT_STANDARD_INFORMATION : + p = "Standard-Information"; + break; + case AT_ATTRIBUTE_LIST : + p = "Attribute-List"; + break; + case AT_FILE_NAME : + p = "Name"; + break; + case AT_OBJECT_ID : + p = "Volume-Version"; + break; + case AT_SECURITY_DESCRIPTOR : + p = "Security-Descriptor"; + break; + case AT_VOLUME_NAME : + p = "Volume-Name"; + break; + case AT_VOLUME_INFORMATION : + p = "Volume-Information"; + break; + case AT_DATA : + p = "Data"; + break; + case AT_INDEX_ROOT : + p = "Index-Root"; + break; + case AT_INDEX_ALLOCATION : + p = "Index-Allocation"; + break; + case AT_BITMAP : + p = "Bitmap"; + break; + case AT_REPARSE_POINT : + p = "Reparse-Point"; + break; + case AT_EA_INFORMATION : + p = "EA-Information"; + break; + case AT_EA : + p = "EA"; + break; + case AT_PROPERTY_SET : + p = "Property-Set"; + break; + case AT_LOGGED_UTILITY_STREAM : + p = "Logged-Utility-Stream"; + break; + case AT_END : + p = "End"; + break; + default : + sprintf(badattr,"*0x%x-Unknown*",attr); + p = badattr; + break; + } + return (p); +} + +static void showattribute(const char *prefix, const struct ATTR *pa) +{ + if (pa) { + if (pa->type) { + printf("%sattr 0x%x : inode %lld type %s", + prefix, pa->key, (long long)pa->inode, + mftattrname(pa->type)); + if (pa->namelen) + showname(" name ",(const char*)pa->name, + pa->namelen/2); + else + printf("\n"); + } else { + if (pa->namelen) { + printf("%sattr 0x%x : type Unknown", + prefix, pa->key); + showname(" name ",(const char*)pa->name, + pa->namelen/2); + } else + printf("%s(definition of attr 0x%x not met)\n", + prefix, pa->key); + } + } +} + +/* + * Determine if an action acts on the MFT + */ + +static BOOL acts_on_mft(int op) +{ + BOOL onmft; + + /* A few actions may have to be added to the list */ + switch (op) { + case InitializeFileRecordSegment : + case DeallocateFileRecordSegment : + case CreateAttribute : + case DeleteAttribute : + case UpdateResidentValue : + case UpdateMappingPairs : + case SetNewAttributeSizes : + case AddIndexEntryRoot : + case DeleteIndexEntryRoot : + case UpdateFileNameRoot : + case WriteEndofFileRecordSegment : + case Win10Action37 : + onmft = TRUE; + break; + default : + onmft = FALSE; + break; + } + return (onmft); +} + +u32 get_undo_offset(const struct LOG_RECORD *logr) +{ + u32 offset; + + if (logr->lcns_to_follow) + offset = 0x30 + le16_to_cpu(logr->undo_offset); + else + offset = 0x28 + le16_to_cpu(logr->undo_offset); + return (offset); +} + +u32 get_redo_offset(const struct LOG_RECORD *logr) +{ + u32 offset; + + if (logr->lcns_to_follow) + offset = 0x30 + le16_to_cpu(logr->redo_offset); + else + offset = 0x28 + le16_to_cpu(logr->redo_offset); + return (offset); +} + +u32 get_extra_offset(const struct LOG_RECORD *logr) +{ + u32 uoffset; + u32 roffset; + + roffset = get_redo_offset(logr) + + le16_to_cpu(logr->redo_length); + uoffset = get_undo_offset(logr) + + le16_to_cpu(logr->undo_length); + return ((((uoffset > roffset ? uoffset : roffset) - 1) | 7) + 1); +} + +static BOOL likelyop(const struct LOG_RECORD *logr) +{ + BOOL likely; + + switch (le32_to_cpu(logr->record_type)) { + case LOG_STANDARD : /* standard record */ + /* Operations in range 0..LastAction-1, can be both null */ + likely = ((unsigned int)le16_to_cpu(logr->redo_operation) + < LastAction) + && ((unsigned int)le16_to_cpu(logr->undo_operation) + < LastAction) + /* Offsets aligned to 8 bytes */ + && !(le16_to_cpu(logr->redo_offset) & 7) + && !(le16_to_cpu(logr->undo_offset) & 7) + /* transaction id must not be null */ + && logr->transaction_id + /* client data length aligned to 8 bytes */ + && !(le32_to_cpu(logr->client_data_length) & 7) + /* client data length less than 64K (131K ?) */ + && (le32_to_cpu(logr->client_data_length) < MAXRECSIZE) + /* if there is redo data, offset must be >= 0x28 */ + && (!le16_to_cpu(logr->redo_length) + || ((unsigned int)le16_to_cpu(logr->redo_offset) >= 0x28)) + /* if there is undo data, offset must be >= 0x28 */ + && (!le16_to_cpu(logr->undo_length) + || ((unsigned int)le16_to_cpu(logr->undo_offset) >= 0x28)); + /* undo data and redo data should be contiguous when both present */ + if (likely && logr->redo_length && logr->undo_length) { + /* undo and redo data may be the same when both present and same size */ + if (logr->undo_offset == logr->redo_offset) { + if (logr->redo_length != logr->undo_length) + likely = FALSE; + } else { + if (le16_to_cpu(logr->redo_offset) + < le16_to_cpu(logr->undo_offset)) { + /* undo expected just after redo */ + if ((((le16_to_cpu(logr->redo_offset) + + le16_to_cpu(logr->redo_length) + - 1) | 7) + 1) + != le16_to_cpu(logr->undo_offset)) + likely = FALSE; + } else { + /* redo expected just after undo */ + if ((((le16_to_cpu(logr->undo_offset) + + le16_to_cpu(logr->undo_length) + - 1) | 7) + 1) + != le16_to_cpu(logr->redo_offset)) + likely = FALSE; + } + } + } + break; + case LOG_CHECKPOINT : /* check-point */ + /* + * undo and redo operations are null + * or CompensationlogRecord with no data + */ + likely = (!logr->redo_operation + || ((logr->redo_operation == const_cpu_to_le16(1)) + && !logr->redo_length)) + && (!logr->undo_operation + || ((logr->undo_operation == const_cpu_to_le16(1)) + && !logr->undo_length)) + /* transaction id must be null */ + && !logr->transaction_id + /* client_data_length is 0x68 or 0x70 (Vista and subsequent) */ + && ((le32_to_cpu(logr->client_data_length) == 0x68) + || (le32_to_cpu(logr->client_data_length) == 0x70)); + break; + default : + likely = FALSE; + break; + } + return (likely); +} + +/* + * Search for a likely record in a block + * + * Must not be used when syncing. + * + * Returns 0 when not found + */ + +static u16 searchlikely(const struct BUFFER *buf) +{ + const struct LOG_RECORD *logr; + const char *data; + u16 k; + + if (opts) + printf("** Error : searchlikely() used for syncing\n"); + data = buf->block.data; + k = buf->headsz; + logr = (const struct LOG_RECORD*)&data[k]; + if (!likelyop(logr)) { + do { + k += 8; + logr = (const struct LOG_RECORD*)&data[k]; + } while ((k <= (blocksz - LOG_RECORD_HEAD_SZ)) + && !likelyop(logr)); + if (k > (blocksz - LOG_RECORD_HEAD_SZ)) + k = 0; + } + return (k); +} + +/* + * From a previous block, determine the location of first record + * + * The previous block must have the beginning of an overlapping + * record, and the current block must have the beginning of next + * record (which can overlap on next blocks). + * The argument "skipped" is the number of blocks in-between. + * + * Note : the overlapping record from previous block does not reach + * the current block when it ends near the end of the last skipped block. + * + * Returns 0 if some bad condition is found + * Returns near blocksz when there is no beginning of record in + * the current block + */ + +static u16 firstrecord(int skipped, const struct BUFFER *buf, + const struct BUFFER *prevbuf) +{ + const struct RECORD_PAGE_HEADER *rph; + const struct RECORD_PAGE_HEADER *prevrph; + const struct LOG_RECORD *logr; + const char *data; + const char *prevdata; + u16 k; + u16 blkheadsz; + s32 size; + + rph = &buf->block.record; + data = buf->block.data; + if (prevbuf) { + prevrph = &prevbuf->block.record; + prevdata = prevbuf->block.data; + blkheadsz = prevbuf->headsz; + /* From previous page, determine where the current one starts */ + k = le16_to_cpu(prevrph->next_record_offset); + /* a null value means there is no full record in next block */ + if (!k) + k = blkheadsz; + } else + k = 0; + /* Minimal size is apparently 48 : offset of redo_operation */ + if (k && ((blocksz - k) >= LOG_RECORD_HEAD_SZ)) { + logr = (const struct LOG_RECORD*)&prevdata[k]; + if (!logr->client_data_length) { + /* + * Sometimes the end of record is free space. + * This apparently means reaching the end of + * a previous session, and must be considered + * as an error. + * We however tolerate this, unless syncing + * is requested. + */ + printf("* Reaching free space at end of block %d\n", + (int)prevbuf->num); + /* As a consequence, there cannot be skipped blocks */ + if (skipped) { + printf("*** Inconsistency : blocks skipped after free space\n"); + k = 0; /* error returned */ + } + if (opts) + k = 0; + else { + k = searchlikely(buf); + printf("* Skipping over free space\n"); + } + } else { + size = le32_to_cpu(logr->client_data_length) + + LOG_RECORD_HEAD_SZ; + if ((size < MINRECSIZE) || (size > MAXRECSIZE)) { + printf("** Bad record size %ld in block %ld" + " offset 0x%x\n", + (long)size,(long)prevbuf->num,(int)k); + k = blkheadsz; + } else { + if ((int)(blocksz - k) >= size) + printf("*** Inconsistency : the final" + " record does not overlap\n"); + k += size - (blocksz - blkheadsz)*(skipped + 1); + } + if ((k <= blkheadsz) + && (k > (blkheadsz - LOG_RECORD_HEAD_SZ))) { + /* There were not enough space in the last skipped block */ + k = blkheadsz; + } else { + if (optv + && ((blocksz - k) < LOG_RECORD_HEAD_SZ)) { + /* Not an error : just no space */ + printf("No minimal record space\n"); + } + if (optv >= 2) + printf("Overlapping record from block %d," + " starting at offset 0x%x\n", + (int)prevbuf->num,(int)k); + } + } + } else { + k = buf->headsz; + if (optv >= 2) { + if (prevbuf) + printf("No minimal record from block %d," + " starting at offset 0x%x\n", + (int)prevbuf->num, (int)k); + else + printf("No block before %d," + " starting at offset 0x%x\n", + (int)buf->num, (int)k); + } + } + /* + * In a wraparound situation, there is frequently no + * match... because there were no wraparound. + * Return an error if syncing is requested, otherwise + * try to find a starting record. + */ + if (k && prevbuf && (prevbuf->num > buf->num)) { + logr = (const struct LOG_RECORD*)&data[k]; + /* Accept reaching the end with no record beginning */ + if ((k != le16_to_cpu(rph->next_record_offset)) + && !likelyop(logr)) { + if (opts) { + k = 0; + printf("** Could not wraparound\n"); + } else { + k = searchlikely(buf); + printf("* Skipping over bad wraparound\n"); + } + } + } + return (k); +} + +/* + * Find the block which defines the first record in current one + * + * Either the wanted block has the beginning of a record overlapping + * on current one, or it ends in such as there is no space for an + * overlapping one. + * + * Returns 0 if the previous block cannot be determined. + */ + +static const struct BUFFER *findprevious(CONTEXT *ctx, const struct BUFFER *buf) +{ + const struct BUFFER *prevbuf; + const struct BUFFER *savebuf; + const struct RECORD_PAGE_HEADER *rph; + int skipped; + int prevblk; + BOOL prevmiddle; + BOOL error; + u16 endoff; + + error = FALSE; + prevblk = buf->num; + skipped = 0; + do { + prevmiddle = FALSE; + if (prevblk > BASEBLKS) + prevblk--; + else + if (prevblk == BASEBLKS) + prevblk = (logfilesz >> blockbits) - 1; + else { + rph = &buf->block.record; + prevblk = (le32_to_cpu(rph->copy.file_offset) + >> blockbits) - 1; + } + /* No previous block if the log only consists of block 2 or 3 */ + if (prevblk < BASEBLKS) { + prevbuf = (struct BUFFER*)NULL; + error = TRUE; /* not a real error */ + } else { + prevbuf = read_buffer(ctx, prevblk); + if (prevbuf) { + rph = &prevbuf->block.record; + prevmiddle = !(rph->flags + & const_cpu_to_le32(1)) + || !rph->next_record_offset; + if (prevmiddle) { + savebuf = prevbuf; + skipped++; + } + } else { + error = TRUE; + printf("** Could not read block %d\n", + (int)prevblk); + } + } + } while (prevmiddle && !error); + + if (!prevmiddle && !error && skipped) { + /* No luck if there is not enough space in this record */ + rph = &prevbuf->block.record; + endoff = le16_to_cpu(rph->next_record_offset); + if (endoff > (blocksz - LOG_RECORD_HEAD_SZ)) { + prevbuf = savebuf; + } + } + return (error ? (struct BUFFER*)NULL : prevbuf); +} + +void copy_attribute(struct ATTR *pa, const char *buf, int length) +{ + const struct ATTR_NEW *panew; + struct ATTR_OLD old_aligned; + + if (pa) { + switch (length) { + case sizeof(struct ATTR_NEW) : + panew = (const struct ATTR_NEW*)buf; + pa->type = panew->type; + pa->lsn = le64_to_cpu(panew->lsn); + pa->inode = MREF(le64_to_cpu(panew->inode)); + break; + case sizeof(struct ATTR_OLD) : + /* Badly aligned, first realign */ + memcpy(&old_aligned,buf,sizeof(old_aligned)); + pa->type = old_aligned.type; + pa->lsn = le64_to_cpu(old_aligned.lsn); + pa->inode = MREF(le64_to_cpu(old_aligned.inode)); + break; + default : + printf("** Unexpected attribute format, length %d\n", + length); + } + } +} + +static int refresh_attributes(const struct ACTION_RECORD *firstaction) +{ + const struct ACTION_RECORD *action; + const struct LOG_RECORD *logr; + struct ATTR *pa; + const char *buf; + u32 extra; + u32 length; + u32 len; + u32 key; + u32 x; + u32 i; + u32 step; + u32 used; + + for (action=firstaction; action; action=action->next) { + logr = &action->record; + buf = ((const char*)logr) + get_redo_offset(logr); + length = le16_to_cpu(logr->redo_length); + switch (le16_to_cpu(action->record.redo_operation)) { + case OpenNonResidentAttribute : + extra = get_extra_offset(logr) + - get_redo_offset(logr); + if (logr->undo_length) { + len = le32_to_cpu(logr->client_data_length) + + LOG_RECORD_HEAD_SZ + - get_extra_offset(logr); + /* this gives a length aligned modulo 8 */ + len = fixnamelen(&buf[extra], len); + } else + len = 0; + pa = getattrentry(le16_to_cpu(logr->target_attribute), + len); + if (pa) { + copy_attribute(pa, buf, length); + pa->namelen = len; + if (len) { + memcpy(pa->name,&buf[extra],len); + } + } + break; + case OpenAttributeTableDump : + i = 24; + step = getle16(buf, 8); + used = getle16(buf, 12); + /* + * Changed from Win10, formerly we got step = 44. + * The record layout has also changed + */ + for (x=0; (x 510) { + printf("** Error : bad" + " attribute name" + " length %d\n", + len); + key = 0; + } + if (key) { /* Apparently, may have to stop before reaching the end */ + pa = getattrentry(key,len); + if (pa) { + pa->namelen = len; + memcpy(pa->name, + &buf[i+4],len); + } + i += len + 6; + x++; + } + } while (key && (i < length)); + } + break; + default : + break; + } + } + return (0); +} + +/* + * Display a fixup + */ + +static void fixup(CONTEXT *ctx, const struct LOG_RECORD *logr, const char *buf, + BOOL redo) +{ + struct ATTR *pa; + int action; + int attr; + int offs; + s32 length; + int extra; + s32 i; + int p; + s32 base; + u16 firstpos; /* position of first mft attribute */ + le32 v; + ATTR_TYPES mftattr; + le64 w; + le64 inode; + le64 size; + int lth; + int len; + + attr = le16_to_cpu(logr->target_attribute); + offs = le16_to_cpu(logr->attribute_offset); + if (redo) { + action = le16_to_cpu(logr->redo_operation); + length = le16_to_cpu(logr->redo_length); + } else { + action = le16_to_cpu(logr->undo_operation); + length = le16_to_cpu(logr->undo_length); + } + if (redo) + printf("redo fixup %dR %s attr 0x%x offs 0x%x\n", + actionnum, actionname(action), attr, offs); + else + printf("undo fixup %dU %s attr 0x%x offs 0x%x\n", + actionnum, actionname(action), attr, offs); + switch (action) { + case InitializeFileRecordSegment : /* 2 */ + /* + * When this is a redo (with a NoOp undo), the + * full MFT record is logged. + * When this is an undo (with DeallocateFileRecordSegment redo), + * only the header of the MFT record is logged. + */ + if (!ctx->vol && !mftrecsz && (length > 8)) { + /* mftrecsz can be determined from usa_count */ + mftrecsz = (getle16(buf,6) - 1)*512; + mftrecbits = 1; + while ((u32)(1 << mftrecbits) < mftrecsz) + mftrecbits++; + } + printf(" new base MFT record, attr 0x%x (%s)\n",attr,attrname(attr)); + printf(" inode %lld\n", + (((long long)le32_to_cpu(logr->target_vcn) + << clusterbits) + + (le16_to_cpu(logr->cluster_index) << 9)) + >> mftrecbits); + if (length >= 18) + printf(" seq number 0x%04x\n",(int)getle16(buf, 16)); + if (length >= 20) + printf(" link count %d\n",(int)getle16(buf, 18)); + if (length >= 24) { + u16 flags; + + flags = getle16(buf, 22); + printf(" flags 0x%x",(int)flags); + switch (flags & 3) { + case 1 : + printf(" (file in use)\n"); + break; + case 3 : + printf(" (directory in use)\n"); + break; + default : + printf(" (not in use)\n"); + break; + } + } + base = getle16(buf, 4) + ((getle16(buf, 6)*2 - 1) | 7) + 1; + while (base < length) { + mftattr = feedle32(buf, base); + printf(" attrib 0x%lx (%s) at offset 0x%x\n", + (long)le32_to_cpu(mftattr), + mftattrname(mftattr), (int)base); + if (mftattr == AT_FILE_NAME) { + showname(" name ",&buf[base + 90], + buf[base + 88] & 255); + inode = feedle64(buf, base + 24); + printf(" parent dir inode %lld\n", + (long long)MREF(le64_to_cpu(inode))); + } + lth = getle32(buf, base + 4); + if ((lth <= 0) || (lth & 7)) + base = length; + else + base += lth; + } + break; + case DeallocateFileRecordSegment : /* 3 */ + printf(" free base MFT record, attr 0x%x (%s)\n", + attr,attrname(attr)); + printf(" inode %lld\n", + (((long long)le32_to_cpu(logr->target_vcn) << clusterbits) + + (le16_to_cpu(logr->cluster_index) << 9)) >> mftrecbits); + break; + case CreateAttribute : /* 5 */ + pa = getattrentry(attr,0); + base = 24; + /* Assume the beginning of the attribute is always present */ + switch (getle32(buf,0)) { + case 0x30 : + printf(" create file name, attr 0x%x\n",attr); + if (pa) + showattribute(" ",pa); + showname(" file ", + &buf[base + 66],buf[base + 64] & 255); + if (base >= -8) + showdate(" created ",feedle64(buf,base + 8)); + if (base >= -16) + showdate(" modified ",feedle64(buf,base + 16)); + if (base >= -24) + showdate(" changed ",feedle64(buf,base + 24)); + if (base >= -32) + showdate(" read ",feedle64(buf,base + 32)); + size = feedle64(buf,base + 40); + printf(" allocated size %lld\n", + (long long)le64_to_cpu(size)); + size = feedle64(buf,base + 48); + printf(" real size %lld\n", + (long long)le64_to_cpu(size)); + v = feedle32(buf,base + 56); + printf(" DOS flags 0x%lx\n", + (long)le32_to_cpu(v)); + break; + case 0x80 : + printf(" create a data stream, attr 0x%x\n",attr); + break; + case 0xc0 : + printf(" create reparse data\n"); + if (pa) + showattribute(" ",pa); + printf(" tag 0x%lx\n",(long)getle32(buf, base)); + showname(" print name ", + &buf[base + 20 + getle16(buf, base + 12)], + getle16(buf, base + 14)/2); + break; + } + break; + case UpdateResidentValue : /* 7 */ + /* + * The record offset designates the mft attribute offset, + * offs and length define a right-justified window in this + * attribute. + * At this stage, we do not know which kind of mft + * attribute this is about, we assume this is standard + * information when it is the first attribute in the + * record. + */ + base = 0x18 - offs; /* p 8 */ + pa = getattrentry(attr,0); + firstpos = 0x30 + (((mftrecsz/512 + 1)*2 - 1 ) | 7) + 1; + if (pa + && !pa->inode + && (pa->type == const_cpu_to_le32(0x80)) + && !(offs & 3) + && (le16_to_cpu(logr->record_offset) == firstpos)) { + printf(" set standard information, attr 0x%x\n",attr); + showattribute(" ",pa); + if ((base >= 0) && ((base + 8) <= length)) + showdate(" created ", + feedle64(buf,base)); + if (((base + 8) >= 0) && ((base + 16) <= length)) + showdate(" modified ", + feedle64(buf,base + 8)); + if (((base + 16) >= 0) && ((base + 24) <= length)) + showdate(" changed ", + feedle64(buf,base + 16)); + if (((base + 24) >= 0) && ((base + 32) <= length)) + showdate(" read ", + feedle64(buf,base + 24)); + if (((base + 32) >= 0) && ((base + 36) <= length)) { + v = feedle32(buf, base + 32); + printf(" DOS flags 0x%lx\n", + (long)le32_to_cpu(v)); + } + if (((base + 52) >= 0) && ((base + 56) <= length)) { + v = feedle32(buf, base + 52); + printf(" security id 0x%lx\n", + (long)le32_to_cpu(v)); + } + if (((base + 64) >= 0) && ((base + 72) <= length)) { + /* + * This is badly aligned for Sparc when + * stamps not present and base == 52 + */ + memcpy(&w, &buf[base + 64], 8); + printf(" journal idx 0x%llx\n", + (long long)le64_to_cpu(w)); + } + } else { + printf(" set an MFT attribute at offset 0x%x, attr 0x%x\n", + (int)offs, attr); + if (pa) + showattribute(" ",pa); + } + break; + case UpdateNonResidentValue : /* 8 */ + printf(" set attr 0x%x (%s)\n",attr,attrname(attr)); + pa = getattrentry(attr,0); + if (pa) + showattribute(" ",pa); + base = 0; /* ? */ +// Should not be decoded, unless attr is of identified type (I30, ...) + if (pa && (pa->namelen == 8) && !memcmp(pa->name, SDS, 8)) { + if (length >= 4) + printf(" security hash 0x%lx\n", + (long)getle32(buf, 0)); + if (length >= 8) + printf(" security id 0x%lx\n", + (long)getle32(buf, 4)); + if (length >= 20) + printf(" entry size %ld\n", + (long)getle32(buf, 16)); + } + if (pa && (pa->namelen == 8) && !memcmp(pa->name, I30, 8)) { + if (!memcmp(buf, "INDX", 4)) + base = 64; /* full record */ + else + base = 0; /* entries */ + inode = feedle64(buf, base); + printf(" inode %lld\n", + (long long)MREF(le64_to_cpu(inode))); + inode = feedle64(buf, base + 16); + printf(" parent inode %lld\n", + (long long)MREF(le64_to_cpu(inode))); + showname(" file ",&buf[base + 82], + buf[base + 80] & 255); + showdate(" date ",feedle64(buf, base + 32)); + } + break; + case UpdateMappingPairs : /* 9 */ + printf(" update runlist in attr 0x%x (%s)\n",attr, + attrname(attr)); + /* argument is a compressed runlist (or part of it ?) */ + /* stop when finding 00 */ + break; + case SetNewAttributeSizes : /* 11 */ + printf(" set sizes in attr 0x%x (%s)\n",attr,attrname(attr)); + base = 0; /* left justified ? */ + size = feedle64(buf,0); + printf(" allocated size %lld\n",(long long)le64_to_cpu(size)); + size = feedle64(buf,8); + printf(" real size %lld\n",(long long)le64_to_cpu(size)); + size = feedle64(buf,16); + printf(" initialized size %lld\n",(long long)le64_to_cpu(size)); + break; + case AddIndexEntryRoot : /* 12 */ + case AddIndexEntryAllocation : /* 14 */ + /* + * The record offset designates the mft attribute offset, + * offs and length define a left-justified window in this + * attribute. + */ + if (action == AddIndexEntryRoot) + printf(" add resident index entry, attr 0x%x\n",attr); + else + printf(" add nonres index entry, attr 0x%x\n",attr); + pa = getattrentry(attr,0); + if (pa) + showattribute(" ",pa); + base = 0; + p = getle16(buf, base + 8); + /* index types may be discriminated by inode in base+0 */ + switch (p) { /* size of index entry */ + case 32 : /* $R entry */ + memcpy(&inode, &buf[base + 20], 8); /* bad align */ + printf(" $R reparse index\n"); + printf(" reparsed inode 0x%016llx\n", + (long long)le64_to_cpu(inode)); + printf(" reparse tag 0x%lx\n", + (long)getle32(buf, 16)); + break; + case 40 : /* $SII entry */ + printf(" $SII security id index\n"); + printf(" security id 0x%lx\n", + (long)getle32(buf, 16)); + printf(" security hash 0x%lx\n", + (long)getle32(buf, 20)); + break; + case 48 : /* $SDH entry */ + printf(" $SDH security id index\n"); + printf(" security id 0x%lx\n", + (long)getle32(buf, 20)); + printf(" security hash 0x%lx\n", + (long)getle32(buf, 16)); + break; + default : + /* directory index are at least 84 bytes long, ntfsdoc p 98 */ + /* have everything needed to create the index */ + lth = buf[base + 80] & 255; + /* consistency of file name length */ + if (getle16(buf,10) == (u32)(2*lth + 66)) { + printf(" directory index\n"); + inode = feedle64(buf,16); + printf(" parent dir inode %lld\n", + (long long)MREF(le64_to_cpu(inode))); + if (feedle32(buf,72) + & const_cpu_to_le32(0x10000000)) + showname(" file (dir) ", + &buf[base + 82], + buf[base + 80] & 255); + else + showname(" file ", + &buf[base + 82], + buf[base + 80] & 255); + inode = feedle64(buf,0); + printf(" file inode %lld\n", + (long long)MREF(le64_to_cpu(inode))); + size = feedle64(buf,64); + printf(" file size %lld\n", + (long long)le64_to_cpu(size)); + showdate(" created ", + feedle64(buf,base + 24)); + showdate(" modified ", + feedle64(buf,base + 32)); + showdate(" changed ", + feedle64(buf,base + 40)); + showdate(" read ", + feedle64(buf,base + 48)); + } else + printf(" unknown index type\n"); + break; + } + break; + case SetIndexEntryVcnRoot : /* 17 */ + printf(" set vcn of non-resident index root, attr 0x%x\n", + attr); + pa = getattrentry(attr,0); + if (pa) + showattribute(" ",pa); + printf(" vcn %lld\n", (long long)getle64(buf,0)); + break; + case UpdateFileNameRoot : /* 19 */ + /* + * Update an entry in a resident directory index. + * The record offset designates the mft attribute offset, + * offs and length define a right-justified window in this + * attribute. + */ + printf(" set directory resident entry, attr 0x%x\n",attr); + base = length - 0x50; + pa = getattrentry(attr,0); + if (pa) + showattribute(" ",pa); + if (pa + && !pa->inode + && (pa->type == const_cpu_to_le32(0x80)) + && !(offs & 3)) { + if (base >= -24) + showdate(" created ",feedle64(buf, + base + 24)); + if (base >= -32) + showdate(" modified ",feedle64(buf, + base + 32)); + if (base >= -40) + showdate(" changed ",feedle64(buf, + base + 40)); + if (base >= -48) + showdate(" read ",feedle64(buf, + base + 48)); + if (base >= -56) { + size = feedle64(buf,base + 56); + printf(" allocated size %lld\n", + (long long)le64_to_cpu(size)); + } + if (base >= -64) { + size = feedle64(buf,base + 64); + printf(" real size %lld\n", + (long long)le64_to_cpu(size)); + } + if (base > -72) { + v = feedle32(buf,base + 72); + printf(" DOS flags 0x%lx\n", + (long)le32_to_cpu(v)); + } + } else { + /* Usually caused by attr not yet defined */ + if (pa && pa->type) + printf("** Unexpected index parameters\n"); + } + break; + case UpdateFileNameAllocation : /* 20 */ + /* update entry in directory index */ + /* only dates, sizes and attrib */ + base = length - 64; /* p 12 */ + printf(" set directory nonres entry, attr 0x%x\n",attr); + pa = getattrentry(attr,0); + if (pa) + showattribute(" ",pa); + if (base >= -8) + showdate(" created ",feedle64(buf, base + 8)); + if (base >= -16) + showdate(" modified ",feedle64(buf, base + 16)); + if (base >= -24) + showdate(" changed ",feedle64(buf, base + 24)); + if (base >= -32) + showdate(" read ",*(const le64*)&buf[base + 32]); + if (base >= -40) { + size = feedle64(buf, base + 40); + printf(" allocated size %lld\n", + (long long)le64_to_cpu(size)); + } + if (base >= -48) { + size = feedle64(buf, base + 48); + printf(" real size %lld\n", + (long long)le64_to_cpu(size)); + } + if (base >= -56) { + v = feedle32(buf, base + 56); + printf(" DOS flags 0x%lx\n",(long)le32_to_cpu(v)); + } + break; + case SetBitsInNonResidentBitMap : /* 21 */ + case ClearBitsInNonResidentBitMap : /* 22 */ + if (action == SetBitsInNonResidentBitMap) + printf(" SetBitsInNonResidentBitMap, attr 0x%x\n", + attr); + else + printf(" ClearBitsInNonResidentBitMap, attr 0x%x\n", + attr); + pa = getattrentry(attr,0); + if (pa) + showattribute(" ",pa); + v = feedle32(buf, 0); + printf(" first bit %ld\n",(long)le32_to_cpu(v)); + v = feedle32(buf, 4); + printf(" bit count %ld\n",(long)le32_to_cpu(v)); + break; + case OpenNonResidentAttribute : /* 28 */ + printf(" OpenNonResidentAttribute, attr 0x%x\n",attr); + extra = get_extra_offset(logr) + - (redo ? get_redo_offset(logr) + : get_undo_offset(logr)); + if (logr->undo_length) { + len = le32_to_cpu(logr->client_data_length) + + LOG_RECORD_HEAD_SZ + - get_extra_offset(logr); + /* this gives a length aligned modulo 8 */ + len = fixnamelen(&buf[extra], len); + } else + len = 0; + pa = getattrentry(attr,len); + if (pa && redo) { + /* + * If this is a redo, collect the attribute data. + * This should only be done when walking forward. + */ + copy_attribute(pa, buf, length); + pa->namelen = len; + if (len) + memcpy(pa->name,&buf[extra],len); + printf(" MFT attribute 0x%lx (%s)\n", + (long)le32_to_cpu(pa->type), + mftattrname(pa->type)); + printf(" lsn 0x%016llx\n", + (long long)pa->lsn); + printf(" inode %lld\n", + (long long)pa->inode); + } + if (logr->undo_length) + showname(" extra : attr name ", &buf[extra], len/2); + if (!redo && length) { + printf(" * undo attr not shown\n"); + } + break; + case OpenAttributeTableDump : /* 29 */ + printf(" OpenAttributeTableDump, attr 0x%x (%s)\n", + attr,attrname(attr)); + i = 24; + if (i < length) { + int x; + int more; + int step; + int used; + + step = getle16(buf, 8); + used = getle16(buf, 12); + /* + * Changed from Win10, formerly we got step = 44. + * The record layout has also changed + */ + if ((step != sizeof(struct ATTR_OLD)) + && (step != sizeof(struct ATTR_NEW))) { + printf(" ** Unexpected step %d\n",step); + } + more = 0; + for (x=0; (xinode, + mftattrname(pa->type)); + if (pa->namelen) + showname(" name ", + (char*)pa->name, + pa->namelen/2); + else + printf("\n"); + } else + more++; + } + } + if (more) + printf(" (%d more attrs not shown)\n",more); + } + break; + case AttributeNamesDump : /* 30 */ + printf(" AttributeNamesDump, attr 0x%x (%s)\n", + attr,attrname(attr)); + i = 8; + if (i < length) { + unsigned int l; + unsigned int key; + int x; + int more; + + more = 0; + x = 0; + do { + l = le16_to_cpu(*(const le16*)&buf[i+2]); + key = le16_to_cpu(*(const le16*)&buf[i]); + if (l > 510) { + printf("** Error : bad attribute name" + " length %d\n",l); + key = 0; + } + /* Apparently, may have to stop before reaching the end */ + if (key) { + pa = getattrentry(key,l); + if (pa) { + pa->namelen = l; + memcpy(pa->name,&buf[i+4],l); + } + if (x < SHOWATTRS) { + printf(" attr 0x%x is",key); + showname(" ",&buf[i+4],l/2); + } else + more++; + i += l + 6; + x++; + } + } while (key && (i < length)); + if (more) + printf(" (%d more attrs not shown)\n",more); + } + break; + default : + break; + } +} + +static void detaillogr(CONTEXT *ctx, const struct LOG_RECORD *logr) +{ + u64 lcn; + u64 baselcn; + unsigned int i; + unsigned int off; + unsigned int undo; + unsigned int redo; + unsigned int extra; + unsigned int end; + unsigned int listsize; + BOOL onmft; + + switch (le32_to_cpu(logr->record_type)) { + case 1 : + onmft = logr->cluster_index + || acts_on_mft(le16_to_cpu(logr->redo_operation)) + || acts_on_mft(le16_to_cpu(logr->undo_operation)); + printf("redo_operation %04x %s\n", + (int)le16_to_cpu(logr->redo_operation), + actionname(le16_to_cpu(logr->redo_operation))); + printf("undo_operation %04x %s\n", + (int)le16_to_cpu(logr->undo_operation), + actionname(le16_to_cpu(logr->undo_operation))); + printf("redo_offset %04x\n", + (int)le16_to_cpu(logr->redo_offset)); + printf("redo_length %04x\n", + (int)le16_to_cpu(logr->redo_length)); + printf("undo_offset %04x\n", + (int)le16_to_cpu(logr->undo_offset)); + printf("undo_length %04x\n", + (int)le16_to_cpu(logr->undo_length)); + printf("target_attribute %04x\n", + (int)le16_to_cpu(logr->target_attribute)); + printf("lcns_to_follow %04x\n", + (int)le16_to_cpu(logr->lcns_to_follow)); + printf("record_offset %04x\n", + (int)le16_to_cpu(logr->record_offset)); + printf("attribute_offset %04x\n", + (int)le16_to_cpu(logr->attribute_offset)); + printf("cluster_index %04x\n", + (int)le16_to_cpu(logr->cluster_index)); + printf("attribute_flags %04x\n", + (int)le16_to_cpu(logr->attribute_flags)); + if (mftrecbits && onmft) + printf("target_vcn %08lx (inode %lld)\n", + (long)le32_to_cpu(logr->target_vcn), + (((long long)le32_to_cpu(logr->target_vcn) + << clusterbits) + + (le16_to_cpu(logr->cluster_index) << 9)) + >> mftrecbits); + else + printf("target_vcn %08lx\n", + (long)le32_to_cpu(logr->target_vcn)); + printf("reserved3 %08lx\n", + (long)le32_to_cpu(logr->reserved3)); + /* Compute a base for the current run of mft */ + baselcn = le64_to_cpu(logr->lcn_list[0]) + - le32_to_cpu(logr->target_vcn); + for (i=0; ilcns_to_follow) + && (ilcn_list[i]); + printf(" (%d offs 0x%x) lcn %016llx",i, + (int)(8*i + sizeof(LOG_RECORD) - 8), + (long long)lcn); + lcn &= 0xffffffffffffULL; + if (mftrecsz && onmft) { + if (clustersz > mftrecsz) + printf(" (MFT records for inodes" + " %lld-%lld)\n", + (long long)((lcn - baselcn) + *clustersz/mftrecsz), + (long long)((lcn + 1 - baselcn) + *clustersz/mftrecsz - 1)); + else + printf(" (MFT record for inode %lld)\n", + (long long)((lcn - baselcn) + *clustersz/mftrecsz)); + printf(" assuming record for inode %lld\n", + (long long)((lcn - baselcn) + *clustersz/mftrecsz + + (le16_to_cpu(logr->cluster_index) + >> 1))); + } else + printf("\n"); + } + /* + * redo_offset and undo_offset are considered unsafe + * (actually they are safe when you know the logic) + * 2) redo : redo (defined by redo_offset) + * 3) undo : undo (defined by undo_offset) + * 4) extra : unknown data (end of undo to data_length) + */ + end = le32_to_cpu(logr->client_data_length) + LOG_RECORD_HEAD_SZ; + if (logr->redo_length && logr->undo_length) + { + /* both undo and redo are present */ + if (le16_to_cpu(logr->undo_offset) <= + le16_to_cpu(logr->redo_offset)) + { + undo = sizeof(LOG_RECORD) - 8 + + 8*le16_to_cpu(logr->lcns_to_follow); + if (logr->redo_offset == logr->undo_offset) + redo = undo; + else + redo = undo + ((le16_to_cpu(logr->undo_length) - 1) | 7) + 1; + extra = redo + ((le16_to_cpu(logr->redo_length) - 1) | 7) + 1; + } + else + { + redo = sizeof(LOG_RECORD) - 8 + + 8*le16_to_cpu(logr->lcns_to_follow); + undo = redo + ((le16_to_cpu(logr->redo_length) - 1) | 7) + 1; + extra = undo + ((le16_to_cpu(logr->undo_length) - 1) | 7) + 1; + } + } + else + if (logr->redo_length) + { + /* redo and not undo */ + redo = undo = sizeof(LOG_RECORD) - 8 + + 8*le16_to_cpu(logr->lcns_to_follow); + extra = redo + ((le16_to_cpu(logr->redo_length) - 1) | 7) + 1; + } + else + { + /* optional undo and not redo */ + redo = undo = sizeof(LOG_RECORD) - 8 + + 8*le16_to_cpu(logr->lcns_to_follow); + extra = undo + ((le16_to_cpu(logr->undo_length) - 1) | 7) + 1; + } + + printf("redo 0x%x (%u) undo 0x%x (%u) extra 0x%x (%d)\n", + redo,(int)(((le16_to_cpu(logr->redo_length) - 1) | 7) + 1), + undo,(int)(((le16_to_cpu(logr->undo_length) - 1) | 7) + 1), + extra,(int)(end > extra ? end - extra : 0)); + + if (logr->redo_length && (get_redo_offset(logr) != redo)) + printf("** Unexpected redo offset 0x%x %u (%u)\n", + get_redo_offset(logr),(int)redo, + (int)le16_to_cpu(logr->lcns_to_follow)); + if (logr->undo_length && (get_undo_offset(logr) != undo)) + printf("** Unexpected undo offset 0x%x %u (%u)\n", + get_undo_offset(logr),(int)undo, + (int)le16_to_cpu(logr->lcns_to_follow)); + if (get_extra_offset(logr) != extra) + printf("** Unexpected extra offset 0x%x %u (%u)\n", + get_extra_offset(logr),(int)extra, + (int)le16_to_cpu(logr->lcns_to_follow)); + + if (extra <= end) + { + /* show redo data */ + if (logr->redo_length) + { + if (logr->lcns_to_follow) + { + off = le16_to_cpu(logr->record_offset) + + le16_to_cpu(logr->attribute_offset); + printf("redo data (new data) cluster 0x%llx pos 0x%x :\n", + (long long)le64_to_cpu(logr->lcn_list[off + >> clusterbits]), + (int)(off & (clustersz - 1))); + } + else + printf("redo data (new data) at offs 0x%x :\n",redo); + if ((u32)(redo + le16_to_cpu(logr->redo_length)) + <= end) + { + hexdump((const char*)logr + + redo,le16_to_cpu(logr->redo_length)); + fixup(ctx, logr, (const char*)logr + redo, TRUE); + } + else printf("redo data overflowing from record\n"); + } + else + { + printf("no redo data (new data)\n"); + fixup(ctx, logr, (const char*)logr + redo, TRUE); + } + + /* show undo data */ + if (logr->undo_length) + { + if (logr->lcns_to_follow) + { + off = le16_to_cpu(logr->record_offset) + + le16_to_cpu(logr->attribute_offset); + printf("undo data (old data) cluster 0x%llx pos 0x%x :\n", + (long long)le64_to_cpu(logr->lcn_list[off + >> clusterbits]), + (int)(off & (clustersz - 1))); + } + else printf("undo data (old data) at offs 0x%x :\n",undo); + if ((u32)(undo + le16_to_cpu(logr->undo_length)) <= end) + { + if ((undo + le16_to_cpu(logr->undo_length)) < 2*blocksz) + { + hexdump((const char*)logr + + undo,le16_to_cpu(logr->undo_length)); + fixup(ctx, logr, (const char*)logr + undo, FALSE); + } + else printf("undo data overflowing from two blocks\n"); + } + else printf("undo data overflowing from record\n"); + } + else + { + printf("no undo data (old data)\n"); + fixup(ctx, logr, (const char*)logr + undo, FALSE); + } + + /* show extra data, if any */ + if (extra != end) + { + if (end > blocksz) + printf("invalid extra data size\n"); + else + { + printf("extra data at offs 0x%x\n",extra); + hexdump((const char*)logr + extra, + end - extra); + } + } + } + else + { + /* sometimes the designated data overflows */ + if (logr->redo_length + && ((u32)(redo + le16_to_cpu(logr->redo_length)) > end)) + printf("* redo data overflows from record\n"); + if (logr->undo_length + && ((u32)(undo + le16_to_cpu(logr->undo_length)) > end)) + printf("* undo data overflows from record\n"); + } + break; + case 2 : + printf("---> checkpoint record\n"); + printf("redo_operation %04x %s\n", + (int)le16_to_cpu(logr->redo_operation), + actionname(le16_to_cpu(logr->redo_operation))); + printf("undo_operation %04x %s\n", + (int)le16_to_cpu(logr->undo_operation), + actionname(le16_to_cpu(logr->undo_operation))); + printf("redo_offset %04x\n", + (int)le16_to_cpu(logr->redo_offset)); + printf("redo_length %04x\n", + (int)le16_to_cpu(logr->redo_length)); + printf("transaction_lsn %016llx\n", + (long long)le64_to_cpu(logr->transaction_lsn)); + printf("attributes_lsn %016llx\n", + (long long)le64_to_cpu(logr->attributes_lsn)); + printf("names_lsn %016llx\n", + (long long)le64_to_cpu(logr->names_lsn)); + printf("dirty_pages_lsn %016llx\n", + (long long)le64_to_cpu(logr->dirty_pages_lsn)); + listsize = le32_to_cpu(logr->client_data_length) + + LOG_RECORD_HEAD_SZ + - offsetof(struct LOG_RECORD, unknown_list); + if (listsize > 8*SHOWLISTS) + listsize = 8*SHOWLISTS; + for (i=0; 8*iunknown_list[i])); + break; + default : + printf("** Unknown action type\n"); + if (le32_to_cpu(logr->client_data_length) < blocksz) { + printf("client_data for record type %ld\n", + (long)le32_to_cpu(logr->record_type)); + hexdump((const char*)&logr->redo_operation, + le32_to_cpu(logr->client_data_length)); + } else + printf("** Bad client data\n"); + break; + } +} + +BOOL within_lcn_range(const struct LOG_RECORD *logr) +{ + u64 lcn; + unsigned int i; + BOOL within; + + within = FALSE; + switch (le32_to_cpu(logr->record_type)) { + case 1 : + for (i=0; ilcns_to_follow); i++) { + lcn = MREF(le64_to_cpu(logr->lcn_list[i])); + if ((lcn >= firstlcn) && (lcn <= lastlcn)) + within = TRUE; + } + break; + default : + break; + } + return (within); +} + +static void showlogr(CONTEXT *ctx, int k, const struct LOG_RECORD *logr) +{ + s32 diff; + + if (optv && (!optc || within_lcn_range(logr))) { + diff = le64_to_cpu(logr->this_lsn) - synced_lsn; + printf("this_lsn %016llx (synced%s%ld) %s\n", + (long long)le64_to_cpu(logr->this_lsn), + (diff < 0 ? "" : "+"),(long)diff, + commitment(diff + synced_lsn)); + printf("client_previous_lsn %016llx\n", + (long long)le64_to_cpu(logr->client_previous_lsn)); + printf("client_undo_next_lsn %016llx\n", + (long long)le64_to_cpu(logr->client_undo_next_lsn)); + printf("client_data_length %08lx\n", + (long)le32_to_cpu(logr->client_data_length)); + printf("seq_number %d\n", + (int)le16_to_cpu(logr->client_id.seq_number)); + printf("client_index %d\n", + (int)le16_to_cpu(logr->client_id.client_index)); + printf("record_type %08lx\n", + (long)le32_to_cpu(logr->record_type)); + printf("transaction_id %08lx\n", + (long)le32_to_cpu(logr->transaction_id)); + printf("log_record_flags %04x\n", + (int)le16_to_cpu(logr->log_record_flags)); + printf("reserved1 %04x %04x %04x\n", + (int)le16_to_cpu(logr->reserved1[0]), + (int)le16_to_cpu(logr->reserved1[1]), + (int)le16_to_cpu(logr->reserved1[2])); + detaillogr(ctx, logr); + } + if (optt) { + const char *state; + + if (logr->record_type == const_cpu_to_le32(2)) + state = "--checkpoint--"; + else + state = commitment(le64_to_cpu(logr->this_lsn)); + printf(" at %04x %016llx %s (%ld) %s\n",k, + (long long)le64_to_cpu(logr->this_lsn), + state, + (long)(le64_to_cpu(logr->this_lsn) - synced_lsn), + actionname(le16_to_cpu(logr->redo_operation))); + if (logr->client_previous_lsn || logr->client_undo_next_lsn) { + if (logr->client_previous_lsn + == logr->client_undo_next_lsn) { + printf(" " + " previous and undo %016llx\n", + (long long)le64_to_cpu( + logr->client_previous_lsn)); + } else { + printf(" " + " previous %016llx", + (long long)le64_to_cpu( + logr->client_previous_lsn)); + + if (logr->client_undo_next_lsn) + printf(" undo %016llx\n", + (long long)le64_to_cpu( + logr->client_undo_next_lsn)); + else + printf("\n"); + } + } + } +} + +/* + * Mark transactions which should be redone + */ + +static void mark_transactions(struct ACTION_RECORD *lastaction) +{ + struct ACTION_RECORD *action; + const struct LOG_RECORD *logr; + le32 id; + int actives; + BOOL more; + BOOL committed; + + actives = 0; + do { + more = FALSE; + id = const_cpu_to_le32(0); + for (action=lastaction; action; action=action->prev) { + logr = &action->record; + if ((logr->redo_operation + == const_cpu_to_le16(ForgetTransaction)) + && !(action->flags & ACTION_TO_REDO) + && !id) { + id = logr->transaction_id; + action->flags |= ACTION_TO_REDO; + if (optv) + printf("Marking transaction 0x%x\n", + (int)le32_to_cpu(id)); + } + committed = ((s64)(le64_to_cpu(logr->this_lsn) + - committed_lsn)) <= 0; + if (!logr->transaction_id + && committed) + action->flags |= ACTION_TO_REDO; + if (id + && (logr->transaction_id == id) + && committed) { + action->flags |= ACTION_TO_REDO; + more = TRUE; + } + } + if (more) + actives++; + } while (more); + /* + * Show unmarked (aborted) actions + */ + if (optv) { + for (action=lastaction; action; action=action->prev) { + logr = &action->record; + if (logr->transaction_id + && !(action->flags & ACTION_TO_REDO)) + printf("** Action %d was aborted\n", + (int)action->num); + } + } + if (optv && (actives > 1)) + printf("%d active transactions in set\n",actives); +} + +/* + * Enqueue an action and play the queued actions on end of set + */ + +static TRISTATE enqueue_action(CONTEXT *ctx, const struct LOG_RECORD *logr, + int size, int num) +{ + struct ACTION_RECORD *action; + TRISTATE state; + int err; + + err = 1; + state = T_ERR; + /* enqueue record */ + action = (struct ACTION_RECORD*) + malloc(size + offsetof(struct ACTION_RECORD, record)); + if (action) { + memcpy(&action->record, logr, size); + action->num = num; + action->flags = 0; + /* enqueue ahead of list, firstaction is the oldest one */ + action->prev = (struct ACTION_RECORD*)NULL; + action->next = ctx->firstaction; + if (ctx->firstaction) + ctx->firstaction->prev = action; + else + ctx->lastaction = action; + ctx->firstaction = action; + err = 0; + state = T_OK; + if ((optp || optu) + && (logr->record_type == const_cpu_to_le32(2))) { + /* if chkp process queue, and increment count */ + playedactions++; + if (playedactions <= playcount) { + if (optv) + printf("* Refreshing attributes\n"); + err = refresh_attributes(ctx->firstaction); + if (optv) + printf("* Undoing transaction set %d" + " (actions %d->%d)\n", + (int)playedactions, + (int)ctx->lastaction->num, + (int)ctx->firstaction->num); + err = play_undos(ctx->vol, ctx->lastaction); + if (err) + printf("* Undoing transaction" + " set failed\n"); + } + if (!err && optp && (playedactions == playcount)) { + if (optv) + printf("* Redoing transaction set %d" + " (actions %d->%d)\n", + (int)playedactions, + (int)ctx->firstaction->num, + (int)ctx->lastaction->num); + mark_transactions(ctx->lastaction); + err = play_redos(ctx->vol, ctx->firstaction); + if (err) + printf("* Redoing transaction" + " set failed\n"); + } + if (err) + state = T_ERR; + else + if (playedactions == playcount) + state = T_DONE; + /* free queue */ + while (ctx->firstaction) { + action = ctx->firstaction->next; + free(ctx->firstaction); + ctx->firstaction = action; + } + ctx->lastaction = (struct ACTION_RECORD*)NULL; + } + if (opts + && ((s64)(le64_to_cpu(logr->this_lsn) - synced_lsn) <= 0)) { + if (optv) + printf("* Refreshing attributes\n"); +// should refresh backward ? + err = refresh_attributes(ctx->firstaction); + mark_transactions(ctx->lastaction); + if (!err) { + if (optv) + printf("* Syncing actions %d->%d\n", + (int)ctx->firstaction->num, + (int)ctx->lastaction->num); + err = play_redos(ctx->vol, ctx->firstaction); + } + if (err) { + printf("* Syncing actions failed\n"); + state = T_ERR; + } else + state = T_DONE; + } + } + return (state); +} + + +static void showheadrcrd(u32 blk, const struct RECORD_PAGE_HEADER *rph) +{ + s32 diff; + + if (optv) { + printf("magic %08lx\n", + (long)le32_to_cpu(rph->head.magic)); + printf("usa_ofs %04x\n", + (int)le16_to_cpu(rph->head.usa_ofs)); + printf("usa_count %04x\n", + (int)le16_to_cpu(rph->head.usa_count)); + if (blk < 4) + printf("file_offset %08lx\n", + (long)le32_to_cpu(rph->copy.file_offset)); + else { + diff = le64_to_cpu(rph->copy.last_lsn) - synced_lsn; + printf("last_lsn %016llx" + " (synced%s%ld)\n", + (long long)le64_to_cpu(rph->copy.last_lsn), + (diff < 0 ? "" : "+"),(long)diff); + } + printf("flags %08lx\n", + (long)le32_to_cpu(rph->flags)); + printf("page_count %d\n", + (int)le16_to_cpu(rph->page_count)); + printf("page_position %d\n", + (int)le16_to_cpu(rph->page_position)); + printf("next_record_offset %04x\n", + (int)le16_to_cpu(rph->next_record_offset)); + printf("reserved4 %04x %04x %04x\n", + (int)le16_to_cpu(rph->reserved4[0]), + (int)le16_to_cpu(rph->reserved4[1]), + (int)le16_to_cpu(rph->reserved4[2])); + diff = le64_to_cpu(rph->last_end_lsn) - synced_lsn; + printf("last_end_lsn %016llx (synced%s%ld)\n", + (long long)le64_to_cpu(rph->last_end_lsn), + (diff < 0 ? "" : "+"),(long)diff); + printf("usn %04x\n", + (int)getle16(rph,le16_to_cpu(rph->head.usa_ofs))); + printf("\n"); + } else { + if (optt) { + const char *state; + + state = commitment(le64_to_cpu(rph->copy.last_lsn)); + diff = le64_to_cpu(rph->copy.last_lsn) - synced_lsn; + printf(" last %016llx (synced%s%ld) %s\n", + (long long)le64_to_cpu(rph->copy.last_lsn), + (diff < 0 ? "" : "+"),(long)diff, state); + state = commitment(le64_to_cpu(rph->last_end_lsn)); + diff = le64_to_cpu(rph->last_end_lsn) - synced_lsn; + printf(" last_end %016llx (synced%s%ld) %s\n", + (long long)le64_to_cpu(rph->last_end_lsn), + (diff < 0 ? "" : "+"),(long)diff, state); + } + } +} + +/* + * Analyze and display an action overlapping log blocks + * + * Returns the position of first action in next block. If this is + * greater than a block size (for actions overlapping more than + * two blocks), then some blocks have to be skipped. + * + * Returns 0 in case of error + */ + +static u16 overlapshow(CONTEXT *ctx, u16 k, u32 blk, const struct BUFFER *buf, + const struct BUFFER *nextbuf) +{ + const struct LOG_RECORD *logr; + const char *data; + const char *nextdata; + char *fullrec; + u32 size; + u32 nextspace; + u32 space; + BOOL likely; + u16 blkheadsz; + + data = buf->block.data; + logr = (const struct LOG_RECORD*)&data[k]; + size = le32_to_cpu(logr->client_data_length) + LOG_RECORD_HEAD_SZ; + blkheadsz = buf->headsz; + if (nextbuf && (blk >= BASEBLKS)) { + nextdata = nextbuf->block.data; + space = blocksz - k; + nextspace = blocksz - blkheadsz; + if ((space >= LOG_RECORD_HEAD_SZ) + && (size > space)) { + fullrec = (char*)malloc(size); + if (size <= (space + nextspace)) { + /* Overlap on two blocks */ + memcpy(fullrec,&data[k],space); + memcpy(&fullrec[space], + nextdata + blkheadsz, + size - space); + likely = likelyop((struct LOG_RECORD*)fullrec); + actionnum++; + if (optv) { + printf("\nOverlapping record %u at 0x%x" + " size %d (next at 0x%x)\n", + (int)actionnum,(int)k, + (int)size, (int)(k + size)); + printf("Overlap marked for block %ld" + " space %d likely %d\n", + (long)blk,(int)space,likely); + } + if (likely) + showlogr(ctx, k, + (struct LOG_RECORD*)fullrec); + else + printf("** Skipping unlikely" + " overlapping record\n"); + k += size - blocksz + blkheadsz; + } else { + const struct BUFFER *midbuf; + int skip; + u32 next; + u32 pos; + int i; + + /* + * The maximum size of of log record is 131104 + * (when both offset and length are 65528 for + * redo or undo). + * So up to 33 log blocks (useful size 4032) + * could be needed. However never both undo and + * redo have been found big, and 17 should be + * the real maximum. + */ + if (optv) + printf("More than two blocks required" + " (size %lu)\n",(long)size); + memcpy(fullrec,&data[k],space); + + skip = (size - space - 1)/nextspace; + pos = space; + likely = TRUE; + for (i=1; (i<=skip) && likely; i++) { + midbuf = read_buffer(ctx, blk + i); + if (midbuf) { + memcpy(&fullrec[pos], + &midbuf->block + .data[blkheadsz], + nextspace); + pos += nextspace; + } else + likely = FALSE; + } + if (pos >= size) { + printf("** Error : bad big overlap" + " pos %d size %d\n", + (int)pos,(int)size); + likely = FALSE; + } + midbuf = read_buffer(ctx, blk + skip + 1); + if (midbuf) + memcpy(&fullrec[pos], + &midbuf->block.data[blkheadsz], + size - pos); + else + likely = FALSE; + if (!likelyop((struct LOG_RECORD*)fullrec)) + likely = FALSE; + actionnum++; + if (optv) { + printf("\nBig overlapping record %u at " + "0x%x size %u (next at 0x%x)\n", + (int)actionnum,(int)k,(int)size, + (int)(k + size)); + printf("Overlap marked for block %ld" + " space %d likely %d\n", + (long)blk,(int)space,likely); + } + if (likely) + showlogr(ctx, k, + (struct LOG_RECORD*)fullrec); + else + printf("** Skipping unlikely" + " overlapping record\n"); + /* next and skip are only for displaying */ + next = (size - space) % nextspace + + blkheadsz; + if ((blocksz - next) < LOG_RECORD_HEAD_SZ) + next = blkheadsz; + if (next == blkheadsz) + skip++; + if (optv) + printf("Next record expected in" + " block %lu index 0x%x\n", + (long)(blk + skip + 1),next); + /* Quick check, with no consequences */ + if (firstrecord(skip,buf,buf) != next) + printf("** Error next != firstrecord" + " after block %d\n",blk); + k += size - blocksz + blkheadsz; + } + if (!likely) + k = 0; + else + if (!k) + printf("* Bad return from overlap()\n"); + free(fullrec); + } else { + /* No conditions for overlap, usually a new session */ + printf("* No block found overlapping on block %d\n", + (int)blk); + k = 0; + } + } else { + /* blocks 2, 3 and the last one have no next block */ + k = 0; + } + return (k); +} + +/* + * Analyze and forward display the actions in a log block + * + * Returns the position of first action in next block. If this is + * greater than a block size, then some blocks have to be skipped. + * + * Returns 0 in case of error + */ + +static u16 forward_rcrd(CONTEXT *ctx, u32 blk, u16 pos, + const struct BUFFER *buf, const struct BUFFER *nextbuf) +{ + const struct RECORD_PAGE_HEADER *rph; + const struct LOG_RECORD *logr; + const char *data; + u16 k; + u16 endoff; + BOOL stop; + + rph = &buf->block.record; + if (rph && (rph->head.magic == magic_RCRD)) { + data = buf->block.data; + showheadrcrd(blk, rph); + k = buf->headsz; + if ((k < pos) && (pos < blocksz)) { + k = ((pos - 1) | 7) + 1; + } +// TODO check bad start > blocksz - 48 + logr = (const struct LOG_RECORD*)&data[k]; + stop = FALSE; + if (!likelyop(logr)) { + if (optv) + printf("* Bad start 0x%x for block %d\n", + (int)pos,(int)blk); + k = searchlikely(buf); + if ((k + sizeof(struct LOG_RECORD)) > blocksz) { + printf("No likely full record in block %lu\n", + (unsigned long)blk); + /* there can be a partial one */ + k = le16_to_cpu(rph->next_record_offset); + if ((k < (u16)sizeof(struct RECORD_PAGE_HEADER)) + || ((blocksz - k) < LOG_RECORD_HEAD_SZ)) + stop = TRUE; + } else { + if (optv) + printf("First record computed at" + " offset 0x%x\n", (int)k); + } + } + while (!stop) { + s32 size; + + logr = (const struct LOG_RECORD*)&data[k]; + size = le32_to_cpu(logr->client_data_length) + + LOG_RECORD_HEAD_SZ; + if ((size < MINRECSIZE) + || (size > MAXRECSIZE) + || (size & 7)) { + printf("** Bad record size %ld in block %ld" + " offset 0x%x\n", + (long)size, (long)buf->num, (int)k); + showlogr(ctx, k, logr); + k = 0; + stop = TRUE; + } else { + endoff = le16_to_cpu(rph->next_record_offset); + if (((u32)(k + size) <= blocksz) + && ((u32)(k + size) <= endoff)) { + actionnum++; + if (optv) { + printf("\n* log action %u at" + " 0x%x size %d (next" + " at 0x%x)\n", + actionnum,k,size, + k + size); + } + showlogr(ctx, k, logr); + if (!logr->client_data_length) { + printf("** Bad" + " client_data_length\n"); + stop = TRUE; + } + k += size; + if ((blocksz - k) + < LOG_RECORD_HEAD_SZ) { + k = nextbuf->headsz; + stop = TRUE; + } + } else { + k = overlapshow(ctx, k, blk, + buf, nextbuf); + stop = TRUE; + } + } + } + } else { + printf("** Not a RCRD record, MAGIC 0x%08lx\n", + (long)le32_to_cpu(rph->head.magic)); + k = 0; + } + return (k); +} + +/* + * Display a restart page + */ + +static void showrest(const struct RESTART_PAGE_HEADER *rest) +{ + const struct RESTART_AREA *resa; + const struct RESTART_CLIENT *rcli; + const char *data; + + data = (const char*)rest; + if ((rest->head.magic == magic_RSTR) + || (rest->head.magic == magic_CHKD)) { + if (optv) { + printf("magic %08lx\n", + (long)le32_to_cpu(rest->head.magic)); + printf("usa_ofs %04x\n", + (int)le16_to_cpu(rest->head.usa_ofs)); + printf("usa_count %04x\n", + (int)le16_to_cpu(rest->head.usa_count)); + printf("chkdsk_lsn %016llx\n", + (long long)le64_to_cpu(rest->chkdsk_lsn)); + printf("system_page_size %08lx\n", + (long)le32_to_cpu(rest->system_page_size)); + printf("log_page_size %08lx\n", + (long)le32_to_cpu(rest->log_page_size)); + printf("restart_offset %04x\n", + (int)le16_to_cpu(rest->restart_offset)); + printf("minor_vers %d\n", + (int)le16_to_cpu(rest->minor_ver)); + printf("major_vers %d\n", + (int)le16_to_cpu(rest->major_ver)); + printf("usn %04x\n", + (int)le16_to_cpu(rest->usn)); + printf("\n"); + } else { + if (optt) + printf(" chkdsk %016llx\n", + (long long)le64_to_cpu(rest->chkdsk_lsn)); + } + resa = (const struct RESTART_AREA*) + &data[le16_to_cpu(rest->restart_offset)]; + if (optv) { + printf("current_lsn %016llx\n", + (long long)le64_to_cpu(resa->current_lsn)); + printf("log_clients %04x\n", + (int)le16_to_cpu(resa->log_clients)); + printf("client_free_list %04x\n", + (int)le16_to_cpu(resa->client_free_list)); + printf("client_in_use_list %04x\n", + (int)le16_to_cpu(resa->client_in_use_list)); + printf("flags %04x\n", + (int)le16_to_cpu(resa->flags)); + printf("seq_number_bits %08lx\n", + (long)le32_to_cpu(resa->seq_number_bits)); + printf("restart_area_length %04x\n", + (int)le16_to_cpu(resa->restart_area_length)); + printf("client_array_offset %04x\n", + (int)le16_to_cpu(resa->client_array_offset)); + printf("file_size %016llx\n", + (long long)le64_to_cpu(resa->file_size)); + printf("last_lsn_data_len %08lx\n", + (long)le32_to_cpu(resa->last_lsn_data_length)); + printf("record_length %04x\n", + (int)le16_to_cpu(resa->record_length)); + printf("log_page_data_offs %04x\n", + (int)le16_to_cpu(resa->log_page_data_offset)); + printf("restart_log_open_count %08lx\n", + (long)le32_to_cpu(resa->restart_log_open_count)); + printf("\n"); + } else { + if (optt) + printf(" latest %016llx\n", + (long long)le64_to_cpu(resa->current_lsn)); + } + + rcli = (const struct RESTART_CLIENT*) + &data[le16_to_cpu(rest->restart_offset) + + le16_to_cpu(resa->client_array_offset)]; + if (optv) { + printf("oldest_lsn %016llx\n", + (long long)le64_to_cpu(rcli->oldest_lsn)); + printf("client_restart_lsn %016llx\n", + (long long)le64_to_cpu(rcli->client_restart_lsn)); + printf("prev_client %04x\n", + (int)le16_to_cpu(rcli->prev_client)); + printf("next_client %04x\n", + (int)le16_to_cpu(rcli->next_client)); + printf("seq_number %04x\n", + (int)le16_to_cpu(rcli->seq_number)); + printf("client_name_length %08x\n", + (int)le32_to_cpu(rcli->client_name_length)); + showname("client_name ", + (const char*)rcli->client_name, + le32_to_cpu(rcli->client_name_length) >> 1); + } else { + if (optt) { + printf(" synced %016llx\n", + (long long)le64_to_cpu( + rcli->oldest_lsn)); + printf(" committed %016llx\n", + (long long)le64_to_cpu( + rcli->client_restart_lsn)); + } + } + } else + printf("Not a RSTR or CHKD record, MAGIC 0x%08lx\n", + (long)le32_to_cpu(rest->head.magic)); +} + +static BOOL dorest(CONTEXT *ctx, unsigned long blk, + const struct RESTART_PAGE_HEADER *rph, BOOL initial) +{ + const struct RESTART_AREA *resa; + const struct RESTART_CLIENT *rcli; + const char *data; + s64 diff; + int offs; + int size; + BOOL change; + BOOL dirty; + + data = (const char*)rph; + offs = le16_to_cpu(rph->restart_offset); + resa = (const struct RESTART_AREA*)&data[offs]; + rcli = (const struct RESTART_CLIENT*)&data[offs + + le16_to_cpu(resa->client_array_offset)]; + if (initial) { + /* Information from block initially found best */ + latest_lsn = le64_to_cpu(resa->current_lsn); + committed_lsn = le64_to_cpu(rcli->client_restart_lsn); + synced_lsn = le64_to_cpu(rcli->oldest_lsn); + memcpy(&log_header, rph, + sizeof(struct RESTART_PAGE_HEADER)); + offs = le16_to_cpu(log_header.restart_offset); + memcpy(&restart, &data[offs], + sizeof(struct RESTART_AREA)); + offs += le16_to_cpu(restart.client_array_offset); + memcpy(&client, &data[offs], + sizeof(struct RESTART_CLIENT)); + dirty = !(resa->flags & RESTART_VOLUME_IS_CLEAN); + if (optv || optt) + printf("* Using initial restart page," + " syncing from 0x%llx, %s\n", + (long long)synced_lsn, + (dirty ? "dirty" : "clean")); + /* Get the block page size */ + blocksz = le32_to_cpu(rph->log_page_size); + if (optv) + printf("* Block size %ld bytes\n", (long)blocksz); + blockbits = 1; + while ((u32)(1 << blockbits) < blocksz) + blockbits++; + } else { + size = offs + le16_to_cpu(resa->restart_area_length); + if (optv) { + if (optv >= 2) + hexdump(data,size); + printf("* RSTR in block %ld 0x%lx (addr 0x%llx)\n", + (long)blk,(long)blk, + (long long)loclogblk(ctx, blk)); + } else { + if (optt) + printf("restart %ld\n",(long)blk); + } + showrest(rph); + /* Information from an older restart block if requested */ + dirty = !(restart.flags & RESTART_VOLUME_IS_CLEAN); + diff = le64_to_cpu(rcli->client_restart_lsn) - committed_lsn; + if (ctx->vol) { + change = (opts > 1) && (diff < 0); + } else { + change = (opts > 1 ? diff < 0 : diff > 0); + } + if (change) { + committed_lsn = le64_to_cpu(rcli->client_restart_lsn); + synced_lsn = le64_to_cpu(rcli->oldest_lsn); + latest_lsn = le64_to_cpu(resa->current_lsn); + memcpy(&log_header, rph, + sizeof(struct RESTART_PAGE_HEADER)); + offs = le16_to_cpu(log_header.restart_offset); + memcpy(&restart, &data[offs], + sizeof(struct RESTART_AREA)); + offs += le16_to_cpu(restart.client_array_offset); + memcpy(&client, &data[offs], + sizeof(struct RESTART_CLIENT)); + dirty = !(resa->flags & RESTART_VOLUME_IS_CLEAN); + if (optv || optt) + printf("* Using %s restart page," + " syncing from 0x%llx, %s\n", + (diff < 0 ? "older" : "newer"), + (long long)synced_lsn, + (dirty ? "dirty" : "clean")); + } + } + restart_lsn = synced_lsn; + return (dirty); +} + +/* + * Read and process the first restart block + * + * In full mode, both restart page are silently analyzed by the + * library and the most recent readable one is used to define the + * sync parameters. + * + * Returns the first restart buffer + * or NULL if the restart block is not valid + */ + + +static const struct BUFFER *read_restart(CONTEXT *ctx) +{ + const struct BUFFER *buf; + BOOL bad; + + bad = FALSE; + if (ctx->vol) { + struct RESTART_PAGE_HEADER *rph; + + rph = (struct RESTART_PAGE_HEADER*)NULL; + /* Full mode : use the restart page selected by the library */ + if (ntfs_check_logfile(log_na, &rph)) { + /* rph is left unchanged for a wiped out log file */ + if (rph) { + dorest(ctx, 0, rph, TRUE); + free(rph); + buf = read_buffer(ctx,0); + } else { + buf = (const struct BUFFER*)NULL; + printf("** The log file has been wiped out\n"); + } + } else { + buf = (const struct BUFFER*)NULL; + printf("** Could not get any restart page\n"); + } + } else { + /* Reduced mode : rely on first restart page */ + blockbits = BLOCKBITS; /* Until the correct value is read */ + blocksz = 1L << blockbits; + buf = read_buffer(ctx,0); + } + if (buf) { + NTFS_RECORD_TYPES magic; + + magic = buf->block.restart.head.magic; + switch (magic) { + case magic_RSTR : + break; + case magic_CHKD : + printf("** The log file has been obsoleted by chkdsk\n"); + bad = TRUE; + break; + case magic_empty : + printf("** The log file has been wiped out\n"); + bad = TRUE; + break; + default : + printf("** Invalid restart block\n"); + bad = TRUE; + break; + } + if (!bad && !ctx->vol) + dorest(ctx, 0, &buf->block.restart, TRUE); + if ((buf->block.restart.major_ver != const_cpu_to_le16(1)) + || (buf->block.restart.minor_ver != const_cpu_to_le16(1))) { + printf("** Unsupported $LogFile version %d.%d\n", + le16_to_cpu(buf->block.restart.major_ver), + le16_to_cpu(buf->block.restart.minor_ver)); + bad = TRUE; + } + if (bad) { + buf = (const struct BUFFER*)NULL; + } + } + return (buf); +} + +/* + * Mark the logfile as synced + */ + +static int reset_logfile(CONTEXT *ctx __attribute__((unused))) +{ + char *buffer; + int off; + int err; + + err = 1; + buffer = (char*)malloc(blocksz); + if (buffer) { + memset(buffer, 0, blocksz); + restart.client_in_use_list = LOGFILE_NO_CLIENT; + restart.flags |= RESTART_VOLUME_IS_CLEAN; + client.oldest_lsn = cpu_to_le64(restart_lsn); + memcpy(buffer, &log_header, + sizeof(struct RESTART_PAGE_HEADER)); + off = le16_to_cpu(log_header.restart_offset); + memcpy(&buffer[off], &restart, + sizeof(struct RESTART_AREA)); + off += le16_to_cpu(restart.client_array_offset); + memcpy(&buffer[off], &client, + sizeof(struct RESTART_CLIENT)); + if (!ntfs_mst_pre_write_fixup((NTFS_RECORD*)buffer, blocksz) + && (ntfs_attr_pwrite(log_na, 0, + blocksz, buffer) == blocksz) + && (ntfs_attr_pwrite(log_na, (u64)1 << blockbits, + blocksz, buffer) == blocksz)) + err = 0; + } + return (err); +} + +/* + * Determine the most recent valid record block + */ + +static const struct BUFFER *best_start(const struct BUFFER *buf, + const struct BUFFER *altbuf) +{ + const struct BUFFER *best; + const struct RECORD_PAGE_HEADER *head; + const struct RECORD_PAGE_HEADER *althead; + s64 diff; + + if (!buf || !altbuf) + best = (buf ? buf : altbuf); + else { + head = &buf->block.record; + althead = &altbuf->block.record; + /* determine most recent, caring for wraparounds */ + diff = le64_to_cpu(althead->last_end_lsn) + - le64_to_cpu(head->last_end_lsn); + if (diff > 0) + best = altbuf; + else + best = buf; + } + if (best && (best->block.record.head.magic != magic_RCRD)) + best = (const struct BUFFER*)NULL; + return (best); +} + +/* + * Interpret the boot data + * + * Probably not needed any more, use ctx->vol + */ + +static BOOL getboot(const char *buf) +{ + u64 sectors; + u64 clusters; + u16 sectpercluster; + BOOL ok; + + ok = TRUE; + /* Beware : bad alignment */ + bytespersect = (buf[11] & 255) + ((buf[12] & 255) << 8); + sectpercluster = buf[13] & 255; + clustersz = bytespersect * (u32)sectpercluster; + clusterbits = 1; + while ((u32)(1 << clusterbits) < clustersz) + clusterbits++; + sectors = getle64(buf, 0x28); + clusters = sectors/sectpercluster; + mftlcn = getle64(buf, 0x30); + if (buf[0x40] & 0x80) + mftrecsz = 1 << (16 - (buf[0x40] & 15)); + else + mftrecsz = (buf[0x40] & 127)*clustersz; + mftrecbits = 1; + while ((u32)(1 << mftrecbits) < mftrecsz) + mftrecbits++; + if (optv) { + if ((long long)sectors*bytespersect > 10000000000LL) + printf("Capacity %lld bytes (%lld GB)\n", + (long long)sectors*bytespersect, + (long long)sectors*bytespersect/1000000000); + else + printf("Capacity %lld bytes (%lld MB)\n", + (long long)sectors*bytespersect, + (long long)sectors*bytespersect/1000000); + printf("sectors %lld (0x%llx), sector size %d\n", + (long long)sectors,(long long)sectors, + (int)bytespersect); + printf("clusters %lld (0x%llx), cluster size %d (%d bits)\n", + (long long)clusters,(long long)clusters, + (int)clustersz,(int)clusterbits); + printf("MFT at cluster %lld (0x%llx), entry size %lu\n", + (long long)mftlcn,(long long)mftlcn, + (unsigned long)mftrecsz); + if (mftrecsz > clustersz) + printf("%ld clusters per MFT entry\n", + (long)(mftrecsz/clustersz)); + else + printf("%ld MFT entries per cluster\n", + (long)(clustersz/mftrecsz)); + } + return (ok); +} + +static int locatelogfile(CONTEXT *ctx) +{ + int err; + + err = 1; + log_ni = ntfs_inode_open(ctx->vol, FILE_LogFile); + if (log_ni) { + log_na = ntfs_attr_open(log_ni, AT_DATA, AT_UNNAMED, 0); + if (log_na) { + logfilesz = log_na->data_size; + err = 0; + } + } + return (err); +} + +/* + * Analyze a $LogFile copy + * + * A $LogFile cannot be played. It can be however be analyzed in + * stand-alone mode. + * The location of the $MFT will have to be determined elsewhere. + */ + +static BOOL getlogfiledata(CONTEXT *ctx, const char *boot) +{ + const struct RESTART_PAGE_HEADER *rph; + const struct RESTART_AREA *rest; + BOOL ok; + u32 off; + s64 size; + + ok = FALSE; + fseek(ctx->file,0L,2); + size = ftell(ctx->file); + rph = (const struct RESTART_PAGE_HEADER*)boot; + off = le16_to_cpu(rph->restart_offset); + rest = (const struct RESTART_AREA*)&boot[off]; + + /* estimate cluster size from log file size (unreliable) */ + switch (le32_to_cpu(rest->seq_number_bits)) { + case 45 : clustersz = 512; break; + case 43 : clustersz = 1024; break; /* can be 1024 or 2048 */ + case 40 : + default : clustersz = 4096; break; + } + + clusterbits = 1; + while ((u32)(1 << clusterbits) < clustersz) + clusterbits++; + printf("* Assuming cluster size %ld\n",(long)clustersz); + logfilelcn = 0; + logfilesz = size; + if (optv) + printf("Log file size %lld bytes, cluster size %ld\n", + (long long)size, (long)clustersz); + /* Have to wait an InitializeFileRecordSegment to get these values */ + mftrecsz = 0; + mftrecbits = 0; + ok = TRUE; + return (ok); +} + +/* + * Get basic volume data + * + * Locate the MFT and Logfile + * Not supposed to read the first log block... + */ + +static BOOL getvolumedata(CONTEXT *ctx, char *boot) +{ + const struct RESTART_AREA *rest; + BOOL ok; + + ok = FALSE; + rest = (const struct RESTART_AREA*)NULL; + if (ctx->vol) { + getboot(boot); + mftlcn = ctx->vol->mft_lcn; + mftcnt = ctx->vol->mft_na->data_size/mftrecsz; + if (!locatelogfile(ctx)) + ok = TRUE; + else { + fprintf(stderr,"** Could not read the log file\n"); + } + } else { + if (ctx->file + && (!memcmp(boot,"RSTR",4) || !memcmp(boot,"CHKD",4))) { + printf("* Assuming a log file copy\n"); + getlogfiledata(ctx, boot); + ok = TRUE; + } else + fprintf(stderr,"** Not an NTFS image or log file\n"); + } +// TODO get rest ?, meaningful ? + if (ok && rest) { + if (rest->client_in_use_list + || !(rest->flags & const_cpu_to_le16(2))) + printf("Volume was not unmounted safely\n"); + else + printf("Volume was unmounted safely\n"); + if (le16_to_cpu(rest->client_in_use_list) > 1) + printf("** multiple clients not implemented\n"); + } + return (ok); +} + +/* + * Open the volume (or the log file) and gets its parameters + * + * Returns TRUE if successful + */ + +static BOOL open_volume(CONTEXT *ctx, const char *device_name) +{ + union { + char buf[1024]; + /* alignment may be needed in getboot() */ + long long force_align; + } boot; + BOOL ok; + int got; + + ok =FALSE; + /* + * First check the boot sector, to avoid library errors + * when trying to mount a log file. + * If the device cannot be fopened or fread, then it is + * unlikely to be a file. + */ + ctx->vol = (ntfs_volume*)NULL; + ctx->file = fopen(device_name, "rb"); + if (ctx->file) { + got = fread(boot.buf,1,1024,ctx->file); + if ((got == 1024) + && (!memcmp(boot.buf, "RSTR", 4) + || !memcmp(boot.buf, "CHKD", 4))) { + /* This appears to be a log file */ + ctx->vol = (ntfs_volume*)NULL; + ok = getvolumedata(ctx, boot.buf); + } + if (!ok) + fclose(ctx->file); + } + if (!ok) { + /* Not a log file, assume an ntfs device, mount it */ + ctx->file = (FILE*)NULL; + ctx->vol = ntfs_mount(device_name, + ((optp || optu || opts) && !optn + ? NTFS_MNT_FORENSIC : NTFS_MNT_RDONLY)); + if (ctx->vol) { + ok = getvolumedata(ctx, boot.buf); + if (!ok) + ntfs_umount(ctx->vol, TRUE); + } + } + return (ok); +} + +static u16 dorcrd(CONTEXT *ctx, u32 blk, u16 pos, const struct BUFFER *buf, + const struct BUFFER *nextbuf) +{ + if (optv) { + if (optv >= 2) + hexdump(buf->block.data,blocksz); + printf("* RCRD in block %ld 0x%lx (addr 0x%llx)" + " from pos 0x%x\n", + (long)blk,(long)blk, + (long long)loclogblk(ctx, blk),(int)pos); + } else { + if (optt) + printf("block %ld\n",(long)blk); + } + return (forward_rcrd(ctx, blk, pos, buf, nextbuf)); +} + +/* + * Concatenate and process a record overlapping on several blocks + */ + +static TRISTATE backoverlap(CONTEXT *ctx, int blk, + const char *data, const char *nextdata, int k) +{ + const struct LOG_RECORD *logr; + char *fullrec; + s32 size; + int space; + int nextspace; + TRISTATE state; + u16 blkheadsz; + + logr = (const struct LOG_RECORD*)&data[k]; + state = T_ERR; + size = le32_to_cpu(logr->client_data_length) + LOG_RECORD_HEAD_SZ; + space = blocksz - k; + blkheadsz = sizeof(struct RECORD_PAGE_HEADER) + + ((2*getle16(data,6) - 1) | 7) + 1; + nextspace = blocksz - blkheadsz; + if ((space >= LOG_RECORD_HEAD_SZ) + && (size > space) + && (size < MAXRECSIZE)) { + fullrec = (char*)malloc(size); + memcpy(fullrec,&data[k],space); + if (size <= (space + nextspace)) + memcpy(&fullrec[space], nextdata + blkheadsz, + size - space); + else { + const struct BUFFER *morebuf; + const char *moredata; + int total; + int more; + unsigned int mblk; + + if (optv) + printf("* big record, size %d\n",size); + total = space; + mblk = blk + 1; + while (total < size) { + if (mblk >= (logfilesz >> blockbits)) + mblk = BASEBLKS; + more = size - total; + if (more > nextspace) + more = nextspace; + morebuf = read_buffer(ctx, mblk); + if (morebuf) { + moredata = morebuf->block.data; + memcpy(&fullrec[total], + moredata + blkheadsz, more); + } + total += more; + mblk++; + } + } + + state = (likelyop((struct LOG_RECORD*)fullrec) ? T_OK : T_ERR); + actionnum++; + if (optv) { + printf("\nOverlapping backward action %d at 0x%x" + " size %d (next at 0x%x)\n", + (int)actionnum,(int)k, + (int)size,(int)(k + size)); + printf("Overlap marked for block %ld space %d" + " likely %d\n", + (long)blk,(int)space,(state == T_OK)); + } + if (state == T_OK) { + showlogr(ctx, k, (struct LOG_RECORD*)fullrec); + if (optp || optu || opts) + state = enqueue_action(ctx, + (struct LOG_RECORD*)fullrec, + size, actionnum); + } else { + /* Try to go on unless playing actions */ + if (optb && (state == T_ERR)) + state = T_OK; + } + free(fullrec); + } else { + /* Error conditions */ + if ((size < MINRECSIZE) || (size > MAXRECSIZE)) { + printf("** Invalid record size %ld" + " in block %ld\n", + (long)size,(long)blk); + } else + printf("** Inconsistency : the final" + " record in block %ld" + " does not overlap\n", + (long)blk); + /* Do not abort, unless playing actions */ + state = (optb ? T_OK : T_ERR); + } + return (state); +} + +static TRISTATE backward_rcrd(CONTEXT *ctx, u32 blk, int skipped, + const struct BUFFER *buf, const struct BUFFER *prevbuf, + const struct BUFFER *nextbuf) +{ + u16 poslist[75]; /* 4096/sizeof(struct LOG_RECORD) */ + const struct RECORD_PAGE_HEADER *rph; + const struct RECORD_PAGE_HEADER *prevrph; + const struct LOG_RECORD *logr; + const char *data; + const char *nextdata; + BOOL stop; + TRISTATE state; + s32 size; + int cnt; + u16 k; + u16 endoff; + int j; + + state = T_ERR; + rph = &buf->block.record; + prevrph = (struct RECORD_PAGE_HEADER*)NULL; + if (prevbuf) + prevrph = &prevbuf->block.record; + data = buf->block.data; + nextdata = nextbuf->block.data; + if (rph && (rph->head.magic == magic_RCRD) + && (!prevrph || (prevrph->head.magic == magic_RCRD))) { + if (optv) { + if (optv >= 2) + hexdump(data,blocksz); + printf("* RCRD in block %ld 0x%lx (addr 0x%llx)\n", + (long)blk,(long)blk, + (long long)loclogblk(ctx, blk)); + } else { + if (optt) + printf("block %ld\n",(long)blk); + } + showheadrcrd(blk, rph); + if (!prevbuf) + k = buf->headsz; + else + k = firstrecord(skipped, buf, prevbuf); + logr = (const struct LOG_RECORD*)&data[k]; + cnt = 0; + /* check whether there is at least one beginning of record */ + endoff = le16_to_cpu(rph->next_record_offset); + if (k && ((k < endoff) || !endoff)) { + logr = (const struct LOG_RECORD*)&data[k]; + if (likelyop(logr)) { + stop = FALSE; + state = T_OK; + if (optv) + printf("First record checked" + " at offset 0x%x\n", (int)k); + } else { + printf("** Bad first record at offset 0x%x\n", + (int)k); + if (optv) + showlogr(ctx, k,logr); + k = searchlikely(buf); + stop = !k; + if (stop) { + printf("** Could not recover," + " stopping at block %d\n", + (int)blk); + state = T_ERR; + } else { + /* Try to go on, unless running */ + if (optb) + state = T_OK; + } + } + while (!stop) { + logr = (const struct LOG_RECORD*)&data[k]; + size = le32_to_cpu(logr->client_data_length) + + LOG_RECORD_HEAD_SZ; + if ((size < MINRECSIZE) + || (size > MAXRECSIZE) + || (size & 7)) { + printf("** Bad size %ld in block %ld" + " offset 0x%x, stopping\n", + (long)size,(long)blk,(int)k); + stop = TRUE; + } else { + if (((u32)(k + size) <= blocksz) + && ((u32)(k + size) <= endoff)) { + poslist[cnt++] = k; + if (!logr->client_data_length) + stop = TRUE; + k += size; + if ((u32)(k + + LOG_RECORD_HEAD_SZ) + > blocksz) + stop = TRUE; + } else { + stop = TRUE; + } + } + } + } else { + stop = TRUE; + state = (k ? T_OK : T_ERR); + } + /* Now examine an overlapping record */ + if (k + && ((k == endoff) || !endoff) + && ((u32)(k + LOG_RECORD_HEAD_SZ) <= blocksz)) { + if (nextbuf && (blk >= BASEBLKS)) { + state = backoverlap(ctx, blk, + data, nextdata, k); + } + } + for (j=cnt-1; (j>=0) && (state==T_OK); j--) { + k = poslist[j]; + logr = (const struct LOG_RECORD*)&data[k]; + size = le32_to_cpu(logr->client_data_length) + + LOG_RECORD_HEAD_SZ; + actionnum++; + if (optv && (!optc || within_lcn_range(logr))) { + printf("\n* log backward action %u at 0x%x" + " size %d (next at 0x%x)\n", + actionnum, k, size, k + size); + } + if ((optv | optt) + && (!nextbuf && (j == (cnt - 1)))) { + printf("* This is the latest record\n"); + if (logr->this_lsn == restart.current_lsn) + printf(" its lsn matches the global" + " restart lsn\n"); + if (logr->this_lsn == client.client_restart_lsn) + printf(" its lsn matches the client" + " restart lsn\n"); + if (logr->client_data_length + == restart.last_lsn_data_length) + printf(" its length matches the" + " last record length\n"); + } + showlogr(ctx, k, logr); + if (optp || optu || opts) + state = enqueue_action(ctx, logr, size, actionnum); + } + } + return (state); +} + +static int walkback(CONTEXT *ctx, const struct BUFFER *buf, u32 blk, + const struct BUFFER *prevbuf, u32 prevblk) +{ + const struct BUFFER *nextbuf; + NTFS_RECORD_TYPES magic; + u32 stopblk; + TRISTATE state; + + if (optv) + printf("\n* block %d at 0x%llx\n",(int)blk, + (long long)loclogblk(ctx, blk)); + ctx->firstaction = (struct ACTION_RECORD*)NULL; + ctx->lastaction = (struct ACTION_RECORD*)NULL; + nextbuf = (const struct BUFFER*)NULL; + stopblk = prevblk + 2; // wraparound ! + state = backward_rcrd(ctx, blk, 0, buf, + prevbuf, (struct BUFFER*)NULL); + while ((state == T_OK) + && !((blk > stopblk) && (prevblk <= stopblk)) + && (!(optp || optu) || (playedactions < playcount))) { + int skipped; + + nextbuf = buf; + buf = prevbuf; + blk = prevblk; + skipped = 0; + prevbuf = findprevious(ctx, buf); + if (prevbuf) { + prevblk = prevbuf->num; + if (prevblk < blk) + skipped = blk - prevblk - 1; + else + skipped = blk - prevblk - 1 + + (logfilesz >> blockbits) - BASEBLKS; + magic = prevbuf->block.record.head.magic; + switch (magic) { + case magic_RCRD : + break; + case magic_CHKD : + printf("** Unexpected block type CHKD\n"); + break; + case magic_RSTR : + printf("** Unexpected block type RSTR\n"); + break; + default : + printf("** Invalid block %d\n",(int)prevblk); + break; + } + if (optv) { + if (skipped) + printf("\n* block %ld at 0x%llx (block" + " %ld used as previous one)\n", + (long)blk, + (long long)loclogblk(ctx, blk), + (long)prevblk); + else + printf("\n* block %ld at 0x%llx\n", + (long)blk, + (long long)loclogblk(ctx, blk)); + } + state = backward_rcrd(ctx, blk, skipped, + buf, prevbuf, nextbuf); + } else { + fprintf(stderr,"** Could not read block %lu\n", + (long)prevblk); + state = T_ERR; + } + } + if ((blk > stopblk) && (prevblk <= stopblk)) + printf("* Earliest block reached\n"); + if ((optp || optu) && (playedactions >= playcount)) + printf("* Transaction set count reached\n"); + if (opts) + printf("* %s %s after playing %u actions\n", + (optn ? "Sync simulation" : "Syncing"), + (state == T_ERR ? "failed" : "successful"), + redocount); + /* free queue */ + while (ctx->firstaction) { + struct ACTION_RECORD *action; + + action = ctx->firstaction->next; + free(ctx->firstaction); + ctx->firstaction = action; + } + ctx->lastaction = (struct ACTION_RECORD*)NULL; + return (state == T_ERR ? 1 : 0); +} + +static int walk(CONTEXT *ctx) +{ + const struct BUFFER *buf; + const struct BUFFER *nextbuf; + const struct BUFFER *prevbuf; + const struct BUFFER *startbuf; + const NTFS_RECORD *record; + const struct RECORD_PAGE_HEADER *rph; + NTFS_RECORD_TYPES magic; + u32 blk; + u32 nextblk; + u32 prevblk; + int err; + u16 blkheadsz; + u16 pos; + BOOL dirty; + BOOL done; + + buf = (struct BUFFER*)NULL; + nextbuf = (struct BUFFER*)NULL; + if (optb || optp || optu || opts) { + prevbuf = (struct BUFFER*)NULL; + } + done = FALSE; + dirty = TRUE; + err = 0; + blk = 0; + pos = 0; + /* read and process the first restart block */ + buf = read_restart(ctx); + if (buf) { + if (optv) + printf("\n* block %d at 0x%llx\n",(int)blk, + (long long)loclogblk(ctx, blk)); + } else { + done = TRUE; + err = 1; + } + + nextblk = blk + 1; + while (!done) { + /* next block is needed to process the current one */ + if ((nextblk >= (logfilesz >> blockbits)) && (optr || optf)) + nextbuf = read_buffer(ctx, BASEBLKS); + else + nextbuf = read_buffer(ctx,nextblk); + if (nextbuf) { + record = (const NTFS_RECORD*)&nextbuf->block.data; + blkheadsz = nextbuf->headsz; + magic = record->magic; + switch (magic) { + case magic_CHKD : + case magic_RSTR : + case magic_RCRD : + break; + default : + printf("** Invalid block\n"); + err = 1; + break; + } + magic = buf->block.record.head.magic; + switch (magic) { + case magic_CHKD : + case magic_RSTR : + dirty = dorest(ctx, blk, &buf->block.restart, + FALSE); + break; + case magic_RCRD : + if (blk < BASEBLKS) + pos = buf->headsz; + pos = dorcrd(ctx, blk, pos, buf, nextbuf); + while (pos >= blocksz) { + if (optv > 1) + printf("Skipping block %d" + " pos 0x%x\n", + (int)nextblk,(int)pos); + pos -= (blocksz - blkheadsz); + nextblk++; + } + if ((blocksz - pos) < LOG_RECORD_HEAD_SZ) { + pos = 0; + nextblk++; + } + if (nextblk != (blk + 1)) { + nextbuf = read_buffer(ctx,nextblk); + } + break; + default : + if (!~magic) { + if (optv) + printf(" empty block\n"); + } + break; + } + } else { + fprintf(stderr,"* Could not read block %d\n",nextblk); + if (ctx->vol) { + /* In full mode, ignore errors on restart blocks */ + if (blk >= RSTBLKS) { + done = TRUE; + err = 1; + } + } else { + done = TRUE; + err = 1; + } + } + blk = nextblk; + nextblk++; + if (optr) { /* Only selected range */ + if ((nextblk == BASEBLKS) && (nextblk < firstblk)) + nextblk = firstblk; + if ((blk >= BASEBLKS) && (blk > lastblk)) + done = TRUE; + } else + if (optf) { /* Full log, forward */ + if (blk*blocksz >= logfilesz) + done = TRUE; + } else + if (optb || optp || optu || opts) { + /* Restart blocks only (2 blocks) */ + if (blk >= RSTBLKS) + done = TRUE; + } else { /* Base blocks only (4 blocks) */ + if (blk >= BASEBLKS) + done = TRUE; + } + if (!done) { + buf = nextbuf; + if (optv) + printf("\n* block %d at 0x%llx\n",(int)blk, + (long long)loclogblk(ctx, blk)); + } + } + if (optv && opts && !dirty) + printf("* Volume is clean, nothing to do\n"); + if (optb || optp || optu + || (opts && dirty)) { + playedactions = 0; + ctx->firstaction = (struct ACTION_RECORD*)NULL; + ctx->lastaction = (struct ACTION_RECORD*)NULL; + buf = nextbuf; + nextbuf = read_buffer(ctx, blk+1); + startbuf = best_start(buf,nextbuf); + if (startbuf) { + if (startbuf == nextbuf) { + /* nextbuf is better, show blk */ + if (optv && buf) { + printf("* Ignored block %d at 0x%llx\n", + (int)blk, + (long long)loclogblk(ctx, blk)); + if (optv >= 2) + hexdump(buf->block.data, + blocksz); + showheadrcrd(blk, &buf->block.record); + } + blk++; + buf = nextbuf; + } else { + /* buf is better, show blk + 1 */ + if (optv && nextbuf) { + printf("* Ignored block %d at 0x%llx\n", + (int)(blk + 1), + (long long)loclogblk(ctx, + blk + 1)); + if (optv >= 2) + hexdump(nextbuf->block.data, + blocksz); + showheadrcrd(blk + 1, + &nextbuf->block.record); + } + } + /* The latest buf may be more recent than restart */ + rph = &buf->block.record; + if ((s64)(le64_to_cpu(rph->last_end_lsn) + - committed_lsn) > 0) { + committed_lsn = le64_to_cpu(rph->last_end_lsn); + if (optv) + printf("* Restart page was obsolete\n"); + } + nextbuf = (const struct BUFFER*)NULL; + prevbuf = findprevious(ctx, buf); + if (prevbuf) { + prevblk = prevbuf->num; + magic = prevbuf->block.record.head.magic; + switch (magic) { + case magic_RCRD : + break; + case magic_CHKD : + printf("** Unexpected block type CHKD\n"); + err = 1; + break; + case magic_RSTR : + err = 1; + printf("** Unexpected block type RSTR\n"); + break; + default : + err = 1; + printf("** Invalid block\n"); + break; + } + } else + prevblk = BASEBLKS; + if (!err) + err = walkback(ctx, buf, blk, + prevbuf, prevblk); + } else { + fprintf(stderr,"** No valid start block, aborting\n"); + err = 1; + } + } + return (err); +} + +BOOL exception(int num) +{ + int i; + + i = 0; + while ((i < 10) && optx[i] && (optx[i] != num)) + i++; + return (optx[i] == num); +} + +static void version(void) +{ + printf("\n%s v%s (libntfs-3g) - Recover updates committed by Windows" + " on an NTFS Volume.\n\n", "ntfsrecover", VERSION); + printf("Copyright (c) 2012-2015 Jean-Pierre Andre\n"); + printf("\n%s\n%s%s\n", ntfs_gpl, ntfs_bugs, ntfs_home); +} + +static void usage(void) +{ + fprintf(stderr,"Usage : for recovering the updates committed by Windows :\n"); + fprintf(stderr," ntfsrecover partition\n"); + fprintf(stderr," (e.g. ntfsrecover /dev/sda1)\n"); + fprintf(stderr,"Advanced : ntfsrecover [-b] [-c first-last] [-i] [-f] [-n] [-p count]\n"); + fprintf(stderr," [-r first-last] [-t] [-u count] [-v] partition\n"); + fprintf(stderr," -b : show the full log backward\n"); + fprintf(stderr," -c : restrict to the actions related to cluster range\n"); + fprintf(stderr," -i : show invalid (stale) records\n"); + fprintf(stderr," -f : show the full log forward\n"); + fprintf(stderr," -h : show this help information\n"); + fprintf(stderr," -n : do not apply any modification\n"); + fprintf(stderr," -p : undo the latest count transaction sets and play one\n"); + fprintf(stderr," -r : show a range of log blocks forward\n"); + fprintf(stderr," -s : sync the committed changes (default)\n"); + fprintf(stderr," -t : show transactions\n"); + fprintf(stderr," -u : undo the latest count transaction sets\n"); + fprintf(stderr," -v : show more information (-vv yet more)\n"); + fprintf(stderr," -V : show version and exit\n"); + fprintf(stderr," Copyright (c) 2012-2015 Jean-Pierre Andre\n"); +} + +/* + * Process command options + */ + +static BOOL getoptions(int argc, char *argv[]) +{ + int c; + int xcount; + u32 xval; + char *endptr; + BOOL err; + static const char *sopt = "-bc:hifnp:r:stu:vVx:"; + static const struct option lopt[] = { + { "backward", no_argument, NULL, 'b' }, + { "clusters", required_argument, NULL, 'c' }, + { "forward", no_argument, NULL, 'f' }, + { "help", no_argument, NULL, 'h' }, + { "no-action", no_argument, NULL, 'n' }, + { "play", required_argument, NULL, 'p' }, + { "range", required_argument, NULL, 'r' }, + { "sync", no_argument, NULL, 's' }, + { "transactions", no_argument, NULL, 't' }, + { "undo", required_argument, NULL, 'u' }, + { "verbose", no_argument, NULL, 'v' }, + { "version", no_argument, NULL, 'V' }, + { "exceptions", required_argument, NULL, 'x' }, + { NULL, 0, NULL, 0 } + }; + + err = FALSE; + optb = FALSE; + optc = FALSE; + optd = FALSE; + optf = FALSE; + opth = FALSE; + opti = FALSE; + optn = FALSE; + optp = FALSE; + optr = FALSE; + opts = 0; + optt = FALSE; + optu = FALSE; + optv = 0; + optV = FALSE; + optx[0] = 0; + + while ((c = getopt_long(argc, argv, sopt, lopt, NULL)) != -1) { + switch (c) { + case 1: /* A non-option argument */ + if (optind == argc) + optd = TRUE; + else { + fprintf(stderr, "Device must be the" + " last argument.\n"); + err = TRUE; + } + break; + case 'b': + optb = TRUE; + break; + case 'c': + firstlcn = strtoull(optarg, &endptr, 0); + lastlcn = firstlcn; + if (*endptr == '-') + lastlcn = strtoull(++endptr, &endptr, 0); + if (*endptr || (lastlcn < firstlcn)) { + fprintf(stderr,"Bad cluster range\n"); + err = TRUE; + } else + optc = TRUE; + break; + case 'f': + optf = TRUE; + break; + case '?': + case 'h': + opth = TRUE; + break; + case 'n': + optn = TRUE; + break; + case 'p': + playcount = strtoull(optarg, &endptr, 0); + if (*endptr) { + fprintf(stderr,"Bad play count\n"); + err = TRUE; + } else + optp = TRUE; + break; + case 'r' : + firstblk = strtoull(optarg, &endptr, 0); + lastblk = firstblk; + if (*endptr == '-') + lastblk = strtoull(++endptr, &endptr, 0); + if (*endptr || (lastblk < firstblk)) { + fprintf(stderr,"Bad log block range\n"); + err = TRUE; + } else + optr = TRUE; + break; + case 's': + opts++; + break; + case 't': + optt = TRUE; + break; + case 'u': + playcount = strtoull(optarg, &endptr, 0); + if (*endptr) { + fprintf(stderr,"Bad undo count\n"); + err = TRUE; + } else + optu = TRUE; + break; + case 'v': + optv++; + break; + case 'V': + optV = TRUE; + break; + case 'x': + /* + * Undocumented : actions to execute, though + * they should be skipped under normal rules. + */ + xcount = 0; + xval = strtoull(optarg, &endptr, 0); + while ((*endptr == ',') + && (xcount < (MAXEXCEPTION - 1))) { + optx[xcount++] = xval; + xval = strtoull(++endptr, &endptr, 0); + } + if (*endptr || (xcount >= MAXEXCEPTION)) { + fprintf(stderr,"Bad exception list\n"); + err = TRUE; + } else { + optx[xcount++] = xval; + optx[xcount] = 0; + } + break; + default: + fprintf(stderr,"Unknown option '%s'.\n", + argv[optind - 1]); + err = TRUE; + } + } + + if (!optd && !optV && !opth) { + fprintf(stderr,"Device argument is missing\n"); + err = TRUE; + } + if (!(optb || optf || optp || optr || opts || optt || optu || optV)) + opts = 1; + if (optb && (optf || optr || opts)) { + fprintf(stderr,"Options -f, -r and -s are incompatible with -b\n"); + err = TRUE; + } + if (optf && (optp || opts || optu)) { + fprintf(stderr,"Options -p, -s and -u are incompatible with -f\n"); + err = TRUE; + } + if (optp && (optr || opts || optt || optu)) { + fprintf(stderr,"Options -r, -s, -t and -u are incompatible with -p\n"); + err = TRUE; + } + if (optr && (opts || optu)) { + fprintf(stderr,"Options -s and -u are incompatible with -r\n"); + err = TRUE; + } + if (opts && (optt || optu)) { + fprintf(stderr,"Options -t and -u are incompatible with -s\n"); + err = TRUE; + } + + if (opth || err) + usage(); + else + if (optV) + version(); + return (!err); +} + +/* + * Quick checks on the layout of needed structs + */ + +static BOOL checkstructs(void) +{ + BOOL ok; + + ok = TRUE; + if (sizeof(struct RECORD_PAGE_HEADER) != 40) { + fprintf(stderr, + "* error : bad sizeof(struct RECORD_PAGE_HEADER) %d\n", + (int)sizeof(struct RECORD_PAGE_HEADER)); + ok = FALSE; + } + if (sizeof(struct LOG_RECORD) != 88) { + fprintf(stderr, + "* error : bad sizeof(struct LOG_RECORD) %d\n", + (int)sizeof(struct LOG_RECORD)); + ok = FALSE; + } + if (sizeof(struct RESTART_PAGE_HEADER) != 32) { + fprintf(stderr, + "* error : bad sizeof(struct RESTART_PAGE_HEADER) %d\n", + (int)sizeof(struct RESTART_PAGE_HEADER)); + ok = FALSE; + } + if (sizeof(struct RESTART_AREA) != 44) { + fprintf(stderr, + "* error : bad sizeof(struct RESTART_AREA) %d\n", + (int)sizeof(struct RESTART_AREA)); + ok = FALSE; + } + if (sizeof(struct ATTR_OLD) != 44) { + fprintf(stderr, + "* error : bad sizeof(struct ATTR_OLD) %d\n", + (int)sizeof(struct ATTR_OLD)); + ok = FALSE; + } + if (sizeof(struct ATTR_NEW) != 40) { + fprintf(stderr, + "* error : bad sizeof(struct ATTR_NEW) %d\n", + (int)sizeof(struct ATTR_NEW)); + ok = FALSE; + } + if (LastAction != 38) { + fprintf(stderr, + "* error : bad action list, %d actions\n", + (int)LastAction); + ok = FALSE; + } + return (ok); +} + +int main(int argc, char *argv[]) +{ + CONTEXT ctx; + unsigned int i; + int err; + + err = 1; + if (checkstructs() + && getoptions(argc,argv)) { + if (optV || opth) { + err = 0; + } else { + redocount = 0; + undocount = 0; + actionnum = 0; + attrcount = 0; + redos_met = 0; + attrtable = (struct ATTR**)NULL; + for (i=0; i<(BUFFERCNT + BASEBLKS); i++) + buffer_table[i] = (struct BUFFER*)NULL; + ntfs_log_set_handler(ntfs_log_handler_outerr); + if (open_volume(&ctx, argv[argc - 1])) { + if (!ctx.vol + && (opts || optp || optu)) { + printf("Options -s, -p and -u" + " require a full device\n"); + err = 1; + } else { + err = walk(&ctx); + if (ctx.vol) { + if ((optp || optu || opts) + && !err + && !optn) { + reset_logfile(&ctx); + } + ntfs_attr_close(log_na); + ntfs_inode_close(log_ni); + ntfs_umount(ctx.vol, TRUE); + } else + fclose(ctx.file); + } + } else + fprintf(stderr,"Could not open %s\n", + argv[argc - 1]); + for (i=0; i<(BUFFERCNT + BASEBLKS); i++) + free(buffer_table[i]); + for (i=0; i +#endif +#ifdef HAVE_STDIO_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif +#ifdef HAVE_FCNTL_H +#include +#endif +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif +#ifdef HAVE_MALLOC_H +#include +#endif +#ifdef HAVE_TIME_H +#include +#endif + +#include "types.h" +#include "endians.h" +#include "support.h" +#include "layout.h" +#include "param.h" +#include "ntfstime.h" +#include "device_io.h" +#include "device.h" +#include "logging.h" +#include "runlist.h" +#include "mft.h" +#include "inode.h" +#include "attrib.h" +#include "bitmap.h" +#include "index.h" +#include "volume.h" +#include "unistr.h" +#include "mst.h" +#include "ntfsrecover.h" +#include "misc.h" + +struct STORE { + struct STORE *upper; + struct STORE *lower; + LCN lcn; + char data[1]; +} ; + +#define dump hexdump + +struct STORE *cluster_door = (struct STORE*)NULL; + +/* check whether a MFT or INDX record is older than action */ +#define older_record(rec, logr) ((s64)(le64_to_cpu((rec)->lsn) \ + - le64_to_cpu((logr)->this_lsn)) < 0) +/* check whether a MFT or INDX record is newer than action */ +#define newer_record(rec, logr) ((s64)(le64_to_cpu((rec)->lsn) \ + - le64_to_cpu((logr)->this_lsn)) > 0) + +/* + * A few functions for debugging + */ + +static int matchcount(const char *d, const char *s, int n) +{ + int m; + + m = 0; + while ((--n >= 0) && (*d++ == *s++)) m++; + return (m); +} + +/* +static void locate(const char *s, int n, const char *p, int m) +{ + int i,j; + + for (i=0; i<=(n - m); i++) + if (s[i] == *p) { + j = 1; + while ((j < m) && (s[i + j] == p[j])) + j++; + if (j == m) + printf("=== found at offset 0x%x %d\n",i,i); + } +} +*/ + +static u64 inode_number(const struct LOG_RECORD *logr) +{ + u64 offset; + + offset = ((u64)le32_to_cpu(logr->target_vcn) + << clusterbits) + + ((u32)le16_to_cpu(logr->cluster_index) + << NTFS_BLOCK_SIZE_BITS); + return (offset >> mftrecbits); +} + +/* + * Find an in-memory copy of a needed cluster + * + * Optionally, allocate a copy. + */ + +static struct STORE *getclusterentry(LCN lcn, BOOL create) +{ + struct STORE **current; + struct STORE *newone; + + current = &cluster_door; + /* A minimal binary tree should be enough */ + while (*current && (lcn != (*current)->lcn)) { + if (lcn > (*current)->lcn) + current = &(*current)->upper; + else + current = &(*current)->lower; + } + if (create && !*current) { + newone = (struct STORE*)malloc(sizeof(struct STORE) + + clustersz); + if (newone) { + newone->upper = (struct STORE*)NULL; + newone->lower = (struct STORE*)NULL; + newone->lcn = lcn; + *current = newone; + } + } + return (*current); +} + +void freeclusterentry(struct STORE *entry) +{ + if (!entry) { + if (cluster_door) + freeclusterentry(cluster_door); + cluster_door = (struct STORE*)NULL; + } else { + if (optv) + printf("* cluster 0x%llx %s updated\n", + (long long)entry->lcn, + (optn ? "would be" : "was")); + if (entry->upper) + freeclusterentry(entry->upper); + if (entry->lower) + freeclusterentry(entry->lower); + free(entry); + } +} + +/* + * Check whether an attribute type is a valid one + */ + +static BOOL valid_type(ATTR_TYPES type) +{ + BOOL ok; + + switch (type) { + case AT_STANDARD_INFORMATION : + case AT_ATTRIBUTE_LIST : + case AT_FILE_NAME : + case AT_OBJECT_ID : + case AT_SECURITY_DESCRIPTOR : + case AT_VOLUME_NAME : + case AT_VOLUME_INFORMATION : + case AT_DATA : + case AT_INDEX_ROOT : + case AT_INDEX_ALLOCATION : + case AT_BITMAP : + case AT_REPARSE_POINT : + case AT_EA_INFORMATION : + case AT_EA : + case AT_PROPERTY_SET : + case AT_LOGGED_UTILITY_STREAM : + case AT_FIRST_USER_DEFINED_ATTRIBUTE : + case AT_END : + ok = TRUE; + break; + default : + ok = FALSE; + break; + } + return (ok); +} + +/* + * Rough check of sanity of an index list + */ + +static int sanity_indx_list(const char *buffer, u32 k, u32 end) +{ + le64 inode; + int err; + int lth; + BOOL done; + + err = 0; + done = FALSE; + while ((k <= end) && !done) { + lth = getle16(buffer,k+8); + if (optv > 1) + /* Usual indexes can be determined from size */ + switch (lth) { + case 16 : /* final without subnode */ + case 24 : /* final with subnode */ + printf("index to none lth 0x%x" + " flags 0x%x pos 0x%x\n", + (int)lth, + (int)getle16(buffer,k+12),(int)k); + break; + case 32 : /* $R in $Reparse */ + /* Badly aligned */ + memcpy(&inode, &buffer[k + 20], 8); + printf("index to reparse of 0x%016llx lth 0x%x" + " flags 0x%x pos 0x%x\n", + (long long)le64_to_cpu(inode), + (int)lth, + (int)getle16(buffer,k+12),(int)k); + break; + case 40 : /* $SII in $Secure */ + printf("index to securid 0x%lx lth 0x%x" + " flags 0x%x pos 0x%x\n", + (long)getle32(buffer,k + 16), + (int)lth, + (int)getle16(buffer,k+12),(int)k); + break; + case 48 : /* $SDH in $Secure */ + printf("index to securid 0x%lx lth 0x%x" + " flags 0x%x pos 0x%x\n", + (long)getle32(buffer,k + 20), + (int)lth, + (int)getle16(buffer,k+12),(int)k); + break; + default : /* at least 80 */ + printf("index to inode 0x%016llx lth 0x%x" + " flags 0x%x pos 0x%x\n", + (long long)getle64(buffer,k), + (int)lth, + (int)getle16(buffer,k+12),(int)k); + } + done = (feedle16(buffer,k+12) & INDEX_ENTRY_END) || !lth; + k += lth; + } + if (k != end) { + printf("** Bad index record length %ld (computed %ld)\n", + (long)end, (long)k); + err = 1; + } + if (!done) { + printf("** Missing end of index mark\n"); + err = 1; + } + return (err); +} + +/* + * Rough check of sanity of an mft record + */ + +static int sanity_mft(const char *buffer) +{ + const MFT_RECORD *record; + const ATTR_RECORD *attr; + u64 instances; + u32 k; + u32 type; + u32 prevtype; + u16 nextinstance; + u16 instance; + int err; + + err = 0; + record = (const MFT_RECORD*)buffer; + nextinstance = le16_to_cpu(record->next_attr_instance); + instances = 0; + k = le16_to_cpu(record->attrs_offset); + attr = (const ATTR_RECORD*)&buffer[k]; + prevtype = 0; + while ((k < mftrecsz) + && (attr->type != AT_END) + && valid_type(attr->type)) { + type = le32_to_cpu(attr->type); + if (type < prevtype) { + printf("** Bad type ordering 0x%lx after 0x%lx\n", + (long)type, (long)prevtype); + err = 1; + } + instance = le16_to_cpu(attr->instance); + /* Can nextinstance wrap around ? */ + if (instance >= nextinstance) { + printf("** Bad attr instance %d (max %d)\n", + (int)instance, (int)nextinstance - 1); + err = 1; + } + if (instance < 64) { + /* Only check up to 64 */ + if (((u64)1 << instance) & instances) { + printf("** Duplicated attr instance %d\n", + (int)instance); + } + instances |= (u64)1 << instance; + } + if (optv > 1) { + if ((attr->type == AT_FILE_NAME) + && buffer[k + 88]) { + printf("attr %08lx offs 0x%x nres %d", + (long)type, (int)k, + (int)attr->non_resident); + showname(" ",&buffer[k+90], + buffer[k + 88] & 255); + } else + printf("attr %08lx offs 0x%x nres %d\n", + (long)type, (int)k, + (int)attr->non_resident); + } + if ((attr->type == AT_INDEX_ROOT) + && sanity_indx_list(buffer, + k + le16_to_cpu(attr->value_offset) + 32, + k + le32_to_cpu(attr->length))) { + err = 1; + } + k += le32_to_cpu(attr->length); + attr = (const ATTR_RECORD*)&buffer[k]; + prevtype = type; + } + if ((optv > 1) && (attr->type == AT_END)) + printf("attr %08lx offs 0x%x\n", + (long)le32_to_cpu(attr->type), (int)k); + if ((attr->type != AT_END) + || (le32_to_cpu(record->bytes_in_use) != (k + 8)) + || (le32_to_cpu(record->bytes_allocated) < (k + 8))) { + printf("** Bad MFT record length %ld" + " (computed %ld allocated %ld)\n", + (long)le32_to_cpu(record->bytes_in_use), + (long)(k + 8), + (long)le32_to_cpu(record->bytes_allocated)); + err = 1; + } + return (err); +} + +/* + * Rough check of sanity of an index block + */ + +static int sanity_indx(ntfs_volume *vol, const char *buffer) +{ + const INDEX_BLOCK *indx; + u32 k; + int err; + + err = 0; + indx = (const INDEX_BLOCK*)buffer; + k = offsetof(INDEX_BLOCK, index) + + le32_to_cpu(indx->index.entries_offset); + err = sanity_indx_list(buffer, k, + le32_to_cpu(indx->index.index_length) + 24); + if ((le32_to_cpu(indx->index.index_length) + > le32_to_cpu(indx->index.allocated_size)) + || (le32_to_cpu(indx->index.allocated_size) + != (vol->indx_record_size - 24))) { + printf("** Bad index length %ld" + " (usable %ld allocated %ld)\n", + (long)le32_to_cpu(indx->index.index_length), + (long)(vol->indx_record_size - 24), + (long)le32_to_cpu(indx->index.allocated_size)); + err = 1; + } + return (err); +} + + +/* + * Allocate a buffer and read a full set of raw clusters + * + * Do not use for accessing $LogFile. + * With option -n reading is first attempted from the memory store + */ + +static char *read_raw(ntfs_volume *vol, const struct LOG_RECORD *logr) +{ + char *buffer; + char *target; + struct STORE *store; + LCN lcn; + int count; + int i; + BOOL fail; + + count = le16_to_cpu(logr->lcns_to_follow); + if (!count) { + printf("** Error : no lcn to read from\n"); + buffer = (char*)NULL; + } else + buffer = (char*)malloc(clustersz*count); +// TODO error messages + if (buffer) { + fail = FALSE; + for (i=0; (ilcn_list[i]); + target = buffer + clustersz*i; + if (optn) { + store = getclusterentry(lcn, FALSE); + if (store) { + memcpy(target, store->data, clustersz); + if (optv) + printf("== lcn 0x%llx from store\n", + (long long)lcn); + if ((optv > 1) && optc + && within_lcn_range(logr)) + dump(store->data, clustersz); + } + } + if (!store + && (ntfs_pread(vol->dev, lcn << clusterbits, + clustersz, target) != clustersz)) { + fail = TRUE; + } else { + if (!store) { + if (optv) + printf("== lcn 0x%llx" + " from device\n", + (long long)lcn); + if ((optv > 1) && optc + && within_lcn_range(logr)) + dump(target, clustersz); + } + } + } + if (fail) { + printf("** Could not read cluster 0x%llx\n", + (long long)lcn); + free(buffer); + buffer = (char*)NULL; + } + } + return (buffer); +} + +/* + * Write a full set of raw clusters + * + * Do not use for accessing $LogFile. + * With option -n a copy of the buffer is kept in memory for later use. + */ + +static int write_raw(ntfs_volume *vol, const struct LOG_RECORD *logr, + char *buffer) +{ + int err; + struct STORE *store; + LCN lcn; + char *source; + int count; + int i; + + err = 0; + count = le16_to_cpu(logr->lcns_to_follow); + if (!count) + printf("** Error : no lcn to write to\n"); + if (optn) { + for (i=0; (ilcn_list[i]); + source = buffer + clustersz*i; + store = getclusterentry(lcn, TRUE); + if (store) { + memcpy(store->data, source, clustersz); + if (optv) + printf("== lcn 0x%llx to store\n", + (long long)lcn); + if ((optv > 1) && optc + && within_lcn_range(logr)) + dump(store->data, clustersz); + } else { + printf("** Could not store cluster 0x%llx\n", + (long long)lcn); + err = 1; + } + } + } else { + for (i=0; (ilcn_list[i]); + if (optv) + printf("== lcn 0x%llx to device\n", + (long long)lcn); + source = buffer + clustersz*i; + if (ntfs_pwrite(vol->dev, lcn << clusterbits, + clustersz, source) != clustersz) { + printf("** Could not write cluster 0x%llx\n", + (long long)lcn); + err = 1; + } + } + } + return (err); +} + +/* + * Write a full set of raw clusters to mft_mirr + */ + +static int write_mirr(ntfs_volume *vol, const struct LOG_RECORD *logr, + char *buffer) +{ + int err; + LCN lcn; + char *source; + int count; + int i; + + err = 0; + count = le16_to_cpu(logr->lcns_to_follow); + if (!count) + printf("** Error : no lcn to write to\n"); + if (!optn) { + for (i=0; (imftmirr_na, + le32_to_cpu(logr->target_vcn) + i); + source = buffer + clustersz*i; + if ((lcn < 0) + || (ntfs_pwrite(vol->dev, lcn << clusterbits, + clustersz, source) != clustersz)) { + printf("** Could not write cluster 0x%llx\n", + (long long)lcn); + err = 1; + } + } + } + return (err); +} + +/* + * Allocate a buffer and read a single protected record + */ + +static char *read_protected(ntfs_volume *vol, const struct LOG_RECORD *logr, + u32 size, BOOL warn) +{ + char *buffer; + char *full; + u32 pos; + LCN lcn; + + /* read full clusters */ + buffer = read_raw(vol, logr); + /* + * if the record is smaller than a cluster, + * make a partial copy and free the full buffer + */ + if (buffer && (size < clustersz)) { + full = buffer; + buffer = (char*)malloc(size); + if (buffer) { + pos = le16_to_cpu(logr->cluster_index) + << NTFS_BLOCK_SIZE_BITS; + memcpy(buffer, full + pos, size); + } + free(full); + } + if (buffer && (ntfs_mst_post_read_fixup_warn( + (NTFS_RECORD*)buffer, size, FALSE) < 0)) { + if (warn) { + lcn = le64_to_cpu(logr->lcn_list[0]); + printf("** Invalid protected record at 0x%llx" + " index %d\n", + (long long)lcn, + (int)le16_to_cpu(logr->cluster_index)); + } + free(buffer); + buffer = (char*)NULL; + } + return (buffer); +} + +/* + * Protect a single record, write, and deallocate the buffer + * + * With option -n a copy of the buffer is kept in protected form in + * memory for later use. + * As the store only knows about clusters, if the record is smaller + * than a cluster, have to read, merge and write. + */ + +static int write_protected(ntfs_volume *vol, const struct LOG_RECORD *logr, + char *buffer, u32 size) +{ + MFT_RECORD *record; + INDEX_BLOCK *indx; + char *full; + u32 pos; + BOOL mftmirr; + BOOL checked; + int err; + + err = 0; + mftmirr = FALSE; + checked = FALSE; + if ((size == mftrecsz) && !memcmp(buffer,"FILE",4)) { + record = (MFT_RECORD*)buffer; + if (optv) + printf("update inode %ld lsn 0x%llx" + " (record %s than action 0x%llx)\n", + (long)le32_to_cpu(record->mft_record_number), + (long long)le64_to_cpu(record->lsn), + ((s64)(le64_to_cpu(record->lsn) + - le64_to_cpu(logr->this_lsn)) < 0 ? + "older" : "newer"), + (long long)le64_to_cpu(logr->this_lsn)); + if (optv > 1) + printf("mft vcn %ld index %d\n", + (long)le32_to_cpu(logr->target_vcn), + (int)le16_to_cpu(logr->cluster_index)); + err = sanity_mft(buffer); + /* Should set to some previous lsn for undos */ + if (opts) + record->lsn = logr->this_lsn; + /* Duplicate on mftmirr if not overflowing its size */ + mftmirr = (((u64)le32_to_cpu(logr->target_vcn) + + le16_to_cpu(logr->lcns_to_follow)) + << clusterbits) + <= (((u64)vol->mftmirr_size) << mftrecbits); + checked = TRUE; + } + if ((size == vol->indx_record_size) && !memcmp(buffer,"INDX",4)) { + indx = (INDEX_BLOCK*)buffer; + if (optv) + printf("update index lsn 0x%llx" + " (index %s than action 0x%llx)\n", + (long long)le64_to_cpu(indx->lsn), + ((s64)(le64_to_cpu(indx->lsn) + - le64_to_cpu(logr->this_lsn)) < 0 ? + "older" : "newer"), + (long long)le64_to_cpu(logr->this_lsn)); + err = sanity_indx(vol, buffer); + /* Should set to some previous lsn for undos */ + if (opts) + indx->lsn = logr->this_lsn; + checked = TRUE; + } + if (!checked) { + printf("** Error : writing protected record of unknown type\n"); + err = 1; + } + if (!err) { + if (!ntfs_mst_pre_write_fixup((NTFS_RECORD*)buffer, size)) { + /* + * If the record is smaller than a cluster, get a full + * cluster, merge and write. + */ + if (size < clustersz) { + full = read_raw(vol, logr); + if (full) { + pos = le16_to_cpu(logr->cluster_index) + << NTFS_BLOCK_SIZE_BITS; + memcpy(full + pos, buffer, size); + err = write_raw(vol, logr, full); + if (!err && mftmirr && !optn) + err = write_mirr(vol, logr, + full); + free(full); + } + } else { + /* write full clusters */ + err = write_raw(vol, logr, buffer); + if (!err && mftmirr && !optn) + err = write_mirr(vol, logr, buffer); + } + } else { + printf("** Failed to protect record\n"); + err = 1; + } + } + return (err); +} + +/* + * Resize attribute records + * + * The attribute value is resized to new size, but the attribute + * and MFT record must be kept aligned to 8 bytes. + */ + +static int resize_attribute(MFT_RECORD *entry, ATTR_RECORD *attr, INDEX_ROOT *index, + int rawresize, int resize) +{ + int err; + u32 newlength; + u32 newused; + u32 newvalue; + u32 indexlth; + u32 indexalloc; + + err = 0; + if (attr) { + newvalue = le32_to_cpu(attr->value_length) + rawresize; + attr->value_length = cpu_to_le32(newvalue); + newlength = le32_to_cpu(attr->length) + resize; + attr->length = cpu_to_le32(newlength); + } + if (entry) { + newused = le32_to_cpu(entry->bytes_in_use) + resize; + entry->bytes_in_use = cpu_to_le32(newused); + } + if (index) { + indexlth = le32_to_cpu(index->index.index_length) + resize; + index->index.index_length = cpu_to_le32(indexlth); + indexalloc = le32_to_cpu(index->index.allocated_size) + resize; + index->index.allocated_size = cpu_to_le32(indexalloc); + } + return (err); +} + +/* + * Adjust the next attribute instance + * + * If a newly created attribute matches the next instance, then + * the next instance has to be incremented. + * + * Do the opposite when undoing an attribute creation, but + * do not change the next instance when deleting an attribute + * or undoing the deletion. + */ + +static void adjust_instance(const ATTR_RECORD *attr, MFT_RECORD *entry, int increment) +{ + u16 instance; + + if (increment > 0) { + /* Allocating a new instance ? */ + if (attr->instance == entry->next_attr_instance) { + instance = (le16_to_cpu(entry->next_attr_instance) + + 1) & 0xffff; + entry->next_attr_instance = cpu_to_le16(instance); + } + } + if (increment < 0) { + /* Freeing the latest instance ? */ + instance = (le16_to_cpu(entry->next_attr_instance) + - 1) & 0xffff; + if (attr->instance == cpu_to_le16(instance)) + entry->next_attr_instance = attr->instance; + } +} + +/* + * Adjust the highest vcn according to mapping pairs + * + * The runlist has to be fully recomputed + */ + +static int adjust_high_vcn(ntfs_volume *vol, ATTR_RECORD *attr) +{ + runlist_element *rl; + runlist_element *xrl; + VCN high_vcn; + int err; + + err = 1; + attr->highest_vcn = cpu_to_le64(0); + rl = ntfs_mapping_pairs_decompress(vol, attr, (runlist_element*)NULL); + if (rl) { + xrl = rl; + while (xrl->length) + xrl++; + high_vcn = xrl->vcn - 1; + attr->highest_vcn = cpu_to_le64(high_vcn); + free(rl); + err = 0; + } else { + printf("** Failed to decompress the runlist\n"); + dump((char*)attr,128); + } + return (err); +} + +/* + * Check index match, to be used for undos only + * + * The action UpdateFileNameRoot updates the time stamps and/or the + * sizes, but the lsn is not updated in the index record. + * As a consequence such UpdateFileNameRoot are not always undone + * and the actual record does not fully match the undo data. + * We however accept the match if the parent directory and the name + * match. + * Alternate workaround : do not check the lsn when undoing + * UpdateFileNameRoot + */ + +static BOOL index_match_undo(const char *first, const char *second, int length) +{ + int len; + BOOL match; + + match = !memcmp(first, second, length); + if (!match) { + if (optv) { + printf("The existing index does not match :\n"); + dump(second,length); + } + len = (first[80] & 255)*2 + 2; + match = (feedle64(first, 16) == feedle64(second, 16)) + && !memcmp(first + 80, second + 80, len); + if (match && optv) + printf("However parent dir and name do match\n"); + } + return (match); +} + + +/* + * Generic idempotent change to a resident attribute + */ + +static int change_resident(ntfs_volume *vol, const struct ACTION_RECORD *action, + char *buffer, const char *data, u32 target, u32 length) +{ + LCN lcn; + ATTR_RECORD *attr; + u32 attrend; + int err; + int changed; + + err = 1; + if (action->record.undo_length != action->record.redo_length) + printf("** Error size change in change_resident\n"); + if (optv > 1) { + lcn = le64_to_cpu(action->record.lcn_list[0]); + printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n", + (long long)inode_number(&action->record), + (long long)lcn, (int)target, (int)length); + } + attr = (ATTR_RECORD*)(buffer + + le16_to_cpu(action->record.record_offset)); + if (optv > 1) { + printf("-> existing record :\n"); + dump(&buffer[target], length); + printf("-> full MFT record :\n"); + dump(buffer,mftrecsz); + } + attrend = le16_to_cpu(action->record.record_offset) + + le32_to_cpu(attr->length); + if ((target + length) > attrend) { + printf("** Error : update overflows from attribute\n"); + } + if (!(length & 7) + && ((target + length) <= attrend) + && (attrend <= mftrecsz) + && !sanity_mft(buffer)) { + changed = memcmp(buffer + target, data, length); + err = 0; + if (changed) { + memcpy(buffer + target, data, length); + if (optv > 1) { + printf("-> new record :\n"); + dump(buffer + target, length); + } + err = write_protected(vol, &action->record, + buffer, mftrecsz); + } + if (optv > 1) { + printf("-> MFT record %s\n", + (changed ? "updated" : "unchanged")); + } + } + return (err); +} + +static int change_resident_expect(ntfs_volume *vol, const struct ACTION_RECORD *action, + char *buffer, const char *data, const char *expected, + u32 target, u32 length, ATTR_TYPES type) +{ + LCN lcn; + ATTR_RECORD *attr; + int err; + BOOL found; + + err = 1; + if (action->record.undo_length != action->record.redo_length) + printf("** Error size change in change_resident\n"); + if (optv > 1) { + lcn = le64_to_cpu(action->record.lcn_list[0]); + printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n", + (long long)inode_number(&action->record), + (long long)lcn, (int)target, (int)length); + } + attr = (ATTR_RECORD*)(buffer + + le16_to_cpu(action->record.record_offset)); + if (optv > 1) { + printf("-> existing record :\n"); + dump(&buffer[target], length); + printf("-> full record :\n"); + dump((char*)attr, le32_to_cpu(attr->length)); + } + if ((attr->type == type) + && !(length & 7) + && ((target + length) <= mftrecsz)) { + found = !memcmp(buffer + target, expected, length); + err = 0; + if (found) { + memcpy(buffer + target, data, length); + if (optv > 1) { + printf("-> new record :\n"); + dump(buffer + target, length); + } + err = write_protected(vol, &action->record, + buffer, mftrecsz); + } + if (optv > 1) { + printf("-> MFT record %s\n", + (found ? "updated" : "unchanged")); + } + } + return (err); +} + +/* + * Generic idempotent change to a an index value + * + */ + +static int change_index_value(ntfs_volume *vol, const struct ACTION_RECORD *action, + char *buffer, const char *data, u32 target, u32 length) +{ + LCN lcn; + u32 count; + u32 xsize; + int changed; + int err; + + err = 1; + count = le16_to_cpu(action->record.lcns_to_follow); + if (optv > 1) { + lcn = le64_to_cpu(action->record.lcn_list[0]); + printf("-> lcn 0x%llx target 0x%x length %d\n", + (long long)lcn, (int)target, (int)length); + } + xsize = vol->indx_record_size; + if (optv > 1) { + printf("-> existing record :\n"); + dump(&buffer[target], length); + } + if ((target + length) <= (count << clusterbits)) { + changed = memcmp(buffer + target, data, length); + err = 0; + if (changed) { + memcpy(buffer + target, data, length); + if (optv > 1) { + printf("-> new record :\n"); + dump(buffer + target, length); + } + err = write_protected(vol, &action->record, + buffer, xsize); + } + if (optv > 1) { + printf("-> data record %s\n", + (changed ? "updated" : "unchanged")); + } + } + return (err); +} + +/* + * Add one or more resident attributes + */ + +static int add_resident(ntfs_volume *vol, const struct ACTION_RECORD *action, + char *buffer, const char *data, u32 target, + u32 length, u32 oldlength) +{ + LCN lcn; + MFT_RECORD *entry; + int err; + BOOL found; + int resize; + + err = 1; + if (optv > 1) { + lcn = le64_to_cpu(action->record.lcn_list[0]); + printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n", + (long long)inode_number(&action->record), + (long long)lcn, (int)target, (int)length); + } + entry = (MFT_RECORD*)buffer; + resize = length - oldlength; + if (optv > 1) { + printf("existing data :\n"); + dump(buffer + target,length); + } + if (!(length & 7) + && !(oldlength & 7) + && ((target + length) <= mftrecsz)) { + /* This has to be an idempotent action */ + err = 0; + if (data && length) + found = !memcmp(buffer + target, + data, length); + else { + found = TRUE; + err = 1; + } + if (!found && !err) { + /* Make space to insert the entry */ + memmove(buffer + target + resize, + buffer + target, + mftrecsz - target - resize); + if (data) + memcpy(buffer + target, data, length); + else + memset(buffer + target, 0, length); + resize_attribute(entry, NULL, NULL, + resize, resize); + if (optv > 1) { + printf("new data at same location :\n"); + dump(buffer + target, length); + } + err = write_protected(vol, &action->record, + buffer, mftrecsz); + } + if (optv > 1) { + printf("-> MFT record %s\n", + (found ? "unchanged" : "expanded")); + } + } + return (err); +} + +/* + * Add one or more non-resident records + */ + +static int delete_non_resident(void /*ntfs_volume *vol, + const struct ACTION_RECORD *action, + const char *data, u32 target, u32 length, u32 oldlength*/) +{ + int err; + + err = 1; + printf("** delete_non_resident() not implemented\n"); + return (err); +} + +/* + * Expand a single resident attribute + */ + +static int expand_resident(ntfs_volume *vol, const struct ACTION_RECORD *action, + char *buffer, const char *data, u32 target, + u32 length, u32 oldlength) +{ + LCN lcn; + ATTR_RECORD *attr; + MFT_RECORD *entry; + int err; + BOOL found; + int resize; + u16 base; + + err = 1; + if (optv > 1) { + lcn = le64_to_cpu(action->record.lcn_list[0]); + printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n", + (long long)inode_number(&action->record), + (long long)lcn, (int)target, (int)length); + } + entry = (MFT_RECORD*)buffer; + attr = (ATTR_RECORD*)(buffer + + le16_to_cpu(action->record.record_offset)); + if (optv > 1) { + printf("existing data :\n"); + dump(buffer + target,length); + } + base = 24 + 2*attr->name_length; + resize = ((base + length - 1) | 7) + - ((base + oldlength - 1) | 7); + if ((target + length) <= mftrecsz) { + /* This has to be an idempotent action */ +// TODO This test is wrong ! + found = le32_to_cpu(attr->value_length) == length; + if (found && data && length) + found = !memcmp(buffer + target, data, length); + err = 0; + if (!found) { + /* Make space to insert the entry */ + memmove(buffer + target + resize, + buffer + target, + mftrecsz - target - resize); +// TODO what to do if length is not a multiple of 8 ? + if (data) + memcpy(buffer + target, data, length); + else + memset(buffer + target, 0, length); + resize_attribute(entry, attr, NULL, + length - oldlength, resize); + if (optv > 1) { + printf("new data at same location :\n"); + dump(buffer + target, length); + } + err = write_protected(vol, &action->record, + buffer, mftrecsz); + } + if (optv > 1) { + printf("-> MFT record %s\n", + (found ? "unchanged" : "expanded")); + } + } + return (err); +} + +/* + * Add one or more non-resident records + */ + +static int add_non_resident(void /*ntfs_volume *vol, + const struct ACTION_RECORD *action, + const char *data, u32 target, u32 length, u32 oldlength*/) +{ + int err; + + printf("** add_non_resident() not implemented\n"); + err = 0; + return (err); +} + +/* + * Generic insert a new resident attribute + */ + +static int insert_resident(ntfs_volume *vol, const struct ACTION_RECORD *action, + char *buffer, const char *data, u32 target, + u32 length) +{ + LCN lcn; + ATTR_RECORD *attr; + const ATTR_RECORD *newattr; + MFT_RECORD *entry; + u32 newused; + u16 links; + int err; + BOOL found; + + err = 1; + if (optv > 1) { + lcn = le64_to_cpu(action->record.lcn_list[0]); + printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n", + (long long)inode_number(&action->record), + (long long)lcn, (int)target, (int)length); + } + entry = (MFT_RECORD*)buffer; + attr = (ATTR_RECORD*)(buffer + + le16_to_cpu(action->record.record_offset)); + newattr = (const ATTR_RECORD*)data; + if (optv > 1) { + printf("existing record :\n"); + dump(buffer + target,length); + if (le32_to_cpu(attr->type) < le32_to_cpu(newattr->type)) { + printf("** Bad attribute order, full record :\n"); + dump(buffer, mftrecsz); + } + } + /* Types must be in ascending order */ + if (valid_type(attr->type) + && (le32_to_cpu(attr->type) + >= le32_to_cpu(newattr->type)) + && !(length & 7) + && ((target + length) <= mftrecsz)) { + /* This has to be an idempotent action */ + found = !memcmp(buffer + target, data, length); + err = 0; + if (!found) { + /* Make space to insert the entry */ + memmove(buffer + target + length, + buffer + target, + mftrecsz - target - length); + memcpy(buffer + target, data, length); + newused = le32_to_cpu(entry->bytes_in_use) + + length; + entry->bytes_in_use = cpu_to_le32(newused); + if (action->record.redo_operation + == const_cpu_to_le16(CreateAttribute)) { + /* + * For a real create, may have to adjust + * the next attribute instance + */ + adjust_instance(newattr, entry, 1); + } + if (newattr->type == AT_FILE_NAME) { + links = le16_to_cpu(entry->link_count) + 1; + entry->link_count = cpu_to_le16(links); + } + if (optv > 1) { + printf("expanded record (now 0x%x" + " bytes used) :\n", + (int)newused); + dump(buffer + target, 2*length); + } + err = write_protected(vol, &action->record, + buffer, mftrecsz); + } + if (optv > 1) { + printf("-> MFT record %s\n", + (found ? "unchanged" : "expanded")); + } + } + return (err); +} + +/* + * Generic remove a single resident attribute + */ + +static int remove_resident(ntfs_volume *vol, const struct ACTION_RECORD *action, + char *buffer, const char *data, u32 target, + u32 length) +{ + LCN lcn; + ATTR_RECORD *attr; + MFT_RECORD *entry; + u32 newused; + u16 links; + int err; + BOOL found; + + err = 1; + if (optv > 1) { + lcn = le64_to_cpu(action->record.lcn_list[0]); + printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n", + (long long)inode_number(&action->record), + (long long)lcn, (int)target, (int)length); + } + entry = (MFT_RECORD*)buffer; + attr = (ATTR_RECORD*)(buffer + + le16_to_cpu(action->record.record_offset)); + if (optv > 1) { + printf("existing record :\n"); + dump(buffer + target,length); + } + if (!(length & 7) + && ((target + length) <= mftrecsz)) { + /* This has to be an idempotent action */ + /* For AT_DATA the value is not always present */ + if (attr->type == AT_DATA) + found = !memcmp(buffer + target, data, + le16_to_cpu(attr->value_offset)); + else + found = !memcmp(buffer + target, data, length); + if (!found && optv) { + printf("data 0x%lx 0x%lx offset %d %ld\n", + (long)le32_to_cpu(attr->type), + (long)le32_to_cpu(AT_DATA), + (int)offsetof(ATTR_RECORD, resident_end), + (long)le16_to_cpu(attr->value_offset)); + printf("The existing record does not match (%d/%d)\n", + (int)matchcount(buffer + target, data, + length),(int)length); + dump(data,length); + printf("full attr :\n"); + dump((const char*)attr,mftrecsz + - le16_to_cpu(action->record.record_offset)); + } + err = 0; + if (found) { + if (attr->type == AT_FILE_NAME) { + links = le16_to_cpu(entry->link_count) - 1; + entry->link_count = cpu_to_le16(links); + } + if (action->record.redo_operation + == const_cpu_to_le16(CreateAttribute)) { + adjust_instance(attr, entry, -1); + } + /* Remove the entry */ + memmove(buffer + target, + buffer + target + length, + mftrecsz - target - length); + newused = le32_to_cpu(entry->bytes_in_use) - length; + entry->bytes_in_use = cpu_to_le32(newused); + if (optv > 1) { + printf("new record at same location" + " (now 0x%x bytes used) :\n", + (int)newused); + dump(buffer + target, length); + } + err = write_protected(vol, &action->record, + buffer, mftrecsz); + } + if (optv > 1) { + printf("-> MFT record %s\n", + (found ? "shrinked" : "unchanged")); + } + } + return (err); +} + +/* + * Delete one or more resident attributes + */ + +static int delete_resident(ntfs_volume *vol, const struct ACTION_RECORD *action, + char *buffer, const char *data, u32 target, + u32 length, u32 oldlength) +{ + LCN lcn; + MFT_RECORD *entry; + int err; + BOOL found; + int resize; + + if (optv > 1) + printf("-> %s()\n",__func__); + err = 1; + if (optv > 1) { + lcn = le64_to_cpu(action->record.lcn_list[0]); + printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n", + (long long)inode_number(&action->record), + (long long)lcn, (int)target, (int)length); + } + entry = (MFT_RECORD*)buffer; + if (optv > 1) { + printf("existing data :\n"); + dump(buffer + target,length); + } + resize = length - oldlength; + if (!(length & 7) + && !(oldlength & 7) + && ((target + oldlength) <= mftrecsz)) { + /* This has to be an idempotent action */ + err = 0; + if (data && length) + found = !memcmp(buffer + target, data, length); + else { + found = FALSE; + err = 1; + } + if (!found && !err) { + /* Remove the entry, if present */ + memmove(buffer + target, + buffer + target - resize, + mftrecsz - target + resize); + resize_attribute(entry, NULL, NULL, + length - oldlength, resize); + if (optv > 1) { + printf("new data at same location :\n"); + dump(buffer + target, length); + } + err = write_protected(vol, &action->record, + buffer, mftrecsz); + } + if (optv > 1) { + printf("-> MFT record %s\n", + (found ? "unchanged" : "shrinked")); + } + } + return (err); +} + +static int shrink_resident(ntfs_volume *vol, const struct ACTION_RECORD *action, + char *buffer, const char *data, u32 target, + u32 length, u32 oldlength) +{ + LCN lcn; + ATTR_RECORD *attr; + MFT_RECORD *entry; + int err; + BOOL found; + int resize; + u16 base; + + if (optv > 1) + printf("-> %s()\n",__func__); + err = 1; + if (optv > 1) { + lcn = le64_to_cpu(action->record.lcn_list[0]); + printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n", + (long long)inode_number(&action->record), + (long long)lcn, (int)target, (int)length); + } + entry = (MFT_RECORD*)buffer; + attr = (ATTR_RECORD*)(buffer + + le16_to_cpu(action->record.record_offset)); + if (optv > 1) { + printf("existing data :\n"); + dump(buffer + target,length); + } + base = 24 + 2*attr->name_length; + resize = ((base + length - 1) | 7) + - ((base + oldlength - 1) | 7); + if ((oldlength > length) +// TODO limit to attr length + && ((target + oldlength) <= mftrecsz)) { + /* This has to be an idempotent action */ + if (data && length) + found = !memcmp(buffer + target, data, length); + else +{ +// TODO wrong : need checking against the old data, but in known cases +// redo data is not available either and existing data is not zero. + found = FALSE; +printf("* fake test, assuming not shrinked : value length %ld length %ld oldlength %ld\n",(long)le32_to_cpu(attr->value_length),(long)length,(long)oldlength); +//dump(buffer + target, oldlength); +} + err = 0; + if (!found) { + if (length) { + /* Relocate end of record */ +// TODO restrict to bytes_in_use + memmove(buffer + target + length, + buffer + target + oldlength, + mftrecsz - target - oldlength); + /* Insert new data or zeroes */ + if (data) + memcpy(buffer + target, data, length); + else + memset(buffer + target, 0, length); + } else { + /* Remove the entry, unless targeted size */ + memmove(buffer + target, + buffer + target - resize, + mftrecsz - target + resize); + } + resize_attribute(entry, attr, NULL, + length - oldlength, resize); + if (optv > 1) { + printf("new data at same location :\n"); + dump(buffer + target, length); + } + err = write_protected(vol, &action->record, + buffer, mftrecsz); + } + if (optv > 1) { + printf("-> MFT record %s\n", + (found ? "unchanged" : "shrinked")); + } + } + return (err); +} + +static int update_index(ntfs_volume *vol, const struct ACTION_RECORD *action, + char *buffer, const char *data, u32 target, u32 length) +{ + LCN lcn; + INDEX_BLOCK *indx; + u32 xsize; + BOOL changed; + int err; + + err = 1; + if (optv > 1) { + lcn = le64_to_cpu(action->record.lcn_list[0]); + printf("-> lcn 0x%llx target 0x%x length %d\n", + (long long)lcn, (int)target, (int)length); + } + xsize = vol->indx_record_size; + indx = (INDEX_BLOCK*)(buffer + + le16_to_cpu(action->record.record_offset)); + if (optv > 1) { + printf("-> existing index :\n"); + dump(&buffer[target], length); + } + if ((indx->magic == magic_INDX) + && !(length & 7) + && ((target + length) <= xsize)) { + /* This has to be an idempotent action */ + changed = memcmp(buffer + target, data, length); + err = 0; + if (changed) { + /* Update the entry */ + memcpy(buffer + target, data, length); + if (optv > 1) { + printf("-> new index :\n"); + dump(&buffer[target], length); + } + err = write_protected(vol, &action->record, + buffer, xsize); + } + if (optv > 1) { + printf("-> INDX record %s\n", + (changed ? "updated" : "unchanged")); + } + } + return (err); +} + +/* + * Controversial deletion of file names, see undo_delete_file() + */ + +static int delete_names(char *buffer) +{ + MFT_RECORD *record; + ATTR_RECORD *attr; + u32 used; + u32 pos; + int length; + int cnt; + + record = (MFT_RECORD*)buffer; + pos = le16_to_cpu(record->attrs_offset); + used = le32_to_cpu(record->bytes_in_use); + cnt = 0; + do { + attr = (ATTR_RECORD*)&buffer[pos]; + length = le32_to_cpu(attr->length); + if (attr->type == AT_FILE_NAME) { + if (optv) + showname("Controversial deletion of ", + &buffer[pos+90], buffer[pos+88] & 255); + memmove(buffer + pos, buffer + pos + length, + mftrecsz - pos - length); + used -= length; + cnt++; + } else + pos += length; + } while ((pos < used) + && (le32_to_cpu(attr->type) <= le32_to_cpu(AT_FILE_NAME))); + record->bytes_in_use = cpu_to_le32(used); + record->link_count = cpu_to_le16(0); + return (cnt ? 0 : 1); +} + +static int rebuildname(const INDEX_ENTRY *index) +{ + ATTR_RECORD *attr; + int headlth; + int datalth; + + datalth = le16_to_cpu(index->length) + - offsetof(INDEX_ENTRY,key.file_name); + headlth = offsetof(ATTR_RECORD,resident_end); + attr = (ATTR_RECORD*)malloc(headlth + datalth); + if (attr) { + attr->type = AT_FILE_NAME; + attr->length = cpu_to_le32(headlth + datalth); + attr->non_resident = 0; + attr->name_length = 0; + attr->name_offset = const_cpu_to_le16(0); + attr->flags = const_cpu_to_le16(0); + attr->instance = const_cpu_to_le16(0); + attr->value_length = cpu_to_le32( + 2*index->key.file_name.file_name_length + + offsetof(FILE_NAME_ATTR, file_name)); + attr->value_offset = cpu_to_le16(headlth); + attr->resident_flags = RESIDENT_ATTR_IS_INDEXED; + memcpy(attr->resident_end, &index->key.file_name, datalth); + free(attr); + } + return (0); +} + +/* + * Controversial creation of an index allocation attribute + * + * This is useful for turning the clock backward, but cannot + * work properly in the general case and must not be used for + * a real sync. + * The main problem is to synchronize the file names when an + * inode is reused with a different name. + */ + +static int insert_index_allocation(ntfs_volume *vol, char *buffer, u32 offs) +{ + MFT_RECORD *record; + ATTR_RECORD *attr; + u32 used; + u32 pos; + u32 xsize; + u16 instance; + int length; + int addedlength; + int namelength; + int err; + static const unsigned char bitmap[] = + { 1, 0, 0, 0, 0, 0, 0, 0 } ; + + err = 1; + if (opts) { + printf("** Call to unsupported insert_index_allocation()\n"); + } else { + record = (MFT_RECORD*)buffer; + pos = le16_to_cpu(record->attrs_offset); + used = le32_to_cpu(record->bytes_in_use); + attr = (ATTR_RECORD*)&buffer[pos]; + while ((pos < used) + && (le32_to_cpu(attr->type) < le32_to_cpu(AT_INDEX_ROOT))) { + pos += le32_to_cpu(attr->length); + attr = (ATTR_RECORD*)&buffer[pos]; + } + length = le32_to_cpu(attr->length); + addedlength = length - 8 /* index allocation */ + + length - 48; /* bitmap */ + if ((attr->type == AT_INDEX_ROOT) + && ((pos + length) == offs) + && ((used + addedlength) < mftrecsz)) { + /* Make space for the attribute */ + memmove(buffer + offs + addedlength, buffer + offs, + mftrecsz - offs - addedlength); + record->bytes_in_use = cpu_to_le32(used + addedlength); + /* + * Insert an AT_INDEX_ALLOCATION + */ + attr = (ATTR_RECORD*)&buffer[offs]; + attr->type = AT_INDEX_ALLOCATION; + attr->length = cpu_to_le32(length - 8); + attr->non_resident = 1; + namelength = buffer[pos + 9] & 255; + attr->name_length = namelength; + attr->name_offset = const_cpu_to_le16(0x40); + memcpy(buffer + offs + 0x40, buffer + pos + 0x18, + 2*namelength); + attr->flags = const_cpu_to_le16(0); + /* Should we really take a new instance ? */ + attr->instance = record->next_attr_instance; + instance = le16_to_cpu(record->next_attr_instance) + 1; + record->next_attr_instance = cpu_to_le16(instance); + attr->lowest_vcn = const_cpu_to_le64(0); + attr->highest_vcn = const_cpu_to_le64(0); + attr->mapping_pairs_offset = cpu_to_le16( + 2*namelength + 0x40); + attr->compression_unit = 0; + xsize = vol->indx_record_size; + attr->allocated_size = cpu_to_le64(xsize); + attr->data_size = attr->allocated_size; + attr->initialized_size = attr->allocated_size; + /* + * Insert an AT_INDEX_BITMAP + */ + attr = (ATTR_RECORD*)&buffer[offs + length - 8]; + attr->type = AT_BITMAP; + attr->length = cpu_to_le32(length - 48); + attr->non_resident = 0; + namelength = buffer[pos + 9] & 255; + attr->name_length = namelength; + attr->name_offset = const_cpu_to_le16(0x18); + memcpy(buffer + offs + length - 8 + 0x18, + buffer + pos + 0x18, 2*namelength); + attr->flags = const_cpu_to_le16(0); + attr->value_length = const_cpu_to_le32(8); + attr->value_offset = cpu_to_le16(2*namelength + 24); + attr->resident_flags = 0; + memcpy((char*)attr->resident_end + 2*namelength, + bitmap, 8); + /* Should we really take a new instance ? */ + attr->instance = record->next_attr_instance; + instance = le16_to_cpu(record->next_attr_instance) + 1; + record->next_attr_instance = cpu_to_le16(instance); + err = sanity_mft(buffer); + } else { + printf("** index root does not match\n"); + err = 1; + } + } + return (err); +} + +/* + * Check whether a full MFT record is fed by an action + * + * If so, checking the validity of existing record is pointless + */ + +static BOOL check_full_mft(const struct ACTION_RECORD *action, BOOL redoing) +{ + const MFT_RECORD *record; + const ATTR_RECORD *attr; + u32 length; + u32 k; + BOOL ok; + + if (redoing) { + record = (const MFT_RECORD*)((const char*)&action->record + + get_redo_offset(&action->record)); + length = le16_to_cpu(action->record.redo_length); + } else { + record = (const MFT_RECORD*)((const char*)&action->record + + get_undo_offset(&action->record)); + length = le16_to_cpu(action->record.undo_length); + } + /* The length in use must be fed */ + ok = !action->record.record_offset + && !action->record.attribute_offset + && (record->magic == magic_FILE) + && (length <= mftrecsz) + && (length >= (offsetof(MFT_RECORD, bytes_in_use) + + sizeof(record->bytes_in_use))); + if (ok) { + k = le16_to_cpu(record->attrs_offset); + attr = (const ATTR_RECORD*)((const char*)record + k); + while (((k + sizeof(attr->type)) <= length) + && (attr->type != AT_END) + && valid_type(attr->type)) { + k += le32_to_cpu(attr->length); + attr = (const ATTR_RECORD*)((const char*)record + k); + } + /* AT_END must be present */ + ok = ((k + sizeof(attr->type)) <= length) + && (attr->type == AT_END); + } + return (ok); +} + +/* + * Check whether a full index block is fed by the log record + * + * If so, checking the validity of existing record is pointless + */ + +static BOOL check_full_index(const struct ACTION_RECORD *action, BOOL redoing) +{ + const INDEX_BLOCK *indx; + u32 length; + + if (redoing) { + indx = (const INDEX_BLOCK*)((const char*)&action->record + + get_redo_offset(&action->record)); + length = le16_to_cpu(action->record.redo_length); + } else { + indx = (const INDEX_BLOCK*)((const char*)&action->record + + get_undo_offset(&action->record)); + length = le16_to_cpu(action->record.undo_length); + } + /* the index length must be fed, so must be the full index block */ + return (!action->record.record_offset + && !action->record.attribute_offset + && (indx->magic == magic_INDX) + && (length >= (offsetof(INDEX_BLOCK, index.index_length) + 4)) + && (length >= (le32_to_cpu(indx->index.index_length) + 24))); +} + +/* + * Create an index block for undoing its deletion + * + * This is useful for turning the clock backward, but cannot + * work properly in the general case and must not be used for + * a real sync. + */ + +static int create_indx(ntfs_volume *vol, const struct ACTION_RECORD *action, + char *buffer) +{ + INDEX_BLOCK *indx; + INDEX_ENTRY_HEADER *ixhead; + INDEX_ENTRY *ixentry; + VCN vcn; + int err; + + if (opts) { + printf("** Call to unsupported create_indx()\n"); + err = 1; + } else { + err = 0; + indx = (INDEX_BLOCK*)buffer; + indx->magic = magic_INDX; +// TODO compute properly + indx->usa_ofs = const_cpu_to_le16(0x28); + indx->usa_count = const_cpu_to_le16(9); + indx->lsn = action->record.this_lsn; + vcn = le32_to_cpu(action->record.target_vcn); + /* beware of size change on big-endian cpus */ + indx->index_block_vcn = cpu_to_le64(vcn); + /* INDEX_HEADER */ + indx->index.entries_offset = const_cpu_to_le32(0x28); + indx->index.index_length = const_cpu_to_le32(0x38); + indx->index.allocated_size = + cpu_to_le32(vol->indx_record_size - 24); + indx->index.ih_flags = 0; + /* INDEX_ENTRY_HEADER */ + ixhead = (INDEX_ENTRY_HEADER*)(buffer + 0x28); + ixhead->length = cpu_to_le16(vol->indx_record_size - 24); + /* terminating INDEX_ENTRY */ + ixentry = (INDEX_ENTRY*)(buffer + 0x40); + ixentry->indexed_file = const_cpu_to_le64(0); + ixentry->length = const_cpu_to_le16(16); + ixentry->key_length = const_cpu_to_le16(0); + ixentry->ie_flags = INDEX_ENTRY_END; + } + return (err); +} + +static int redo_action37(ntfs_volume *vol, const struct ACTION_RECORD *action, + char *buffer) +{ + u32 target; + u32 length; + int err; + + if (optv > 1) + printf("-> %s()\n",__func__); + err = 1; + length = le16_to_cpu(action->record.redo_length); + target = le16_to_cpu(action->record.record_offset) + + le16_to_cpu(action->record.attribute_offset); + if (optv > 1) { + printf("existing data :\n"); + dump(buffer + target,length); + } + if ((target + length) == mftrecsz) { + memset(buffer + target, 0, length); + err = write_protected(vol, &action->record, + buffer, mftrecsz); + if (optv > 1) { + printf("-> MFT record trimmed\n"); + } + } else { + printf("** Bad action-37, inode %lld record :\n", + (long long)inode_number(&action->record)); + printf("target %d length %d sum %d\n", + (int)target,(int)length,(int)(target + length)); + dump(buffer,mftrecsz); + } + err = 0; + return (err); +} + +static int redo_add_index(ntfs_volume *vol, const struct ACTION_RECORD *action, + char *buffer) +{ + LCN lcn; + const char *data; + INDEX_BLOCK *indx; + u32 target; + u32 length; + u32 xsize; + u32 indexlth; + int err; + BOOL found; + + if (optv > 1) + printf("-> %s()\n",__func__); + err = 1; + data = ((const char*)&action->record) + + get_redo_offset(&action->record); + length = le16_to_cpu(action->record.redo_length); + target = le16_to_cpu(action->record.record_offset) + + le16_to_cpu(action->record.attribute_offset); + if (optv > 1) { + lcn = le64_to_cpu(action->record.lcn_list[0]); + printf("-> lcn 0x%llx target 0x%x length %d\n", + (long long)lcn, (int)target, (int)length); + } + xsize = vol->indx_record_size; + indx = (INDEX_BLOCK*)(buffer + + le16_to_cpu(action->record.record_offset)); + if (optv > 1) { + printf("-> existing record :\n"); + dump(&buffer[target], length); + } + if ((indx->magic == magic_INDX) + && !(length & 7) + && ((target + length) <= xsize)) { + /* This has to be an idempotent action */ + found = !memcmp(buffer + target, data, length); + err = 0; + if (!found) { + /* Make space to insert the entry */ + memmove(buffer + target + length, + buffer + target, + xsize - target - length); + memcpy(buffer + target, data, length); + indexlth = le32_to_cpu(indx->index.index_length) + + length; + indx->index.index_length = cpu_to_le32(indexlth); + if (optv > 1) { + printf("-> inserted record :\n"); + dump(&buffer[target], length); + } + err = write_protected(vol, &action->record, + buffer, xsize); + } + if (optv > 1) { + printf("-> INDX record %s\n", + (found ? "unchanged" : "inserted")); + } + } + return (err); +} + +static int redo_add_root_index(ntfs_volume *vol, + const struct ACTION_RECORD *action, char *buffer) +{ + LCN lcn; + const char *data; + ATTR_RECORD *attr; + MFT_RECORD *entry; + INDEX_ROOT *index; + u32 target; + u32 length; + int err; + BOOL found; + + if (optv > 1) + printf("-> %s()\n",__func__); + err = 1; + data = ((const char*)&action->record) + + get_redo_offset(&action->record); + length = le16_to_cpu(action->record.redo_length); + target = le16_to_cpu(action->record.record_offset) + + le16_to_cpu(action->record.attribute_offset); + if (optv > 1) { + lcn = le64_to_cpu(action->record.lcn_list[0]); + printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n", + (long long)inode_number(&action->record), + (long long)lcn, (int)target, (int)length); + } + entry = (MFT_RECORD*)buffer; + attr = (ATTR_RECORD*)(buffer + + le16_to_cpu(action->record.record_offset)); + index = (INDEX_ROOT*)(((char*)attr) + + le16_to_cpu(attr->value_offset)); + if (optv > 1) { + printf("existing index :\n"); + dump(buffer + target,length); + } + if ((attr->type == AT_INDEX_ROOT) + && !(length & 7) + && ((target + length) <= mftrecsz)) { + /* This has to be an idempotent action */ + found = !memcmp(buffer + target, data, length); + err = 0; + if (!found) { + /* Make space to insert the entry */ + memmove(buffer + target + length, + buffer + target, + mftrecsz - target - length); + memcpy(buffer + target, data, length); + resize_attribute(entry, attr, index, length, length); + if (optv > 1) { + printf("new index at same location :\n"); + dump(buffer + target, length); + } + err = write_protected(vol, &action->record, + buffer, mftrecsz); + } + if (optv > 1) { + printf("-> MFT record %s\n", + (found ? "unchanged" : "expanded")); + } + } + return (err); +} + +static int redo_compensate(ntfs_volume *vol __attribute__((unused)), + const struct ACTION_RECORD *action, + char *buffer __attribute__((unused))) +{ + u64 lsn; + s64 diff; + + if (optv > 1) + printf("-> %s()\n",__func__); + lsn = le64_to_cpu(action->record.this_lsn); + diff = lsn - restart_lsn; + if (diff > 0) + restart_lsn = lsn; + return (0); +} + +static int redo_create_file(ntfs_volume *vol, + const struct ACTION_RECORD *action, char *buffer) +{ + LCN lcn; + const char *data; + MFT_RECORD *record; + const MFT_RECORD *fullrec; + u32 target; + u32 length; + int err; + int changed; + + if (optv > 1) + printf("-> %s()\n",__func__); + err = 1; + data = ((const char*)&action->record) + + get_redo_offset(&action->record); + length = le16_to_cpu(action->record.redo_length); + target = le16_to_cpu(action->record.record_offset) + + le16_to_cpu(action->record.attribute_offset); + if (optv > 1) { + lcn = le64_to_cpu(action->record.lcn_list[0]); + printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n", + (long long)inode_number(&action->record), + (long long)lcn, (int)target, (int)length); + } + record = (MFT_RECORD*)buffer; + if (optv > 1) { + printf("-> existing record :\n"); + dump(buffer,mftrecsz); + } + if ((target + length) <= mftrecsz) { + changed = memcmp(buffer + target, data, length); + err = 0; + if (changed || !(record->flags & MFT_RECORD_IN_USE)) { + memcpy(buffer + target, data, length); + record->flags |= MFT_RECORD_IN_USE; + if (optv > 1) { + printf("-> new record :\n"); + dump(buffer,mftrecsz); + } + err = write_protected(vol, &action->record, + buffer, mftrecsz); + } + if (optv > 1) { + printf("-> MFT record %s\n", + (changed ? "updated" : "unchanged")); + } + } else { +// TODO make sure this was caused by bad fixups + /* + * Could not read protected, assume newly allocated record. + * Check we are creating a full MFT record, and so + * existing data is meaningless. + */ + fullrec = (const MFT_RECORD*)data; + if ((length > offsetof(MFT_RECORD, bytes_in_use)) + && (le32_to_cpu(fullrec->bytes_in_use) <= length) + && (fullrec->magic == magic_FILE) + && !target + && (length <= mftrecsz)) { + buffer = (char*)malloc(mftrecsz); + memcpy(buffer, data, length); + if (optv > 1) { + printf("-> created MFT record :\n"); + dump(buffer, length); + } + err = write_protected(vol, &action->record, + buffer, mftrecsz); + } + } + return (err); +} + +static int redo_create_attribute(ntfs_volume *vol, + const struct ACTION_RECORD *action, char *buffer) +{ + const char *data; + u32 target; + u32 length; + int err; + + if (optv > 1) + printf("-> %s()\n",__func__); + err = 1; + data = ((const char*)&action->record) + + get_redo_offset(&action->record); + length = le16_to_cpu(action->record.redo_length); + target = le16_to_cpu(action->record.record_offset) + + le16_to_cpu(action->record.attribute_offset); +// Could also be AT_DATA or AT_INDEX_ALLOCATION + if (!action->record.undo_length) + err = insert_resident(vol, action, buffer, data, + target, length); + return (err); +} + +static int redo_delete_attribute(ntfs_volume *vol, + const struct ACTION_RECORD *action, char *buffer) +{ + const char *data; + u32 target; + u32 length; + int err; + + if (optv > 1) + printf("-> %s()\n",__func__); + err = 1; + data = ((const char*)&action->record) + + get_undo_offset(&action->record); + length = le16_to_cpu(action->record.undo_length); + target = le16_to_cpu(action->record.record_offset) + + le16_to_cpu(action->record.attribute_offset); + if (!action->record.redo_length) + err = remove_resident(vol, action, buffer, data, + target, length); + return (err); +} + +static int redo_delete_file(ntfs_volume *vol, + const struct ACTION_RECORD *action, char *buffer) +{ + LCN lcn; + const char *data; + MFT_RECORD *record; + u32 target; + u32 length; + int err; + int changed; + + if (optv > 1) + printf("-> %s()\n",__func__); + err = 1; + data = ((const char*)&action->record) + + get_undo_offset(&action->record); + length = le16_to_cpu(action->record.undo_length); + target = le16_to_cpu(action->record.record_offset) + + le16_to_cpu(action->record.attribute_offset); + if (optv > 1) { + lcn = le64_to_cpu(action->record.lcn_list[0]); + printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n", + (long long)inode_number(&action->record), + (long long)lcn, (int)target, (int)length); + } + if (optv > 1) { + printf("-> existing record :\n"); + dump(buffer,mftrecsz); + } + record = (MFT_RECORD*)buffer; + if ((target + length) <= mftrecsz) { + /* write a void mft entry (needed ?) */ + changed = memcmp(buffer + target, data, length) + || (record->flags & MFT_RECORD_IN_USE); + err = 0; + if (changed) { + memcpy(buffer + target, data, length); + record->flags &= ~MFT_RECORD_IN_USE; + if (optv > 1) { + printf("-> new record :\n"); + dump(buffer,mftrecsz); + } + err = write_protected(vol, &action->record, + buffer, mftrecsz); + } + if (optv > 1) { + printf("-> MFT record %s\n", + (changed ? "updated" : "unchanged")); + } + } + return (err); +} + +static int redo_delete_index(ntfs_volume *vol, + const struct ACTION_RECORD *action, char *buffer) +{ + LCN lcn; + const char *data; + INDEX_BLOCK *indx; + u32 target; + u32 length; + u32 xsize; + u32 indexlth; + int err; + BOOL found; + + if (optv > 1) + printf("-> %s()\n",__func__); + err = 1; + data = ((const char*)&action->record) + + get_undo_offset(&action->record); + length = le16_to_cpu(action->record.undo_length); +// TODO merge with undo_add_index ? + target = le16_to_cpu(action->record.record_offset) + + le16_to_cpu(action->record.attribute_offset); + if (optv > 1) { + lcn = le64_to_cpu(action->record.lcn_list[0]); + printf("-> lcn 0x%llx target 0x%x length %d\n", + (long long)lcn, (int)target, (int)length); + } + xsize = vol->indx_record_size; + indx = (INDEX_BLOCK*)(buffer + + le16_to_cpu(action->record.record_offset)); + if (optv > 1) { + printf("-> existing record :\n"); + dump(&buffer[target], length); + } + if ((indx->magic == magic_INDX) + && !(length & 7) + && ((target + length) <= xsize)) { + /* This has to be an idempotent action */ + found = !memcmp(buffer + target, data, length); + err = 0; + if (found) { + /* Remove the entry */ + memmove(buffer + target, + buffer + target + length, + xsize - target - length); + indexlth = le32_to_cpu(indx->index.index_length) + - length; + indx->index.index_length = cpu_to_le32(indexlth); + err = write_protected(vol, &action->record, + buffer, xsize); + } + if (optv > 1) { + printf("-> INDX record %s\n", + (found ? "unchanged" : "removed")); + } + } + return (err); +} + +static int redo_delete_root_index(ntfs_volume *vol, + const struct ACTION_RECORD *action, char *buffer) +{ + LCN lcn; + const char *data; + ATTR_RECORD *attr; + MFT_RECORD *entry; + INDEX_ROOT *index; + BOOL found; + u32 target; + u32 length; + int err; + + if (optv > 1) + printf("-> %s()\n",__func__); + err = 1; + data = ((const char*)&action->record) + + get_undo_offset(&action->record); + length = le16_to_cpu(action->record.undo_length); + target = le16_to_cpu(action->record.record_offset) + + le16_to_cpu(action->record.attribute_offset); + + if (optv > 1) { + lcn = le64_to_cpu(action->record.lcn_list[0]); + printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n", + (long long)inode_number(&action->record), + (long long)lcn, (int)target, (int)length); + } + entry = (MFT_RECORD*)buffer; + attr = (ATTR_RECORD*)(buffer + + le16_to_cpu(action->record.record_offset)); + index = (INDEX_ROOT*)(((char*)attr) + + le16_to_cpu(attr->value_offset)); + if (optv > 1) { + printf("existing index :\n"); + dump(buffer + target,length); + } + if ((attr->type == AT_INDEX_ROOT) + && !(length & 7) + && ((target + length) <= mftrecsz)) { + /* This has to be an idempotent action */ + found = !memcmp(buffer + target, data, length); + err = 0; + /* Only delete if present */ + if (found) { + /* Remove the entry */ + memmove(buffer + target, + buffer + target + length, + mftrecsz - target - length); + resize_attribute(entry, attr, index, -length, -length); + if (optv > 1) { + printf("new index at same location :\n"); + dump(buffer + target, length); + } + err = write_protected(vol, &action->record, + buffer, mftrecsz); + } + if (optv > 1) { + printf("-> MFT record %s\n", + (found ? "shrinked" : "updated")); + } + } + return (err); +} + +static int redo_force_bits(ntfs_volume *vol, + const struct ACTION_RECORD *action, char *buffer) +{ + LCN lcn; + const struct BITMAP_ACTION *data; + u32 i; + int err; + int wanted; + u32 firstbit; + u32 count; + + if (optv > 1) + printf("-> %s()\n",__func__); + err = 1; + data = (const struct BITMAP_ACTION*) + (((const char*)&action->record) + + get_redo_offset(&action->record)); + firstbit = le32_to_cpu(data->firstbit); + count = le32_to_cpu(data->count); + if (action->record.redo_operation + == const_cpu_to_le16(SetBitsInNonResidentBitMap)) + wanted = 1; + else + wanted = 0; +// TODO consistency undo_offset == redo_offset, etc. +// firstbit + count < 8*clustersz (multiple clusters possible ?) + if (optv > 1) { + lcn = le64_to_cpu(action->record.lcn_list[0]); + printf("-> lcn 0x%llx firstbit %d count %d wanted %d\n", + (long long)lcn,(int)firstbit,(int)count,(int)wanted); + } + for (i=0; irecord, buffer)) { + err = 0; + if (optv > 1) + printf("-> record updated\n"); + } + if (err) + printf("** redo_clearbits failed\n"); + return (err); +} + +static int redo_open_attribute(ntfs_volume *vol __attribute__((unused)), + const struct ACTION_RECORD *action) +{ + const char *data; + struct ATTR *pa; + const struct ATTR_OLD *attr_old; + const struct ATTR_NEW *attr_new; + const char *name; + le64 inode; + u32 namelen; + u32 length; + u32 extra; + int err; + + if (optv > 1) + printf("-> %s()\n",__func__); + err = 1; + data = ((const char*)&action->record) + + get_redo_offset(&action->record); + length = le16_to_cpu(action->record.redo_length); + extra = get_extra_offset(&action->record); + if (action->record.undo_length) { + name = ((const char*)&action->record) + extra; + namelen = le32_to_cpu(action->record.client_data_length) + + LOG_RECORD_HEAD_SZ - extra; + /* fix namelen which was aligned modulo 8 */ + namelen = fixnamelen(name, namelen); + if (optv > 1) { + printf("-> length %d namelen %d",(int)length, + (int)namelen); + showname(", ", name, namelen/2); + } + } else { + name = ""; + namelen = 0; + } + pa = getattrentry(le16_to_cpu(action->record.target_attribute),0); + if (pa) { + if (optv) { + /* + * If the actions have been displayed, the + * attribute has already been fed. Check + * whether it matches what we have in store. + */ + switch (length) { + case sizeof(struct ATTR_OLD) : + attr_old = (const struct ATTR_OLD*)data; + /* Badly aligned */ + memcpy(&inode, &attr_old->inode, 8); + err = (MREF(le64_to_cpu(inode)) != pa->inode) + || (attr_old->type != pa->type); + break; + case sizeof(struct ATTR_NEW) : + attr_new = (const struct ATTR_NEW*)data; + err = (MREF(le64_to_cpu(attr_new->inode)) + != pa->inode) + || (attr_new->type != pa->type); + break; + default : err = 1; + } + if (!err) { + err = (namelen != pa->namelen) + || (namelen + && memcmp(name, pa->name, namelen)); + } + if (optv > 1) + printf("-> attribute %s the recorded one\n", + (err ? "does not match" : "matches")); + } else { + copy_attribute(pa, data, length); + pa->namelen = namelen; + if (namelen) + memcpy(pa->name, data, namelen); + err = 0; + } + } else + if (optv) + printf("* Unrecorded attribute\n"); + return (err); +} + +static int redo_sizes(ntfs_volume *vol, const struct ACTION_RECORD *action, + char *buffer) +{ + const char *data; + u32 target; + u32 length; + int err; + + if (optv > 1) + printf("-> %s()\n",__func__); + err = 1; + data = ((const char*)&action->record) + + get_redo_offset(&action->record); + length = le16_to_cpu(action->record.redo_length); + target = le16_to_cpu(action->record.record_offset) + + le16_to_cpu(action->record.attribute_offset) + + offsetof(ATTR_RECORD, allocated_size); + err = change_resident(vol, action, buffer, + data, target, length); + return (err); +} + +static int redo_update_index(ntfs_volume *vol, + const struct ACTION_RECORD *action, char *buffer) +{ + const char *data; + u32 target; + u32 length; + int err; + + if (optv > 1) + printf("-> %s()\n",__func__); + err = 1; + data = ((const char*)&action->record) + + get_redo_offset(&action->record); + length = le16_to_cpu(action->record.redo_length); + /* target is left-justified to creation time */ + target = le16_to_cpu(action->record.record_offset) + + le16_to_cpu(action->record.attribute_offset) + + offsetof(INDEX_ENTRY, key.file_name.creation_time); + err = update_index(vol, action, buffer, data, target, length); + return (err); +} + +static int redo_update_index_value(ntfs_volume *vol, + const struct ACTION_RECORD *action, char *buffer) +{ + const char *data; + u32 length; + u32 target; + int err; + + if (optv > 1) + printf("-> %s()\n",__func__); + err = 1; + data = ((const char*)&action->record) + + get_redo_offset(&action->record); + length = le16_to_cpu(action->record.redo_length); + target = le16_to_cpu(action->record.record_offset) + + le16_to_cpu(action->record.attribute_offset); + err = change_index_value(vol, action, buffer, data, target, length); + return (err); +} + +static int redo_update_mapping(ntfs_volume *vol, + const struct ACTION_RECORD *action, char *buffer) +{ + LCN lcn; + const char *data; + ATTR_RECORD *attr; + MFT_RECORD *entry; + u32 target; + u32 length; + u32 source; + u32 alen; + u32 newused; + int resize; + int err; + int changed; + + if (optv > 1) + printf("-> %s()\n",__func__); + err = 1; + data = ((const char*)&action->record) + + get_redo_offset(&action->record); + length = le16_to_cpu(action->record.redo_length); + resize = length - le16_to_cpu(action->record.undo_length); + target = le16_to_cpu(action->record.record_offset) + + le16_to_cpu(action->record.attribute_offset); + if (optv > 1) { + lcn = le64_to_cpu(action->record.lcn_list[0]); + printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n", + (long long)inode_number(&action->record), + (long long)lcn, (int)target, (int)length); + } + if (optv > 1) { + printf("-> existing record :\n"); + dump(&buffer[target], length); + } + entry = (MFT_RECORD*)buffer; + attr = (ATTR_RECORD*)(buffer + + le16_to_cpu(action->record.record_offset)); + if (!attr->non_resident) { + printf("** Error : update_mapping on resident attr\n"); + } + if (valid_type(attr->type) + && attr->non_resident + && !(resize & 7) + && ((target + length) <= mftrecsz)) { + changed = memcmp(buffer + target, data, length); + err = 0; + if (changed) { + /* Adjust space for new mapping pairs */ + source = target - resize; + if (resize > 0) { + memmove(buffer + target + length, + buffer + source + length, + mftrecsz - target - length); + } + if (resize < 0) { + memmove(buffer + target + length, + buffer + source + length, + mftrecsz - source - length); + } + memcpy(buffer + target, data, length); + /* Resize the attribute */ + alen = le32_to_cpu(attr->length) + resize; + attr->length = cpu_to_le32(alen); + /* Resize the mft record */ + newused = le32_to_cpu(entry->bytes_in_use) + + resize; + entry->bytes_in_use = cpu_to_le32(newused); + /* Compute the new highest_vcn */ + err = adjust_high_vcn(vol, attr); + if (optv > 1) { + printf("-> new record :\n"); + dump(buffer + target, length); + } + if (!err) { + err = write_protected(vol, + &action->record, + buffer, mftrecsz); + } + } + if (optv > 1) { + printf("-> MFT record %s\n", + (changed ? "updated" : "unchanged")); + } + } + return (err); +} + +static int redo_update_resident(ntfs_volume *vol, + const struct ACTION_RECORD *action, char *buffer) +{ + LCN lcn; + const char *data; + u32 target; + u32 length; + u32 oldlength; + u32 end; + u32 redo; + int err; + int changed; + + if (optv > 1) + printf("-> %s()\n",__func__); + err = 1; + end = le32_to_cpu(action->record.client_data_length) + + LOG_RECORD_HEAD_SZ; + length = le16_to_cpu(action->record.redo_length); + redo = get_redo_offset(&action->record); + if ((redo + length) > end) + data = (char*)NULL; + else + data = ((const char*)&action->record) + redo; + oldlength = le16_to_cpu(action->record.undo_length); + target = le16_to_cpu(action->record.record_offset) + + le16_to_cpu(action->record.attribute_offset); + if (length == oldlength) { + if (optv > 1) { + lcn = le64_to_cpu(action->record.lcn_list[0]); + printf("-> inode %lld lcn 0x%llx target 0x%x" + " length %d\n", + (long long)inode_number(&action->record), + (long long)lcn, (int)target, (int)length); + } + if (optv > 1) { + printf("-> existing record :\n"); + dump(&buffer[target], length); + } + if ((target + length) <= mftrecsz) { + changed = memcmp(buffer + target, data, length); + err = 0; + if (changed) { + memcpy(buffer + target, data, length); + if (optv > 1) { + printf("-> new record :\n"); + dump(buffer + target, length); + } + err = write_protected(vol, &action->record, + buffer, mftrecsz); + } + if (optv > 1) { + printf("-> MFT record %s\n", + (changed ? "updated" : "unchanged")); + } + } + } else { + if (length > oldlength) + err = expand_resident(vol, action, buffer, data, + target, length, oldlength); + else + err = shrink_resident(vol, action, buffer, data, + target, length, oldlength); + } + return (err); +} + +static int redo_update_root_index(ntfs_volume *vol, + const struct ACTION_RECORD *action, char *buffer) +{ + const char *data; + const char *expected; + u32 target; + u32 length; + int err; + + if (optv > 1) + printf("-> %s()\n",__func__); + err = 1; + data = ((const char*)&action->record) + + get_redo_offset(&action->record); + expected = ((const char*)&action->record) + + get_undo_offset(&action->record); + length = le16_to_cpu(action->record.redo_length); + /* the fixup is right-justified to the name length */ + target = le16_to_cpu(action->record.record_offset) + + le16_to_cpu(action->record.attribute_offset) + + offsetof(INDEX_ENTRY, key.file_name.file_name_length) + - length; + err = change_resident_expect(vol, action, buffer, data, expected, + target, length, AT_INDEX_ROOT); + return (err); +} + +static int redo_update_root_vcn(ntfs_volume *vol, + const struct ACTION_RECORD *action, char *buffer) +{ + const char *data; + const char *expected; + u32 target; + u32 length; + int err; + + if (optv > 1) + printf("-> %s()\n",__func__); + err = 1; + data = ((const char*)&action->record) + + get_redo_offset(&action->record); + expected = ((const char*)&action->record) + + get_undo_offset(&action->record); + length = le16_to_cpu(action->record.redo_length); +// length must be 8 + target = le16_to_cpu(action->record.record_offset) + + le16_to_cpu(action->record.attribute_offset) ++ 16; // explanation needed (right justified ?) + err = change_resident_expect(vol, action, buffer, data, expected, + target, length, AT_INDEX_ROOT); + return (err); +} + +static int redo_update_value(ntfs_volume *vol, + const struct ACTION_RECORD *action, char *buffer) +{ + LCN lcn; + const char *data; + u32 length; + u32 target; + u32 count; + u32 redo; + u32 end; + u32 i; + int changed; + int err; + + if (optv > 1) + printf("-> %s()\n",__func__); + err = 1; + length = le16_to_cpu(action->record.redo_length); + redo = get_redo_offset(&action->record); + end = le32_to_cpu(action->record.client_data_length) + + LOG_RECORD_HEAD_SZ; + /* sometimes there is no redo data */ + if ((redo + length) > end) + data = (char*)NULL; + else + data = ((const char*)&action->record) + redo; + target = le16_to_cpu(action->record.record_offset) + + le16_to_cpu(action->record.attribute_offset); + count = le16_to_cpu(action->record.lcns_to_follow); + if (optv > 1) { + lcn = le64_to_cpu(action->record.lcn_list[0]); + printf("-> lcn 0x%llx target 0x%x length %d\n", + (long long)lcn, (int)target, (int)length); + } + if (optv > 1) { + printf("-> existing record :\n"); + dump(&buffer[target], length); + } + if ((target + length) <= (count << clusterbits)) { + if (data) + changed = memcmp(buffer + target, data, length); + else { + for (i=0; (i 1) { + printf("-> new record :\n"); + dump(buffer + target, length); + } + err = write_raw(vol, &action->record, buffer); + } + if (optv > 1) { + printf("-> data record %s\n", + (changed ? "updated" : "unchanged")); + } + } + + return (err); +} + +static int redo_update_vcn(ntfs_volume *vol, + const struct ACTION_RECORD *action, char *buffer) +{ + const char *data; + u32 target; + u32 length; + int err; + + if (optv > 1) + printf("-> %s()\n",__func__); + err = 1; + data = ((const char*)&action->record) + + get_redo_offset(&action->record); + length = le16_to_cpu(action->record.redo_length); + /* target is left-justified to creation time */ + target = le16_to_cpu(action->record.record_offset) + + le16_to_cpu(action->record.attribute_offset) + + 16; // to better describe + err = update_index(vol, action, buffer, data, target, length); + return (err); +} + +static int redo_write_end(ntfs_volume *vol, + const struct ACTION_RECORD *action, char *buffer) +{ + LCN lcn; + const char *data; + u32 target; + u32 length; + u32 oldlength; + u32 end; + u32 redo; + int err; + int changed; + + if (optv > 1) + printf("-> %s()\n",__func__); + err = 1; + end = le32_to_cpu(action->record.client_data_length) + + LOG_RECORD_HEAD_SZ; + length = le16_to_cpu(action->record.redo_length); + redo = get_redo_offset(&action->record); + if ((redo + length) > end) + data = (char*)NULL; + else + data = ((const char*)&action->record) + redo; + oldlength = le16_to_cpu(action->record.undo_length); + target = le16_to_cpu(action->record.record_offset) + + le16_to_cpu(action->record.attribute_offset); + if (length == oldlength) { + if (optv > 1) { + lcn = le64_to_cpu(action->record.lcn_list[0]); + printf("-> inode %lld lcn 0x%llx target 0x%x" + " length %d\n", + (long long)inode_number(&action->record), + (long long)lcn, (int)target, (int)length); + } + if (optv > 1) { + printf("-> existing record :\n"); + dump(&buffer[target], length); + } + if ((target + length) <= mftrecsz) { + changed = memcmp(buffer + target, data, length); + err = 0; + if (changed) { + memcpy(buffer + target, data, length); + if (optv > 1) { + printf("-> new record :\n"); + dump(buffer + target, length); + } + err = write_protected(vol, &action->record, + buffer, mftrecsz); + } + if (optv > 1) { + printf("-> MFT record %s\n", + (changed ? "updated" : "unchanged")); + } + } + } else { + if (length > oldlength) + err = add_resident(vol, action, buffer, data, + target, length, oldlength); + else + err = delete_resident(vol, action, buffer, data, + target, length, oldlength); + } + return (err); +} + +static int redo_write_index(ntfs_volume *vol, + const struct ACTION_RECORD *action, char *buffer) +{ + LCN lcn; + const char *data; + INDEX_BLOCK *indx; + u32 target; + u32 length; + u32 xsize; + int err; + int changed; + + if (optv > 1) + printf("-> %s()\n",__func__); + err = 1; + data = ((const char*)&action->record) + + get_redo_offset(&action->record); + length = le16_to_cpu(action->record.redo_length); + /* target is left-justified to creation time */ + target = le16_to_cpu(action->record.record_offset) + + le16_to_cpu(action->record.attribute_offset); + if (optv > 1) { + lcn = le64_to_cpu(action->record.lcn_list[0]); + printf("-> lcn 0x%llx target 0x%x length %d\n", + (long long)lcn, (int)target, (int)length); + } + xsize = vol->indx_record_size; + indx = (INDEX_BLOCK*)buffer; + if (action->record.record_offset) { + printf("** Non-null record_offset in redo_write_index()\n"); + } + if (optv > 1) { + printf("-> existing index :\n"); + dump(&buffer[target], length); + } + if ((indx->magic == magic_INDX) + && !(length & 7) + && ((target + length) <= xsize)) { + /* This has to be an idempotent action */ + changed = memcmp(buffer + target, data, length); + err = 0; + if (changed) { + /* Update the entry */ + memcpy(buffer + target, data, length); + /* If truncating, set the new size */ + indx->index.index_length = + cpu_to_le32(target + length - 0x18); + if (optv > 1) { + printf("-> new index :\n"); + dump(&buffer[target], length); + } + err = write_protected(vol, &action->record, + buffer, xsize); + } + if (optv > 1) { + printf("-> INDX record %s\n", + (changed ? "updated" : "unchanged")); + } + } + return (err); +} + +static int undo_action37(ntfs_volume *vol __attribute__((unused)), + const struct ACTION_RECORD *action, + char *buffer __attribute__((unused))) +{ +/* + const char *data; + u32 target; + u32 length; +*/ + int err; + + if (optv > 1) + printf("-> %s()\n",__func__); + err = 1; +/* + data = ((const char*)&action->record) + + get_redo_offset(&action->record); + length = le16_to_cpu(action->record.redo_length); + target = le16_to_cpu(action->record.record_offset) + + le16_to_cpu(action->record.attribute_offset); +*/ + printf("* Ignored action-37, inode %lld record :\n", + (long long)inode_number(&action->record)); + err = 0; + return (err); +} + +static int undo_add_index(ntfs_volume *vol, + const struct ACTION_RECORD *action, char *buffer) +{ + LCN lcn; + const char *data; + INDEX_BLOCK *indx; + u32 target; + u32 length; + u32 xsize; + u32 indexlth; + int err; + BOOL found; + + if (optv > 1) + printf("-> %s()\n",__func__); + err = 1; + data = ((const char*)&action->record) + + get_redo_offset(&action->record); + length = le16_to_cpu(action->record.redo_length); + target = le16_to_cpu(action->record.record_offset) + + le16_to_cpu(action->record.attribute_offset); + if (optv > 1) { + lcn = le64_to_cpu(action->record.lcn_list[0]); + printf("-> lcn 0x%llx target 0x%x length %d\n", + (long long)lcn, (int)target, (int)length); + } + xsize = vol->indx_record_size; + indx = (INDEX_BLOCK*)(buffer + + le16_to_cpu(action->record.record_offset)); + if (optv > 1) { + printf("-> existing record :\n"); + dump(&buffer[target], length); + } + if ((indx->magic == magic_INDX) + && !(length & 7) + && ((target + length) <= xsize)) { + /* This has to be an idempotent action */ + found = index_match_undo(buffer + target, data, length); + err = 0; + if (found) { + /* Remove the entry */ + memmove(buffer + target, + buffer + target + length, + xsize - target - length); + indexlth = le32_to_cpu(indx->index.index_length) + - length; + indx->index.index_length = cpu_to_le32(indexlth); + err = write_protected(vol, &action->record, + buffer, xsize); + } else { + sanity_indx(vol,buffer); + printf("full record :\n"); + dump(buffer,xsize); + } + if (optv > 1) { + printf("-> INDX record %s\n", + (found ? "removed" : "unchanged")); + } + } + return (err); +} + +static int undo_add_root_index(ntfs_volume *vol, + const struct ACTION_RECORD *action, char *buffer) +{ + LCN lcn; + const char *data; + ATTR_RECORD *attr; + MFT_RECORD *entry; + INDEX_ROOT *index; + BOOL found; + u32 target; + u32 length; + int err; + + if (optv > 1) + printf("-> %s()\n",__func__); + err = 1; + data = ((const char*)&action->record) + + get_redo_offset(&action->record); + length = le16_to_cpu(action->record.redo_length); + target = le16_to_cpu(action->record.record_offset) + + le16_to_cpu(action->record.attribute_offset); + if (optv > 1) { + lcn = le64_to_cpu(action->record.lcn_list[0]); + printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n", + (long long)inode_number(&action->record), + (long long)lcn, (int)target, (int)length); + } + entry = (MFT_RECORD*)buffer; + attr = (ATTR_RECORD*)(buffer + + le16_to_cpu(action->record.record_offset)); + index = (INDEX_ROOT*)(((char*)attr) + + le16_to_cpu(attr->value_offset)); + if (optv > 1) { + printf("existing index :\n"); + dump(buffer + target,length); + } + if ((attr->type == AT_INDEX_ROOT) + && !(length & 7) + && ((target + length) <= mftrecsz)) { + /* This has to be an idempotent action */ + found = index_match_undo(buffer + target, data, length); + err = 0; + if (found && !older_record(entry, &action->record)) { + /* Remove the entry */ + memmove(buffer + target, + buffer + target + length, + mftrecsz - target - length); + resize_attribute(entry, attr, index, -length, -length); + if (optv > 1) { + printf("new index at same location :\n"); + dump(buffer + target, length); + } + err = write_protected(vol, &action->record, + buffer, mftrecsz); + } + if (optv > 1) { + printf("-> MFT record %s\n", + (found ? "shrinked" : "unchanged")); + } + } + return (err); +} + +static int undo_create_attribute(ntfs_volume *vol, + const struct ACTION_RECORD *action, char *buffer) +{ + const char *data; + u32 target; + u32 length; + int err; + + if (optv > 1) + printf("-> %s()\n",__func__); + err = 1; + data = ((const char*)&action->record) + + get_redo_offset(&action->record); + length = le16_to_cpu(action->record.redo_length); + target = le16_to_cpu(action->record.record_offset) + + le16_to_cpu(action->record.attribute_offset); + if (!action->record.undo_length) + err = remove_resident(vol, action, buffer, data, + target, length); + return (err); +} + +static int undo_delete_attribute(ntfs_volume *vol, + const struct ACTION_RECORD *action, char *buffer) +{ + const char *data; + u32 target; + u32 length; + int err; + + if (optv > 1) + printf("-> %s()\n",__func__); + err = 1; + data = ((const char*)&action->record) + + get_undo_offset(&action->record); + length = le16_to_cpu(action->record.undo_length); + target = le16_to_cpu(action->record.record_offset) + + le16_to_cpu(action->record.attribute_offset); + if (!action->record.redo_length) + err = insert_resident(vol, action, buffer, data, + target, length); + return (err); +} + +static int undo_delete_index(ntfs_volume *vol, + const struct ACTION_RECORD *action, char *buffer) +{ + LCN lcn; + const char *data; + INDEX_BLOCK *indx; + u32 target; + u32 length; + u32 xsize; + u32 indexlth; + int err; + BOOL found; + +// MERGE with redo_add_root_index() ? + if (optv > 1) + printf("-> %s()\n",__func__); + err = 1; + data = ((const char*)&action->record) + + get_undo_offset(&action->record); + length = le16_to_cpu(action->record.undo_length); + target = le16_to_cpu(action->record.record_offset) + + le16_to_cpu(action->record.attribute_offset); + if (optv > 1) { + lcn = le64_to_cpu(action->record.lcn_list[0]); + printf("-> lcn 0x%llx target 0x%x length %d\n", + (long long)lcn, (int)target, (int)length); + } + xsize = vol->indx_record_size; + indx = (INDEX_BLOCK*)(buffer + + le16_to_cpu(action->record.record_offset)); + if (optv > 1) { + printf("-> existing record :\n"); + dump(&buffer[target], length); + } + if ((indx->magic == magic_INDX) + && !(length & 7) + && ((target + length) <= xsize) + && !sanity_indx(vol,buffer)) { + /* This has to be an idempotent action */ + found = !memcmp(buffer + target, data, length); + err = 0; + if (!found) { + /* Make space to insert the entry */ + memmove(buffer + target + length, + buffer + target, + xsize - target - length); + memcpy(buffer + target, data, length); + indexlth = le32_to_cpu(indx->index.index_length) + + length; + indx->index.index_length = cpu_to_le32(indexlth); + if (optv > 1) { + printf("-> inserted record :\n"); + dump(&buffer[target], length); + } + /* rebuildname() has no effect currently, should drop */ + rebuildname((const INDEX_ENTRY*)data); + err = write_protected(vol, &action->record, + buffer, xsize); + } + if (optv > 1) { + printf("-> INDX record %s\n", + (found ? "unchanged" : "inserted")); + } + } + return (err); +} + +static int undo_delete_root_index(ntfs_volume *vol, + const struct ACTION_RECORD *action, char *buffer) +{ + LCN lcn; + const char *data; + ATTR_RECORD *attr; + MFT_RECORD *entry; + INDEX_ROOT *index; + u32 target; + u32 length; + int err; + BOOL found; + + if (optv > 1) + printf("-> %s()\n",__func__); + err = 1; + data = ((const char*)&action->record) + + get_undo_offset(&action->record); + length = le16_to_cpu(action->record.undo_length); + target = le16_to_cpu(action->record.record_offset) + + le16_to_cpu(action->record.attribute_offset); + if (optv > 1) { + lcn = le64_to_cpu(action->record.lcn_list[0]); + printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n", + (long long)inode_number(&action->record), + (long long)lcn, (int)target, (int)length); + } + entry = (MFT_RECORD*)buffer; + attr = (ATTR_RECORD*)(buffer + + le16_to_cpu(action->record.record_offset)); + index = (INDEX_ROOT*)(((char*)attr) + + le16_to_cpu(attr->value_offset)); + if (attr->type != AT_INDEX_ROOT) { + printf("** Unexpected attr type 0x%lx\n", + (long)le32_to_cpu(attr->type)); + printf("existing mft\n"); + dump((char*)buffer,512); + printf("existing index\n"); + dump(buffer + target,length); + } + if (optv > 1) { + printf("existing index :\n"); + dump(buffer + target,length); + } + if ((attr->type == AT_INDEX_ROOT) + && !(length & 7) + && ((target + length) <= mftrecsz)) { + /* This has to be an idempotent action */ + found = !memcmp(buffer + target, data, length); + err = 0; + /* Do not insert if present */ + if (!found) { + /* Make space to insert the entry */ + memmove(buffer + target + length, + buffer + target, + mftrecsz - target - length); + memcpy(buffer + target, data, length); + resize_attribute(entry, attr, index, length, length); + if (optv > 1) { + printf("new index :\n"); + dump(buffer + target, length); + } + err = write_protected(vol, &action->record, + buffer, mftrecsz); + } + if (optv > 1) { + printf("-> MFT record %s\n", + (found ? "unchanged" : "expanded")); + } + } + return (err); +} + +static int undo_create_file(ntfs_volume *vol, + const struct ACTION_RECORD *action, char *buffer) +{ + LCN lcn; + const char *data; + MFT_RECORD *record; + u32 target; + u32 length; + int err; + int changed; + + if (optv > 1) + printf("-> %s()\n",__func__); + err = 1; + /* redo initialize, clearing the in_use flag ? */ + data = ((const char*)&action->record) + + get_redo_offset(&action->record); + length = le16_to_cpu(action->record.redo_length); + target = le16_to_cpu(action->record.record_offset) + + le16_to_cpu(action->record.attribute_offset); + if (optv > 1) { + lcn = le64_to_cpu(action->record.lcn_list[0]); + printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n", + (long long)inode_number(&action->record), + (long long)lcn, (int)target, (int)length); + } + record = (MFT_RECORD*)buffer; + if (optv > 1) { + printf("-> existing record :\n"); + dump(buffer,mftrecsz); + } + if ((target + length) <= mftrecsz) { + changed = memcmp(buffer + target, data, length); + err = 0; + if (changed || (record->flags & MFT_RECORD_IN_USE)) { + memcpy(buffer + target, data, length); + record->flags &= ~MFT_RECORD_IN_USE; + if (optv > 1) { + printf("-> new record :\n"); + dump(buffer,mftrecsz); + } + err = write_protected(vol, &action->record, + buffer, mftrecsz); + } + if (optv > 1) { + printf("-> MFT record %s\n", + (changed ? "updated" : "unchanged")); + } + } + return (err); +} + +static int undo_delete_file(ntfs_volume *vol, + const struct ACTION_RECORD *action, char *buffer) +{ + LCN lcn; + const char *data; + MFT_RECORD *record; + u32 target; + u32 length; + int err; + int changed; + + if (optv > 1) + printf("-> %s()\n",__func__); + err = 1; + data = ((const char*)&action->record) + + get_undo_offset(&action->record); + length = le16_to_cpu(action->record.undo_length); + target = le16_to_cpu(action->record.record_offset) + + le16_to_cpu(action->record.attribute_offset); + if (optv > 1) { + lcn = le64_to_cpu(action->record.lcn_list[0]); + printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n", + (long long)inode_number(&action->record), + (long long)lcn, (int)target, (int)length); + } + if (optv > 1) { + printf("-> existing record :\n"); + dump(buffer,mftrecsz); + } + record = (MFT_RECORD*)buffer; + if ((target + length) <= mftrecsz) { + changed = memcmp(buffer + target, data, length) + || !(record->flags & MFT_RECORD_IN_USE); + err = 0; + if (changed) { + memcpy(buffer + target, data, length); + /* + * Unclear what we should do for recreating a file. + * Only 24 bytes are available, the used length is not known, + * the number of links suggests we should keep the current + * names... If so, when will they be deleted ? + * We will have to make stamp changes in the standard + * information attribute, so better not to delete it. + * Should we create a data or index attribute ? + * Here, we assume we should delete the file names when + * the record now appears to not be in use and there are + * links. + */ + if (record->link_count + && !(record->flags & MFT_RECORD_IN_USE)) + err = delete_names(buffer); + record->flags |= MFT_RECORD_IN_USE; + if (optv > 1) { + printf("-> new record :\n"); + dump(buffer,mftrecsz); + } + if (!err) + err = write_protected(vol, + &action->record, + buffer, mftrecsz); + } + if (optv > 1) { + printf("-> MFT record %s\n", + (changed ? "updated" : "unchanged")); + } + } + return (err); +} + +static int undo_force_bits(ntfs_volume *vol, + const struct ACTION_RECORD *action, char *buffer) +{ + LCN lcn; + const struct BITMAP_ACTION *data; + u32 i; + int err; + int wanted; + u32 firstbit; + u32 count; + + if (optv > 1) + printf("-> %s()\n",__func__); + err = 1; + data = (const struct BITMAP_ACTION*) + (((const char*)&action->record) + + get_redo_offset(&action->record)); + firstbit = le32_to_cpu(data->firstbit); + count = le32_to_cpu(data->count); + if (action->record.redo_operation + == const_cpu_to_le16(SetBitsInNonResidentBitMap)) + wanted = 0; + else + wanted = 1; +// TODO consistency undo_offset == redo_offset, etc. +// firstbit + count < 8*clustersz (multiple clusters possible ?) + if (optv > 1) { + lcn = le64_to_cpu(action->record.lcn_list[0]); + printf("-> lcn 0x%llx firstbit %d count %d wanted %d\n", + (long long)lcn,(int)firstbit,(int)count,(int)wanted); + } + for (i=0; irecord, buffer)) { + err = 0; + if (optv > 1) + printf("-> record updated\n"); + } + if (err) + printf("** redo_clearbits failed\n"); + return (err); +} + +static int undo_open_attribute(ntfs_volume *vol __attribute__((unused)), + const struct ACTION_RECORD *action) +{ + const char *data; + struct ATTR *pa; + const struct ATTR_OLD *attr_old; + const struct ATTR_NEW *attr_new; + const char *name; + le64 inode; + u32 namelen; + u32 length; + u32 extra; + int err; + + if (optv > 1) + printf("-> %s()\n",__func__); + err = 1; + data = ((const char*)&action->record) + + get_redo_offset(&action->record); + length = le16_to_cpu(action->record.redo_length); + extra = get_extra_offset(&action->record); + if (action->record.undo_length) { + name = ((const char*)&action->record) + extra; + namelen = le32_to_cpu(action->record.client_data_length) + + LOG_RECORD_HEAD_SZ - extra; + /* fix namelen which was aligned modulo 8 */ + namelen = fixnamelen(name, namelen); + if (optv > 1) { + printf("-> length %d namelen %d",(int)length, + (int)namelen); + showname(", ", name, namelen/2); + } + } else { + namelen = 0; + name = ""; + } + pa = getattrentry(le16_to_cpu(action->record.target_attribute),0); +// TODO Only process is attr is not older ? + if (pa) { + /* check whether the redo attr matches what we have in store */ + switch (length) { + case sizeof(struct ATTR_OLD) : + attr_old = (const struct ATTR_OLD*)data; + /* Badly aligned */ + memcpy(&inode, &attr_old->inode, 8); + err = (MREF(le64_to_cpu(inode)) != pa->inode) + || (attr_old->type != pa->type); + break; + case sizeof(struct ATTR_NEW) : + attr_new = (const struct ATTR_NEW*)data; + err = (MREF(le64_to_cpu(attr_new->inode))!= pa->inode) + || (attr_new->type != pa->type); + break; + default : err = 1; + } + if (!err) { + err = (namelen != pa->namelen) + || (namelen + && memcmp(name, pa->name, namelen)); + } + if (optv > 1) + printf("-> attribute %s the recorded one\n", + (err ? "does not match" : "matches")); + } else + if (optv) + printf("* Unrecorded attribute\n"); +err = 0; + return (err); +} + +static int undo_sizes(ntfs_volume *vol, + const struct ACTION_RECORD *action, char *buffer) +{ + const char *data; + MFT_RECORD *entry; + ATTR_RECORD *attr; + u32 target; + u32 length; + u32 offs; + int err; + + if (optv > 1) + printf("-> %s()\n",__func__); + err = 1; + data = ((const char*)&action->record) + + get_undo_offset(&action->record); + length = le16_to_cpu(action->record.undo_length); + target = le16_to_cpu(action->record.record_offset) + + le16_to_cpu(action->record.attribute_offset) + + offsetof(ATTR_RECORD, allocated_size); + entry = (MFT_RECORD*)buffer; + if (!(entry->flags & MFT_RECORD_IS_DIRECTORY)) + err = change_resident(vol, action, buffer, + data, target, length); + else { + /* On a directory, may have to build an index allocation */ + offs = le16_to_cpu(action->record.record_offset); + attr = (ATTR_RECORD*)(buffer + offs); + if (attr->type != AT_INDEX_ALLOCATION) { + err = insert_index_allocation(vol, buffer, offs); + if (!err) + err = change_resident(vol, action, buffer, + data, target, length); + } else + err = change_resident(vol, action, buffer, + data, target, length); + } + return (err); +} + +static int undo_update_index(ntfs_volume *vol, const struct ACTION_RECORD *action, + char *buffer) +{ + const char *data; + u32 target; + u32 length; + int err; + + if (optv > 1) + printf("-> %s()\n",__func__); + err = 1; + data = ((const char*)&action->record) + + get_undo_offset(&action->record); + length = le16_to_cpu(action->record.undo_length); + /* target is left-justified to creation time */ + target = le16_to_cpu(action->record.record_offset) + + le16_to_cpu(action->record.attribute_offset) + + offsetof(INDEX_ENTRY, key.file_name.creation_time); + err = update_index(vol, action, buffer, data, target, length); + return (err); +} + +static int undo_update_index_value(ntfs_volume *vol, + const struct ACTION_RECORD *action, char *buffer) +{ + LCN lcn; + const char *data; + u32 length; + u32 target; + int changed; + int err; + + if (optv > 1) + printf("-> %s()\n",__func__); + err = 1; + data = ((const char*)&action->record) + + get_undo_offset(&action->record); + length = le16_to_cpu(action->record.undo_length); + target = le16_to_cpu(action->record.record_offset) + + le16_to_cpu(action->record.attribute_offset); + if (optv > 1) { + lcn = le64_to_cpu(action->record.lcn_list[0]); + printf("-> lcn 0x%llx target 0x%x length %d\n", + (long long)lcn, (int)target, (int)length); + } + if (optv > 1) { + printf("-> existing record :\n"); + dump(&buffer[target], length); + } + if ((target + length) <= vol->indx_record_size) { + changed = length && memcmp(buffer + target, data, length); + err = 0; + if (changed) { + memcpy(buffer + target, data, length); + if (optv > 1) { + printf("-> new record :\n"); + dump(buffer + target, length); + } + err = write_protected(vol, &action->record, buffer, + vol->indx_record_size); + } + if (optv > 1) { + printf("-> data record %s\n", + (changed ? "updated" : "unchanged")); + } + } + return (err); +} + +static int undo_update_vcn(ntfs_volume *vol, const struct ACTION_RECORD *action, + char *buffer) +{ + const char *data; + u32 target; + u32 length; + int err; + + if (optv > 1) + printf("-> %s()\n",__func__); + err = 1; + data = ((const char*)&action->record) + + get_undo_offset(&action->record); + length = le16_to_cpu(action->record.undo_length); + /* target is left-justified to creation time */ + target = le16_to_cpu(action->record.record_offset) + + le16_to_cpu(action->record.attribute_offset) + + 16; // to better describe + err = update_index(vol, action, buffer, data, target, length); + return (err); +} + +static int undo_update_mapping(ntfs_volume *vol, const struct ACTION_RECORD *action, + char *buffer) +{ + LCN lcn; + const char *data; + ATTR_RECORD *attr; + MFT_RECORD *entry; + u32 target; + u32 length; + u32 source; + u32 alen; + u32 newused; + int err; + int changed; + int resize; + + if (optv > 1) + printf("-> %s()\n",__func__); + err = 1; + data = ((const char*)&action->record) + + get_undo_offset(&action->record); + length = le16_to_cpu(action->record.undo_length); + resize = length - le16_to_cpu(action->record.redo_length); + target = le16_to_cpu(action->record.record_offset) + + le16_to_cpu(action->record.attribute_offset); + if (optv > 1) { + lcn = le64_to_cpu(action->record.lcn_list[0]); + printf("-> inode %lld lcn 0x%llx target 0x%x new length %d resize %d\n", + (long long)inode_number(&action->record), + (long long)lcn, (int)target, (int)length, (int)resize); + } +// TODO share with redo_update_mapping() + if (optv > 1) { + printf("-> existing record :\n"); + dump(&buffer[target], length); + } + entry = (MFT_RECORD*)buffer; + attr = (ATTR_RECORD*)(buffer + + le16_to_cpu(action->record.record_offset)); + if (!attr->non_resident) { + printf("** Error : update_mapping on resident attr\n"); + } + if (valid_type(attr->type) + && attr->non_resident + && !(resize & 7) + && ((target + length) <= mftrecsz)) { + changed = memcmp(buffer + target, data, length); + err = 0; + if (changed) { + /* Adjust space for new mapping pairs */ + source = target - resize; + if (resize > 0) { + memmove(buffer + target + length, + buffer + source + length, + mftrecsz - target - length); + } + if (resize < 0) { + memmove(buffer + target + length, + buffer + source + length, + mftrecsz - source - length); + } + memcpy(buffer + target, data, length); + /* Resize the attribute */ + alen = le32_to_cpu(attr->length) + resize; + attr->length = cpu_to_le32(alen); + /* Resize the mft record */ + newused = le32_to_cpu(entry->bytes_in_use) + + resize; + entry->bytes_in_use = cpu_to_le32(newused); + /* Compute the new highest_vcn */ + err = adjust_high_vcn(vol, attr); + if (optv > 1) { + printf("-> new record :\n"); + dump(buffer + target, length); + } + if (!err) { + err = write_protected(vol, + &action->record, buffer, + mftrecsz); + } + } + if (optv > 1) { + printf("-> MFT record %s\n", + (changed ? "updated" : "unchanged")); + } + } + return (err); +} + +static int undo_update_resident(ntfs_volume *vol, + const struct ACTION_RECORD *action, char *buffer) +{ + LCN lcn; + const char *data; + u32 target; + u32 length; + u32 oldlength; + u32 end; + u32 undo; + int err; + int changed; + + if (optv > 1) + printf("-> %s()\n",__func__); + err = 1; + end = le32_to_cpu(action->record.client_data_length) + + LOG_RECORD_HEAD_SZ; + length = le16_to_cpu(action->record.undo_length); + undo = get_undo_offset(&action->record); + if ((undo + length) > end) + data = (char*)NULL; + else + data = ((const char*)&action->record) + undo; + oldlength = le16_to_cpu(action->record.redo_length); + target = le16_to_cpu(action->record.record_offset) + + le16_to_cpu(action->record.attribute_offset); + if (length == oldlength) { + if (optv > 1) { + lcn = le64_to_cpu(action->record.lcn_list[0]); + printf("-> inode %lld lcn 0x%llx target 0x%x length %d\n", + (long long)inode_number(&action->record), + (long long)lcn, (int)target, (int)length); + } + if (optv > 1) { + printf("-> existing record :\n"); + dump(&buffer[target], length); + } + if ((target + length) <= mftrecsz) { + changed = memcmp(buffer + target, data, length); + err = 0; + if (changed) { + memcpy(buffer + target, data, length); + if (optv > 1) { + printf("-> new record :\n"); + dump(buffer + target, length); + } + err = write_protected(vol, &action->record, + buffer, mftrecsz); + } + if (optv > 1) { + printf("-> MFT record %s\n", + (changed ? "updated" : "unchanged")); + } + } + } else { + if (length > oldlength) + err = expand_resident(vol, action, buffer, data, + target, length, oldlength); + else + err = shrink_resident(vol, action, buffer, data, + target, length, oldlength); + } + return (err); +} + +static int undo_update_root_index(ntfs_volume *vol, + const struct ACTION_RECORD *action, char *buffer) +{ + const char *data; + const char *expected; + u32 target; + u32 length; + int err; + + if (optv > 1) + printf("-> %s()\n",__func__); + err = 1; + data = ((const char*)&action->record) + + get_undo_offset(&action->record); + expected = ((const char*)&action->record) + + get_redo_offset(&action->record); + length = le16_to_cpu(action->record.undo_length); + /* the fixup is right-justified to the name length */ + target = le16_to_cpu(action->record.record_offset) + + le16_to_cpu(action->record.attribute_offset) + + offsetof(INDEX_ENTRY, key.file_name.file_name_length) + - length; + err = change_resident_expect(vol, action, buffer, data, expected, + target, length, AT_INDEX_ROOT); + return (err); +} + +static int undo_update_root_vcn(ntfs_volume *vol, + const struct ACTION_RECORD *action, char *buffer) +{ + const char *data; + const char *expected; + u32 target; + u32 length; + int err; + + if (optv > 1) + printf("-> %s()\n",__func__); + err = 1; + data = ((const char*)&action->record) + + get_undo_offset(&action->record); + expected = ((const char*)&action->record) + + get_redo_offset(&action->record); + length = le16_to_cpu(action->record.undo_length); + /* the fixup is right-justified to the name length */ + target = le16_to_cpu(action->record.record_offset) + + le16_to_cpu(action->record.attribute_offset) + + 16; // explanation needed + err = change_resident_expect(vol, action, buffer, data, expected, + target, length, AT_INDEX_ROOT); + return (err); +} + +static int undo_update_value(ntfs_volume *vol, + const struct ACTION_RECORD *action, char *buffer) +{ + LCN lcn; + const char *data; + u32 length; + u32 target; + u32 count; + int changed; + int err; + + if (optv > 1) + printf("-> %s()\n",__func__); + err = 1; + data = ((const char*)&action->record) + + get_undo_offset(&action->record); + length = le16_to_cpu(action->record.undo_length); + target = le16_to_cpu(action->record.record_offset) + + le16_to_cpu(action->record.attribute_offset); + count = le16_to_cpu(action->record.lcns_to_follow); + if (optv > 1) { + lcn = le64_to_cpu(action->record.lcn_list[0]); + printf("-> lcn 0x%llx target 0x%x length %d\n", + (long long)lcn, (int)target, (int)length); + } + if (length) { + if (optv > 1) { + printf("-> existing record :\n"); + dump(&buffer[target], length); + } + if ((target + length) <= (count << clusterbits)) { + changed = memcmp(buffer + target, data, length); + err = 0; + if (changed) { + memcpy(buffer + target, data, length); + if (optv > 1) { + printf("-> new record :\n"); + dump(buffer + target, length); + } + err = write_raw(vol, &action->record, buffer); + } + if (optv > 1) { + printf("-> data record %s\n", + (changed ? "updated" : "unchanged")); + } + } + } else { + /* + * No undo data, we cannot undo, sometimes the redo + * data even overflows from record. + * Just ignore for now. + */ + if (optv) + printf("Cannot undo, there is no undo data\n"); + err = 0; + } + + return (err); +} + +static int undo_write_end(ntfs_volume *vol, + const struct ACTION_RECORD *action, char *buffer) +{ + LCN lcn; + const char *data; + u32 target; + u32 length; + u32 oldlength; + u32 end; + u32 undo; + int err; + int changed; + + if (optv > 1) + printf("-> %s()\n",__func__); + err = 1; + end = le32_to_cpu(action->record.client_data_length) + + LOG_RECORD_HEAD_SZ; + length = le16_to_cpu(action->record.undo_length); + undo = get_undo_offset(&action->record); + if ((undo + length) > end) + data = (char*)NULL; + else + data = ((const char*)&action->record) + undo; + oldlength = le16_to_cpu(action->record.redo_length); + target = le16_to_cpu(action->record.record_offset) + + le16_to_cpu(action->record.attribute_offset); + if (length == oldlength) { + if (optv > 1) { + lcn = le64_to_cpu(action->record.lcn_list[0]); + printf("-> inode %lld lcn 0x%llx target 0x%x" + " length %d\n", + (long long)inode_number(&action->record), + (long long)lcn, (int)target, (int)length); + } + if (optv > 1) { + printf("-> existing record :\n"); + dump(&buffer[target], length); + } + if ((target + length) <= mftrecsz) { + changed = memcmp(buffer + target, data, length); + err = 0; + if (changed) { + memcpy(buffer + target, data, length); + if (optv > 1) { + printf("-> new record :\n"); + dump(buffer + target, length); + } + err = write_protected(vol, &action->record, + buffer, mftrecsz); + } + if (optv > 1) { + printf("-> MFT record %s\n", + (changed ? "updated" : "unchanged")); + } + } + } else { + if (length > oldlength) + err = add_resident(vol, action, buffer, data, + target, length, oldlength); + else + err = delete_resident(vol, action, buffer, data, + target, length, oldlength); + } + return (err); +} + +static int undo_write_index(ntfs_volume *vol, + const struct ACTION_RECORD *action, char *buffer) +{ + LCN lcn; + const char *data; + u32 target; + u32 length; + u32 oldlength; + u32 end; + u32 undo; + int err; + int changed; + + if (optv > 1) + printf("-> %s()\n",__func__); + err = 1; + end = le32_to_cpu(action->record.client_data_length) + + LOG_RECORD_HEAD_SZ; + length = le16_to_cpu(action->record.undo_length); + undo = get_undo_offset(&action->record); + if ((undo + length) > end) + data = (char*)NULL; + else + data = ((const char*)&action->record) + undo; + oldlength = le16_to_cpu(action->record.redo_length); + target = le16_to_cpu(action->record.record_offset) + + le16_to_cpu(action->record.attribute_offset); + if (length == oldlength) { + if (optv > 1) { + lcn = le64_to_cpu(action->record.lcn_list[0]); + printf("-> inode %lld lcn 0x%llx target 0x%x" + " length %d\n", + (long long)inode_number(&action->record), + (long long)lcn, (int)target, (int)length); + } + if (optv > 1) { + printf("-> existing record :\n"); + dump(&buffer[target], length); + } + if ((target + length) <= mftrecsz) { + changed = memcmp(buffer + target, data, length); + err = 0; + if (changed) { + memcpy(buffer + target, data, length); + if (optv > 1) { + printf("-> new record :\n"); + dump(buffer + target, length); + } + err = write_protected(vol, &action->record, + buffer, mftrecsz); + } + if (optv > 1) { + printf("-> MFT record %s\n", + (changed ? "updated" : "unchanged")); + } + } + } else { + if (length > oldlength) + err = add_non_resident(/*vol, action, data, + target, length, oldlength*/); + else + err = delete_non_resident(/*vol, action, data, + target, length, oldlength*/); + } + return (err); +} + +enum ACTION_KIND { ON_NONE, ON_MFT, ON_INDX, ON_RAW } ; + +static enum ACTION_KIND get_action_kind(const struct ACTION_RECORD *action) +{ + struct ATTR *pa; + const char *data; + enum ACTION_KIND kind; + /* + * If we are sure the action was defined by Vista + * or subsequent, just use attribute_flags. + * Unfortunately, only on some cases we can determine + * the action was defined by Win10 (or subsequent). + */ + if (action->record.log_record_flags + & const_cpu_to_le16(RECORD_DELETING | RECORD_ADDING)) { + if (action->record.attribute_flags + & const_cpu_to_le16(ACTS_ON_INDX)) + kind = ON_INDX; + else + if (action->record.attribute_flags + & const_cpu_to_le16(ACTS_ON_MFT)) + kind = ON_MFT; + else + kind = ON_RAW; + } else { + /* + * In other cases, we have to rely on the attribute + * definition, but this has defects when undoing. + */ + pa = getattrentry(le16_to_cpu( + action->record.target_attribute),0); + if (!pa || !pa->type) { + /* + * Even when the attribute has not been recorded, + * we can sometimes tell the record does not apply + * to MFT or INDX : such records always have a zero + * record_offset, and if attribute_offset is zero, their + * magic can be checked. If neither condition is true, + * the action cannot apply to MFT or INDX. + * (this is useful for undoing) + */ + data = (const char*)&action->record + + get_redo_offset(&action->record); + if (action->record.record_offset + || (!action->record.attribute_offset + && (le16_to_cpu(action->record.redo_length) + >= 4) + && memcmp(data,"FILE",4) + && memcmp(data,"INDX",4))) { + kind = ON_RAW; + } else { + printf("** Error : attribute 0x%x" + " is not defined\n", + (int)le16_to_cpu( + action->record.target_attribute)); + kind = ON_NONE; + } + } else { + if (pa->type == AT_INDEX_ALLOCATION) + kind = ON_INDX; + else + kind = ON_RAW; + } + } + return (kind); +} + + +/* + * Display the redo actions which were executed + * + * Useful for getting indications on the coverage of a test + */ + +void show_redos(void) +{ + int i; + + if (optv && redos_met) { + printf("Redo actions which were executed :\n"); + for (i=0; i<64; i++) + if ((((u64)1) << i) & redos_met) + printf("%s\n", actionname(i)); + } +} + +static int distribute_redos(ntfs_volume *vol, + const struct ACTION_RECORD *action, char *buffer) +{ + int rop, uop; + int err; + + err = 0; + rop = le16_to_cpu(action->record.redo_operation); + uop = le16_to_cpu(action->record.undo_operation); + switch (rop) { + case AddIndexEntryAllocation : + if (action->record.undo_operation + == const_cpu_to_le16(DeleteIndexEntryAllocation)) + err = redo_add_index(vol, action, buffer); + break; + case AddIndexEntryRoot : + if (action->record.undo_operation + == const_cpu_to_le16(DeleteIndexEntryRoot)) + err = redo_add_root_index(vol, action, buffer); + break; + case ClearBitsInNonResidentBitMap : + if (action->record.undo_operation + == const_cpu_to_le16(SetBitsInNonResidentBitMap)) + err = redo_force_bits(vol, action, buffer); + break; + case CompensationlogRecord : + if (action->record.undo_operation + == const_cpu_to_le16(Noop)) + err = redo_compensate(vol, action, buffer); + break; + case CreateAttribute : + if (action->record.undo_operation + == const_cpu_to_le16(DeleteAttribute)) + err = redo_create_attribute(vol, action, buffer); + break; + case DeallocateFileRecordSegment : + if (action->record.undo_operation + == const_cpu_to_le16(InitializeFileRecordSegment)) + err = redo_delete_file(vol, action, buffer); + break; + case DeleteAttribute : + if (action->record.undo_operation + == const_cpu_to_le16(CreateAttribute)) + err = redo_delete_attribute(vol, action, buffer); + break; + case DeleteIndexEntryAllocation : + if (action->record.undo_operation + == const_cpu_to_le16(AddIndexEntryAllocation)) + err = redo_delete_index(vol, action, buffer); + break; + case DeleteIndexEntryRoot : + if (action->record.undo_operation + == const_cpu_to_le16(AddIndexEntryRoot)) + err = redo_delete_root_index(vol, action, buffer); + break; + case InitializeFileRecordSegment : + if (action->record.undo_operation + == const_cpu_to_le16(Noop)) + err = redo_create_file(vol, action, buffer); + break; + case OpenNonResidentAttribute : + if (action->record.undo_operation + == const_cpu_to_le16(Noop)) + err = redo_open_attribute(vol, action); + break; + case SetBitsInNonResidentBitMap : + if (action->record.undo_operation + == const_cpu_to_le16(ClearBitsInNonResidentBitMap)) + err = redo_force_bits(vol, action, buffer); + break; + case SetIndexEntryVcnAllocation : + if (action->record.undo_operation + == const_cpu_to_le16(SetIndexEntryVcnAllocation)) + err = redo_update_vcn(vol, action, buffer); + break; + case SetIndexEntryVcnRoot : + if (action->record.undo_operation + == const_cpu_to_le16(SetIndexEntryVcnRoot)) + err = redo_update_root_vcn(vol, action, buffer); + break; + case SetNewAttributeSizes : + if (action->record.undo_operation + == const_cpu_to_le16(SetNewAttributeSizes)) + err = redo_sizes(vol, action, buffer); + break; + case UpdateFileNameAllocation : + if (action->record.undo_operation + == const_cpu_to_le16(UpdateFileNameAllocation)) + err = redo_update_index(vol, action, buffer); + break; + case UpdateFileNameRoot : + if (action->record.undo_operation + == const_cpu_to_le16(UpdateFileNameRoot)) + err = redo_update_root_index(vol, action, buffer); + break; + case UpdateMappingPairs : + if (action->record.undo_operation + == const_cpu_to_le16(UpdateMappingPairs)) + err = redo_update_mapping(vol, action, buffer); + break; + case UpdateNonResidentValue : + switch (get_action_kind(action)) { + case ON_INDX : + err = redo_update_index_value(vol, action, buffer); + break; + case ON_RAW : + err = redo_update_value(vol, action, buffer); + break; + default : + printf("** Bad attribute type\n"); + err = 1; + } + break; + case UpdateResidentValue : + if (action->record.undo_operation + == const_cpu_to_le16(UpdateResidentValue)) + err = redo_update_resident(vol, action, buffer); + break; + case Win10Action37 : + if (action->record.undo_operation + == const_cpu_to_le16(Noop)) + err = redo_action37(vol, action, buffer); + break; + case WriteEndofFileRecordSegment : + if (action->record.undo_operation + == const_cpu_to_le16(WriteEndofFileRecordSegment)) + err = redo_write_end(vol, action, buffer); + break; + case WriteEndOfIndexBuffer : + if (action->record.undo_operation + == const_cpu_to_le16(WriteEndOfIndexBuffer)) + err = redo_write_index(vol, action, buffer); + break; + case AttributeNamesDump : + case DirtyPageTableDump : + case ForgetTransaction : + case Noop : + case OpenAttributeTableDump : + break; + default : + printf("** Unsupported redo %s\n", actionname(rop)); + err = 1; + break; + } + redos_met |= ((u64)1) << rop; + if (err) + printf("* Redoing action %d %s (%s) failed\n", + action->num,actionname(rop), actionname(uop)); + return (err); +} + +/* + * Play the redo actions from earliest to latest + * + * Currently we can only redo the last undone transaction, + * otherwise the attribute table would be out of phase. + */ + +int play_redos(ntfs_volume *vol, const struct ACTION_RECORD *firstaction) +{ + const struct ACTION_RECORD *action; + MFT_RECORD *entry; + INDEX_BLOCK *indx; + char *buffer; + s64 this_lsn; + s64 data_lsn; + u32 xsize; + u16 rop; + u16 uop; + int err; + BOOL warn; + BOOL executed; + enum ACTION_KIND kind; + + err = 0; + action = firstaction; + while (action && !err) { + this_lsn = le64_to_cpu(action->record.this_lsn); + /* Only committed actions should be redone */ + if ((!optc || within_lcn_range(&action->record)) + && (action->flags & ACTION_TO_REDO)) { + rop = le16_to_cpu(action->record.redo_operation); + uop = le16_to_cpu(action->record.undo_operation); + if (optv) + printf("Redo action %d %s (%s) 0x%llx\n", + action->num, + actionname(rop), actionname(uop), + (long long)le64_to_cpu( + action->record.this_lsn)); + buffer = (char*)NULL; + switch (rop) { + /* Actions always acting on MFT */ + case AddIndexEntryRoot : + case CreateAttribute : + case DeallocateFileRecordSegment : + case DeleteAttribute : + case DeleteIndexEntryRoot : + case InitializeFileRecordSegment : + case SetIndexEntryVcnRoot : + case SetNewAttributeSizes : + case UpdateFileNameRoot : + case UpdateMappingPairs : + case UpdateResidentValue : + case Win10Action37 : + case WriteEndofFileRecordSegment : + kind = ON_MFT; + break; + /* Actions always acting on INDX */ + case AddIndexEntryAllocation : + case DeleteIndexEntryAllocation : + case SetIndexEntryVcnAllocation : + case UpdateFileNameAllocation : + case WriteEndOfIndexBuffer : + kind = ON_INDX; + break; + /* Actions never acting on MFT or INDX */ + case ClearBitsInNonResidentBitMap : + case SetBitsInNonResidentBitMap : + kind = ON_RAW; + break; + /* Actions which may act on MFT */ + case Noop : /* on MFT if DeallocateFileRecordSegment */ + kind = ON_NONE; + break; + /* Actions which may act on INDX */ + case UpdateNonResidentValue : + /* Known cases : INDX, $SDS, ATTR_LIST */ + kind = get_action_kind(action); + if (kind == ON_NONE) + err = 1; + break; + case CompensationlogRecord : + case OpenNonResidentAttribute : + /* probably not important */ + kind = ON_NONE; + break; + /* Actions currently ignored */ + case AttributeNamesDump : + case DirtyPageTableDump : + case ForgetTransaction : + case OpenAttributeTableDump : + case TransactionTableDump : + kind = ON_NONE; + break; + /* Actions with no known use case */ + case CommitTransaction : + case DeleteDirtyClusters : + case EndTopLevelAction : + case HotFix : + case PrepareTransaction : + case UpdateRecordDataAllocation : + case UpdateRecordDataRoot : + case Win10Action35 : + case Win10Action36 : + default : + err = 1; + kind = ON_NONE; + break; + } + executed = FALSE; + switch (kind) { + case ON_MFT : +/* + the check below cannot be used on WinXP +if (!(action->record.attribute_flags & const_cpu_to_le16(ACTS_ON_MFT))) +printf("** %s (action %d) not acting on MFT\n",actionname(rop),(int)action->num); +*/ + /* Check whether data is to be discarded */ + warn = (rop != InitializeFileRecordSegment) + || !check_full_mft(action,TRUE); + buffer = read_protected(vol, &action->record, + mftrecsz, warn); + entry = (MFT_RECORD*)buffer; + if (entry && (entry->magic == magic_FILE)) { + data_lsn = le64_to_cpu(entry->lsn); + /* + * Beware of records not updated + * during the last session which may + * have a stale lsn (consequence + * of ntfs-3g resetting the log) + */ + executed = ((s64)(data_lsn + - this_lsn) >= 0) + && (((s64)(data_lsn + - latest_lsn)) <= 0) + && !exception(action->num); + } else { + if (!warn) { + /* Old record not needed */ + if (!buffer) + buffer = + (char*)malloc(mftrecsz); + if (buffer) + executed = FALSE; + else + err = 1; + } else { + printf("** %s (action %d) not" + " acting on MFT\n", + actionname(rop), + (int)action->num); + err = 1; + } + } + break; + case ON_INDX : +/* + the check below cannot be used on WinXP +if (!(action->record.attribute_flags & const_cpu_to_le16(ACTS_ON_INDX))) +printf("** %s (action %d) not acting on INDX\n",actionname(rop),(int)action->num); +*/ + xsize = vol->indx_record_size; + /* Check whether data is to be discarded */ + warn = (rop != UpdateNonResidentValue) + || !check_full_index(action,TRUE); + buffer = read_protected(vol, &action->record, + xsize, warn); + indx = (INDEX_BLOCK*)buffer; + if (indx && (indx->magic == magic_INDX)) { + data_lsn = le64_to_cpu(indx->lsn); + /* + * Beware of records not updated + * during the last session which may + * have a stale lsn (consequence + * of ntfs-3g resetting the log) + */ + executed = ((s64)(data_lsn + - this_lsn) >= 0) + && (((s64)(data_lsn + - latest_lsn)) <= 0) + && ! exception(action->num); + } else { + if (!warn) { + /* Old record not needed */ + if (!buffer) + buffer = + (char*)malloc(xsize); + if (buffer) + executed = FALSE; + else + err = 1; + } else { + printf("** %s (action %d) not" + " acting on INDX\n", + actionname(rop), + (int)action->num); + err = 1; + } + } + break; + case ON_RAW : + if (action->record.attribute_flags + & (const_cpu_to_le16( + ACTS_ON_INDX | ACTS_ON_MFT))) { + printf("** Error : action %s on MFT" + " or INDX\n", + actionname(rop)); + err = 1; + } else { + buffer = read_raw(vol, &action->record); + if (!buffer) + err = 1; + } + break; + default : + buffer = (char*)NULL; + break; + } + if (!err && (!executed || !opts)) { + err = distribute_redos(vol, action, buffer); + redocount++; + } else { + if (optv) + printf("Action %d %s (%s) not redone\n", + action->num, + actionname(rop), + actionname(uop)); + } + if (buffer) + free(buffer); + } + if (!err) + action = action->next; + } + return (err); +} + +static int distribute_undos(ntfs_volume *vol, const struct ACTION_RECORD *action, + char *buffer) +{ + int rop, uop; + int err; + + err = 0; + rop = le16_to_cpu(action->record.redo_operation); + uop = le16_to_cpu(action->record.undo_operation); + switch (rop) { + case AddIndexEntryAllocation : + if (action->record.undo_operation + == const_cpu_to_le16(DeleteIndexEntryAllocation)) + err = undo_add_index(vol, action, buffer); + break; + case AddIndexEntryRoot : + if (action->record.undo_operation + == const_cpu_to_le16(DeleteIndexEntryRoot)) + err = undo_add_root_index(vol, action, buffer); + break; + case ClearBitsInNonResidentBitMap : + if (action->record.undo_operation + == const_cpu_to_le16(SetBitsInNonResidentBitMap)) + err = undo_force_bits(vol, action, buffer); + break; + case CreateAttribute : + if (action->record.undo_operation + == const_cpu_to_le16(DeleteAttribute)) + err = undo_create_attribute(vol, action, buffer); + break; + case DeallocateFileRecordSegment : + if (action->record.undo_operation + == const_cpu_to_le16(InitializeFileRecordSegment)) + err = undo_delete_file(vol, action, buffer); + break; + case DeleteAttribute : + if (action->record.undo_operation + == const_cpu_to_le16(CreateAttribute)) + err = undo_delete_attribute(vol, action, buffer); + break; + case DeleteIndexEntryAllocation : + if (action->record.undo_operation + == const_cpu_to_le16(AddIndexEntryAllocation)) + err = undo_delete_index(vol, action, buffer); + break; + case DeleteIndexEntryRoot : + if (action->record.undo_operation + == const_cpu_to_le16(AddIndexEntryRoot)) + err = undo_delete_root_index(vol, action, buffer); + break; + case InitializeFileRecordSegment : + if (action->record.undo_operation + == const_cpu_to_le16(Noop)) + err = undo_create_file(vol, action, buffer); + break; + case OpenNonResidentAttribute : + if (action->record.undo_operation + == const_cpu_to_le16(Noop)) + err = undo_open_attribute(vol, action); + break; + case SetBitsInNonResidentBitMap : + if (action->record.undo_operation + == const_cpu_to_le16(ClearBitsInNonResidentBitMap)) + err = undo_force_bits(vol, action, buffer); + break; + case SetIndexEntryVcnAllocation : + if (action->record.undo_operation + == const_cpu_to_le16(SetIndexEntryVcnAllocation)) + err = undo_update_vcn(vol, action, buffer); + break; + case SetIndexEntryVcnRoot : + if (action->record.undo_operation + == const_cpu_to_le16(SetIndexEntryVcnRoot)) + err = undo_update_root_vcn(vol, action, buffer); + break; + case SetNewAttributeSizes : + if (action->record.undo_operation + == const_cpu_to_le16(SetNewAttributeSizes)) + err = undo_sizes(vol, action, buffer); + break; + case UpdateFileNameAllocation : + if (action->record.undo_operation + == const_cpu_to_le16(UpdateFileNameAllocation)) + err = undo_update_index(vol, action, buffer); + break; + case UpdateFileNameRoot : + if (action->record.undo_operation + == const_cpu_to_le16(UpdateFileNameRoot)) + err = undo_update_root_index(vol, action, buffer); + break; + case UpdateMappingPairs : + if (action->record.undo_operation + == const_cpu_to_le16(UpdateMappingPairs)) + err = undo_update_mapping(vol, action, buffer); + break; + case UpdateNonResidentValue : + switch (get_action_kind(action)) { + case ON_INDX : + err = undo_update_index_value(vol, action, buffer); + break; + case ON_RAW : + err = undo_update_value(vol, action, buffer); + break; + default : + printf("** Bad attribute type\n"); + err = 1; + } + break; + case UpdateResidentValue : + if (action->record.undo_operation + == const_cpu_to_le16(UpdateResidentValue)) + err = undo_update_resident(vol, action, buffer); + break; + case Win10Action37 : + if (action->record.undo_operation + == const_cpu_to_le16(Noop)) + err = undo_action37(vol, action, buffer); + break; + case WriteEndofFileRecordSegment : + if (action->record.undo_operation + == const_cpu_to_le16(WriteEndofFileRecordSegment)) + err = undo_write_end(vol, action, buffer); + break; + case WriteEndOfIndexBuffer : + if (action->record.undo_operation + == const_cpu_to_le16(WriteEndOfIndexBuffer)) + err = undo_write_index(vol, action, buffer); + break; + case AttributeNamesDump : + case CompensationlogRecord : + case DirtyPageTableDump : + case ForgetTransaction : + case Noop : + case OpenAttributeTableDump : + break; + default : + printf("** Unsupported undo %s\n", actionname(rop)); + err = 1; + break; + } + if (err) + printf("* Undoing action %d %s (%s) failed\n", + action->num,actionname(rop), actionname(uop)); + return (err); +} + +/* + * Play the undo actions from latest to earliest + * + * For structured record, a check is made on the lsn to only + * try to undo the actions which were executed. This implies + * identifying actions on a structured record. + * + * Returns 0 if successful + */ + +int play_undos(ntfs_volume *vol, const struct ACTION_RECORD *lastaction) +{ + const struct ACTION_RECORD *action; + MFT_RECORD *entry; + INDEX_BLOCK *indx; + char *buffer; + u32 xsize; + u16 rop; + u16 uop; + int err; + BOOL executed; + enum ACTION_KIND kind; + + err = 0; + action = lastaction; + while (action && !err) { + if (!optc || within_lcn_range(&action->record)) { + rop = le16_to_cpu(action->record.redo_operation); + uop = le16_to_cpu(action->record.undo_operation); + if (optv) + printf("Undo action %d %s (%s) lsn 0x%llx\n", + action->num, + actionname(rop), actionname(uop), + (long long)le64_to_cpu( + action->record.this_lsn)); + buffer = (char*)NULL; + executed = FALSE; + kind = ON_NONE; + switch (rop) { + /* Actions always acting on MFT */ + case AddIndexEntryRoot : + case CreateAttribute : + case DeallocateFileRecordSegment : + case DeleteAttribute : + case DeleteIndexEntryRoot : + case InitializeFileRecordSegment : + case SetIndexEntryVcnRoot : + case SetNewAttributeSizes : + case UpdateFileNameRoot : + case UpdateMappingPairs : + case UpdateResidentValue : + case Win10Action37 : + case WriteEndofFileRecordSegment : + kind = ON_MFT; + break; + /* Actions always acting on INDX */ + case AddIndexEntryAllocation : + case DeleteIndexEntryAllocation : + case SetIndexEntryVcnAllocation : + case UpdateFileNameAllocation : + case WriteEndOfIndexBuffer : + kind = ON_INDX; + break; + /* Actions never acting on MFT or INDX */ + case ClearBitsInNonResidentBitMap : + case SetBitsInNonResidentBitMap : + kind = ON_RAW; + break; + /* Actions which may act on MFT */ + case Noop : /* on MFT if DeallocateFileRecordSegment */ + break; + /* Actions which may act on INDX */ + case UpdateNonResidentValue : + /* Known cases : INDX, $SDS, ATTR_LIST */ + kind = get_action_kind(action); + if (kind == ON_NONE) + err = 1; + break; + case OpenNonResidentAttribute : + /* probably not important */ + kind = ON_NONE; + break; + /* Actions currently ignored */ + case AttributeNamesDump : + case CommitTransaction : + case CompensationlogRecord : + case DeleteDirtyClusters : + case DirtyPageTableDump : + case EndTopLevelAction : + case ForgetTransaction : + case HotFix : + case OpenAttributeTableDump : + case PrepareTransaction : + case TransactionTableDump : + case UpdateRecordDataAllocation : + case UpdateRecordDataRoot : + case Win10Action35 : + case Win10Action36 : + kind = ON_NONE; + break; + } + switch (kind) { + case ON_MFT : +/* + the check below cannot be used on WinXP +if (!(action->record.attribute_flags & const_cpu_to_le16(ACTS_ON_MFT))) +printf("** %s (action %d) not acting on MFT\n",actionname(rop),(int)action->num); +*/ + buffer = read_protected(vol, &action->record, + mftrecsz, TRUE); + entry = (MFT_RECORD*)buffer; + if (entry) { + if (entry->magic == magic_FILE) { + executed = !older_record(entry, + &action->record); + if (!executed + && exception(action->num)) + executed = TRUE; +if (optv > 1) +printf("record lsn 0x%llx is %s than action %d lsn 0x%llx\n", +(long long)le64_to_cpu(entry->lsn), +(executed ? "not older" : "older"), +(int)action->num, +(long long)le64_to_cpu(action->record.this_lsn)); + } else { + printf("** %s (action %d) not" + " acting on MFT\n", + actionname(rop), + (int)action->num); + err = 1; + } + } else { +// TODO make sure this is about a newly allocated record (with bad fixup) +// TODO check this is inputting a full record (record lth == data lth) + buffer = (char*)malloc(mftrecsz); + } + break; + case ON_INDX : +/* + the check below cannot be used on WinXP +if (!(action->record.attribute_flags & const_cpu_to_le16(ACTS_ON_INDX))) +printf("** %s (action %d) not acting on INDX\n",actionname(rop),(int)action->num); +*/ + xsize = vol->indx_record_size; + buffer = read_protected(vol, &action->record, + xsize, TRUE); + indx = (INDEX_BLOCK*)buffer; + if (indx) { + if (indx->magic == magic_INDX) { + executed = !older_record(indx, + &action->record); + if (!executed + && exception(action->num)) + executed = TRUE; +if (optv > 1) +printf("index lsn 0x%llx is %s than action %d lsn 0x%llx\n", +(long long)le64_to_cpu(indx->lsn), +(executed ? "not older" : "older"), +(int)action->num, +(long long)le64_to_cpu(action->record.this_lsn)); + } else { + printf("** %s (action %d) not" + " acting on INDX\n", + actionname(rop), + (int)action->num); + err = 1; + } + } else { +// TODO make sure this is about a newly allocated record (with bad fixup) +// TODO check this is inputting a full record (record lth == data lth) +// recreate an INDX record if this is the first entry + buffer = (char*)malloc(xsize); + err = create_indx(vol, action, buffer); + executed = TRUE; + } + break; + case ON_RAW : + if (action->record.attribute_flags + & (const_cpu_to_le16( + ACTS_ON_INDX | ACTS_ON_MFT))) { + printf("** Error : action %s on MFT" + " or INDX\n", + actionname(rop)); + err = 1; + } else { + buffer = read_raw(vol, &action->record); + if (!buffer) + err = 1; + } + executed = TRUE; + break; + default : + executed = TRUE; + buffer = (char*)NULL; + break; + } + if (!err && executed) { + err = distribute_undos(vol, action, buffer); + undocount++; + } + if (buffer) + free(buffer); + } + if (!err) + action = action->prev; + } + return (err); +} From e736fea196c67a195c9ed0d770108e5515e6744f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Pierre=20Andr=C3=A9?= Date: Mon, 9 Nov 2015 15:44:33 +0100 Subject: [PATCH 38/61] Fixed relocating the MFT runlists in ntfsresize The MFT has two runlists which may be partially stored in extents. When these runlists have to be relocated, the relocations must be done after the old runlists are not needed any more to read the data of standard files, but before the MFT may be needed to extend the runlists of standard files. Before doing so the MFT runlists have to be refreshed from device in order to collect the updates which cannot be done in memory during the first stage. --- ntfsprogs/ntfsresize.c | 147 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 139 insertions(+), 8 deletions(-) diff --git a/ntfsprogs/ntfsresize.c b/ntfsprogs/ntfsresize.c index ce3b9f54..0e9b3a33 100644 --- a/ntfsprogs/ntfsresize.c +++ b/ntfsprogs/ntfsresize.c @@ -5,7 +5,7 @@ * Copyright (c) 2002-2005 Anton Altaparmakov * Copyright (c) 2002-2003 Richard Russon * Copyright (c) 2007 Yura Pakhuchiy - * Copyright (c) 2011-2014 Jean-Pierre Andre + * Copyright (c) 2011-2015 Jean-Pierre Andre * * This utility will resize an NTFS volume without data loss. * @@ -404,7 +404,7 @@ static void version(void) printf("Copyright (c) 2002-2005 Anton Altaparmakov\n"); printf("Copyright (c) 2002-2003 Richard Russon\n"); printf("Copyright (c) 2007 Yura Pakhuchiy\n"); - printf("Copyright (c) 2011-2014 Jean-Pierre Andre\n"); + printf("Copyright (c) 2011-2015 Jean-Pierre Andre\n"); printf("\n%s\n%s%s", ntfs_gpl, ntfs_bugs, ntfs_home); } @@ -1327,11 +1327,23 @@ static void expand_attribute_runlist(ntfs_volume *vol, struct DELAYED *delayed) #endif type = delayed->type; rl = delayed->rl; - ni = ntfs_inode_open(vol,mref); + + /* The MFT inode is permanently open, do not reopen or close */ + if (mref == FILE_MFT) + ni = vol->mft_ni; + else + ni = ntfs_inode_open(vol,mref); if (ni) { - na = ntfs_attr_open(ni, type, + if (mref == FILE_MFT) + na = (type == AT_DATA ? vol->mft_na : vol->mftbmp_na); + else + na = ntfs_attr_open(ni, type, delayed->attr_name, delayed->name_len); if (na) { + /* + * The runlist is first updated in memory, and + * the updated one is used for updating on device + */ if (!ntfs_attr_map_whole_runlist(na)) { if (replace_runlist(na,rl,delayed->lowest_vcn) || ntfs_attr_update_mapping_pairs(na,0)) @@ -1341,12 +1353,13 @@ static void expand_attribute_runlist(ntfs_volume *vol, struct DELAYED *delayed) } else perr_exit("Could not map attribute 0x%lx in inode %lld", (long)le32_to_cpu(type),(long long)mref); - ntfs_attr_close(na); + if (mref != FILE_MFT) + ntfs_attr_close(na); } else perr_exit("Could not open attribute 0x%lx in inode %lld", (long)le32_to_cpu(type),(long long)mref); ntfs_inode_mark_dirty(ni); - if (ntfs_inode_close(ni)) + if ((mref != FILE_MFT) && ntfs_inode_close(ni)) perr_exit("Failed to close inode %lld through the library", (long long)mref); } else @@ -1354,6 +1367,91 @@ static void expand_attribute_runlist(ntfs_volume *vol, struct DELAYED *delayed) (long long)mref); } +/* + * Reload the MFT before merging delayed updates of runlist + * + * The delayed updates of runlists are those which imply updating + * the runlists which overflow from their original MFT record. + * Such updates must be done in the new location of the MFT and + * the allocations must be recorded in the new location of the + * MFT bitmap. + * The MFT data and MFT bitmap may themselves have delayed parts + * of their runlists, and at this stage, their runlists may have + * been partially updated on disk, and partially to be updated. + * Their in-memory runlists still point at the old location, they + * are obsolete, and we have to read the partially updated runlist + * from the device before merging the delayed updates. + * + * Returns 0 if successful + * -1 otherwise + */ + +static int reload_mft(ntfs_resize_t *resize) +{ + ntfs_inode *ni; + ntfs_attr *na; + int r; + int xi; + + r = 0; + /* get the base inode */ + ni = resize->vol->mft_ni; + if (!ntfs_file_record_read(resize->vol, FILE_MFT, &ni->mrec, NULL)) { + for (xi=0; !r && xivol->mft_ni->nr_extents; xi++) { + r = ntfs_file_record_read(resize->vol, + ni->extent_nis[xi]->mft_no, + &ni->extent_nis[xi]->mrec, NULL); + } + + if (!r) { + /* reopen the MFT bitmap, and swap vol->mftbmp_na */ + na = ntfs_attr_open(resize->vol->mft_ni, + AT_BITMAP, NULL, 0); + if (na && !ntfs_attr_map_whole_runlist(na)) { + ntfs_attr_close(resize->vol->mftbmp_na); + resize->vol->mftbmp_na = na; + } else + r = -1; + } + + if (!r) { + /* reopen the MFT data, and swap vol->mft_na */ + na = ntfs_attr_open(resize->vol->mft_ni, + AT_DATA, NULL, 0); + if (na && !ntfs_attr_map_whole_runlist(na)) { + ntfs_attr_close(resize->vol->mft_na); + resize->vol->mft_na = na; + } else + r = -1; + } + } else + r = -1; + return (r); +} + +/* + * Re-record the MFT extents in MFT bitmap + * + * When both MFT data and MFT bitmap have delayed runlists, MFT data + * is updated first, and the extents may be recorded at old location. + */ + +static int record_mft_in_bitmap(ntfs_resize_t *resize) +{ + ntfs_inode *ni; + int r; + int xi; + + r = 0; + /* get the base inode */ + ni = resize->vol->mft_ni; + for (xi=0; !r && xivol->mft_ni->nr_extents; xi++) { + r = ntfs_bitmap_set_run(resize->vol->mftbmp_na, + ni->extent_nis[xi]->mft_no, 1); + } + return (r); +} + /* * Process delayed runlist updates */ @@ -1365,9 +1463,26 @@ static void delayed_updates(ntfs_resize_t *resize) if (ntfs_volume_get_free_space(resize->vol)) err_exit("Failed to determine free space\n"); + if (resize->delayed_runlists && reload_mft(resize)) + err_exit("Failed to reload the MFT for delayed updates\n"); + + /* + * Important : updates to MFT must come first, so that + * the new location of MFT is used for adding needed extents. + * Now, there are runlists in the MFT bitmap and MFT data. + * Extents to MFT bitmap have to be stored in the new MFT + * data, and extents to MFT data have to be recorded in + * the MFT bitmap. + * So we update MFT data first, and we record the MFT + * extents again in the MFT bitmap if they were recorded + * in the old location. + */ + while (resize->delayed_runlists) { delayed = resize->delayed_runlists; expand_attribute_runlist(resize->vol, delayed); + if ((delayed->mref == FILE_MFT) && (delayed->type == AT_BITMAP)) + record_mft_in_bitmap(resize); resize->delayed_runlists = resize->delayed_runlists->next; if (delayed->attr_name) free(delayed->attr_name); @@ -1385,6 +1500,7 @@ static void delayed_updates(ntfs_resize_t *resize) static void replace_later(ntfs_resize_t *resize, runlist *rl, runlist *head_rl) { struct DELAYED *delayed; + struct DELAYED *previous; ATTR_RECORD *a; MFT_REF mref; leMFT_REF lemref; @@ -1415,8 +1531,21 @@ static void replace_later(ntfs_resize_t *resize, runlist *rl, runlist *head_rl) delayed->lowest_vcn = le64_to_cpu(a->lowest_vcn); delayed->rl = rl; delayed->head_rl = head_rl; - delayed->next = resize->delayed_runlists; - resize->delayed_runlists = delayed; + /* Queue ahead of list if this is MFT or head is not MFT */ + if ((delayed->mref == FILE_MFT) + || !resize->delayed_runlists + || (resize->delayed_runlists->mref != FILE_MFT)) { + delayed->next = resize->delayed_runlists; + resize->delayed_runlists = delayed; + } else { + /* Queue after all MFTs is this is not MFT */ + previous = resize->delayed_runlists; + while (previous->next + && (previous->next->mref == FILE_MFT)) + previous = previous->next; + delayed->next = previous->next; + previous->next = delayed; + } } else perr_exit("Could not store delayed update data"); } @@ -2514,6 +2643,7 @@ static void truncate_badclust_file(ntfs_resize_t *resize) lookup_data_attr(resize->vol, FILE_BadClus, "$Bad", &resize->ctx); /* FIXME: sanity_check_attr(ctx->attr); */ + resize->mref = FILE_BadClus; truncate_badclust_bad_attr(resize); if (write_mft_record(resize->vol, resize->ctx->ntfs_ino->mft_no, @@ -2539,6 +2669,7 @@ static void truncate_bitmap_file(ntfs_resize_t *resize) printf("Updating $Bitmap file ...\n"); lookup_data_attr(resize->vol, FILE_Bitmap, NULL, &resize->ctx); + resize->mref = FILE_Bitmap; truncate_bitmap_data_attr(resize); if (resize->new_mft_start) { From cba621e82261a64bf39406ffc2da8e9334369dbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Pierre=20Andr=C3=A9?= Date: Mon, 9 Nov 2015 15:58:00 +0100 Subject: [PATCH 39/61] Silenced a compiler warning in ntfswipe (cosmetic) Avoid a signed-to-unsigned compare warning --- ntfsprogs/ntfswipe.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ntfsprogs/ntfswipe.c b/ntfsprogs/ntfswipe.c index 274f13f5..ccd27ab4 100644 --- a/ntfsprogs/ntfswipe.c +++ b/ntfsprogs/ntfswipe.c @@ -981,7 +981,7 @@ static s64 wipe_mft(ntfs_volume *vol, int byte, enum action act) // We know that the end marker will only take 4 bytes size = le32_to_cpu(rec->bytes_in_use) - 4; - if ((size <= 0) || (size > vol->mft_record_size)) { + if ((size <= 0) || (size > (int)vol->mft_record_size)) { ntfs_log_error("Bad mft record %lld\n", (long long)i); total = -1; From 1aaaa8fac109ecfbaf329d770c833d4416cc00c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Pierre=20Andr=C3=A9?= Date: Mon, 9 Nov 2015 16:00:31 +0100 Subject: [PATCH 40/61] Wrote as much data as possible in compressed attribute pwrite When writing to compressed data, the function ntfs_attr_pwrite() cannot cross a compression block border. This is a problem for archivers which rely on libntfs-3g, so the function is now wrapped in another one which restarts the writing as needed. --- libntfs-3g/attrib.c | 48 +++++++++++++++++++++++++++++++++------------ 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/libntfs-3g/attrib.c b/libntfs-3g/attrib.c index 77b1f03a..1465d2c1 100644 --- a/libntfs-3g/attrib.c +++ b/libntfs-3g/attrib.c @@ -5,7 +5,7 @@ * Copyright (c) 2002-2005 Richard Russon * Copyright (c) 2002-2008 Szabolcs Szakacsits * Copyright (c) 2004-2007 Yura Pakhuchiy - * Copyright (c) 2007-2014 Jean-Pierre Andre + * Copyright (c) 2007-2015 Jean-Pierre Andre * Copyright (c) 2010 Erik Larsson * * This program/include file is free software; you can redistribute it and/or @@ -1780,7 +1780,8 @@ static int ntfs_attr_truncate_i(ntfs_attr *na, const s64 newsize, * appropriately to the return code of ntfs_pwrite(), or to EINVAL in case of * invalid arguments. */ -s64 ntfs_attr_pwrite(ntfs_attr *na, const s64 pos, s64 count, const void *b) +static s64 ntfs_attr_pwrite_i(ntfs_attr *na, const s64 pos, s64 count, + const void *b) { s64 written, to_write, ofs, old_initialized_size, old_data_size; s64 total = 0; @@ -1799,15 +1800,6 @@ s64 ntfs_attr_pwrite(ntfs_attr *na, const s64 pos, s64 count, const void *b) BOOL wasnonresident = FALSE; BOOL compressed; - ntfs_log_enter("Entering for inode %lld, attr 0x%x, pos 0x%llx, count " - "0x%llx.\n", (long long)na->ni->mft_no, na->type, - (long long)pos, (long long)count); - - if (!na || !na->ni || !na->ni->vol || !b || pos < 0 || count < 0) { - errno = EINVAL; - ntfs_log_perror("%s", __FUNCTION__); - goto errno_set; - } vol = na->ni->vol; compressed = (na->data_flags & ATTR_COMPRESSION_MASK) != const_cpu_to_le16(0); @@ -2268,7 +2260,6 @@ done: NAttrClearDataAppending(na); } out: - ntfs_log_leave("\n"); return total; rl_err_out: eo = errno; @@ -2335,6 +2326,39 @@ errno_set: goto out; } +s64 ntfs_attr_pwrite(ntfs_attr *na, const s64 pos, s64 count, const void *b) +{ + s64 total; + s64 written; + + ntfs_log_enter("Entering for inode %lld, attr 0x%x, pos 0x%llx, count " + "0x%llx.\n", (long long)na->ni->mft_no, na->type, + (long long)pos, (long long)count); + + total = 0; + if (!na || !na->ni || !na->ni->vol || !b || pos < 0 || count < 0) { + errno = EINVAL; + written = -1; + ntfs_log_perror("%s", __FUNCTION__); + goto out; + } + + /* + * Compressed attributes may be written partially, so + * we may have to iterate. + */ + do { + written = ntfs_attr_pwrite_i(na, pos + total, + count - total, (const u8*)b + total); + if (written > 0) + total += written; + } while ((written > 0) && (total < count)); +out : + ntfs_log_leave("\n"); + return (total > 0 ? total : written); +} + + int ntfs_attr_pclose(ntfs_attr *na) { s64 ofs; From 37bd6661d4d5e14743a43d1b6b9945358da94e37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Pierre=20Andr=C3=A9?= Date: Mon, 9 Nov 2015 16:09:52 +0100 Subject: [PATCH 41/61] Fixed getting space for making an index non resident Under some rare condition there is no space in an MFT entry to make an index non-resident, and the index root has to be moved to an extent. This fix cares for the situation when the attribute list was inserted beforehand. --- libntfs-3g/index.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libntfs-3g/index.c b/libntfs-3g/index.c index d498dde4..d93b1b3d 100644 --- a/libntfs-3g/index.c +++ b/libntfs-3g/index.c @@ -1175,8 +1175,7 @@ retry : * index, we may have to move the root to an extent */ if ((errno == ENOSPC) - && !ctx->al_entry - && !ntfs_inode_add_attrlist(icx->ni)) { + && (ctx->al_entry || !ntfs_inode_add_attrlist(icx->ni))) { ntfs_attr_put_search_ctx(ctx); ctx = (ntfs_attr_search_ctx*)NULL; ir = ntfs_ir_lookup(icx->ni, icx->name, icx->name_len, From 34d29fe0b0a9c5b70e5a7d6f0fb27786b4b81828 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Pierre=20Andr=C3=A9?= Date: Mon, 9 Nov 2015 16:14:31 +0100 Subject: [PATCH 42/61] Fixed reparse data check for non-Microsoft tags Windows requires non-Microsoft reparse points (identified by having bit 31 of the reparse tag clear) to have a 16-byte GUID following the regular reparse point header. This GUID is not, and cannot, be included in the "reparse data length" field. (Contributed by Eric Biggers) --- libntfs-3g/reparse.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/libntfs-3g/reparse.c b/libntfs-3g/reparse.c index 7b96902c..2198b491 100644 --- a/libntfs-3g/reparse.c +++ b/libntfs-3g/reparse.c @@ -422,8 +422,10 @@ static int ntfs_drive_letter(ntfs_volume *vol, ntfschar letter) /* * Do some sanity checks on reparse data * - * The only general check is about the size (at least the tag must - * be present) + * Microsoft reparse points have an 8-byte header whereas + * non-Microsoft reparse points have a 24-byte header. In each case, + * 'reparse_data_length' must equal the number of non-header bytes. + * * If the reparse data looks like a junction point or symbolic * link, more checks can be done. * @@ -441,7 +443,9 @@ static BOOL valid_reparse_data(ntfs_inode *ni, ok = ni && reparse_attr && (size >= sizeof(REPARSE_POINT)) && (((size_t)le16_to_cpu(reparse_attr->reparse_data_length) - + sizeof(REPARSE_POINT)) == size); + + sizeof(REPARSE_POINT) + + ((reparse_attr->reparse_tag & + IO_REPARSE_TAG_IS_MICROSOFT) ? 0 : sizeof(GUID))) == size); if (ok) { switch (reparse_attr->reparse_tag) { case IO_REPARSE_TAG_MOUNT_POINT : From fb0afd41c879cb1ed1dbd9b8acace7c40b4ff1c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Pierre=20Andr=C3=A9?= Date: Mon, 9 Nov 2015 16:16:39 +0100 Subject: [PATCH 43/61] Decoded the full list of bad clusters in ntfsclone and ntfsresize When the bad cluster list required extent, ntfsclone and ntfsresize did not process the extents, leading to unexpected read errors and unmatching bitmaps. This fix enables the full list to be taken into account. --- ntfsprogs/ntfsclone.c | 69 +++++++++--------------------------------- ntfsprogs/ntfsresize.c | 30 +++++++++--------- 2 files changed, 29 insertions(+), 70 deletions(-) diff --git a/ntfsprogs/ntfsclone.c b/ntfsprogs/ntfsclone.c index 1504c252..fd73c4dc 100644 --- a/ntfsprogs/ntfsclone.c +++ b/ntfsprogs/ntfsclone.c @@ -3,7 +3,7 @@ * * Copyright (c) 2003-2006 Szabolcs Szakacsits * Copyright (c) 2004-2006 Anton Altaparmakov - * Copyright (c) 2010-2014 Jean-Pierre Andre + * Copyright (c) 2010-2015 Jean-Pierre Andre * Special image format support copyright (c) 2004 Per Olofsson * * Clone NTFS data and/or metadata to a sparse file, image, device or stdout. @@ -391,7 +391,7 @@ static void version(void) "Efficiently clone, image, restore or rescue an NTFS Volume.\n\n" "Copyright (c) 2003-2006 Szabolcs Szakacsits\n" "Copyright (c) 2004-2006 Anton Altaparmakov\n" - "Copyright (c) 2010-2014 Jean-Pierre Andre\n\n"); + "Copyright (c) 2010-2015 Jean-Pierre Andre\n\n"); fprintf(stderr, "%s\n%s%s", ntfs_gpl, ntfs_bugs, ntfs_home); exit(0); } @@ -2458,64 +2458,27 @@ static void check_output_device(s64 input_size) set_filesize(input_size); } -static ntfs_attr_search_ctx *attr_get_search_ctx(ntfs_inode *ni) -{ - ntfs_attr_search_ctx *ret; - - if ((ret = ntfs_attr_get_search_ctx(ni, NULL)) == NULL) - perr_printf("ntfs_attr_get_search_ctx"); - - return ret; -} - -/** - * lookup_data_attr - * - * Find the $DATA attribute (with or without a name) for the given ntfs inode. - */ -static ntfs_attr_search_ctx *lookup_data_attr(ntfs_inode *ni, const char *aname) -{ - ntfs_attr_search_ctx *ctx; - ntfschar *ustr; - int len = 0; - - if ((ctx = attr_get_search_ctx(ni)) == NULL) - return NULL; - - if ((ustr = ntfs_str2ucs(aname, &len)) == NULL) { - perr_printf("Couldn't convert '%s' to Unicode", aname); - goto error_out; - } - - if (ntfs_attr_lookup(AT_DATA, ustr, len, CASE_SENSITIVE, - 0, NULL, 0, ctx)) { - perr_printf("ntfs_attr_lookup"); - goto error_out; - } - ntfs_ucsfree(ustr); - return ctx; -error_out: - ntfs_attr_put_search_ctx(ctx); - return NULL; -} - static void ignore_bad_clusters(ntfs_walk_clusters_ctx *image) { ntfs_inode *ni; - ntfs_attr_search_ctx *ctx = NULL; - runlist *rl, *rl_bad; + ntfs_attr *na; + runlist *rl; s64 nr_bad_clusters = 0; + static le16 Bad[4] = { + const_cpu_to_le16('$'), const_cpu_to_le16('B'), + const_cpu_to_le16('a'), const_cpu_to_le16('d') + } ; if (!(ni = ntfs_inode_open(vol, FILE_BadClus))) perr_exit("ntfs_open_inode"); - if ((ctx = lookup_data_attr(ni, "$Bad")) == NULL) - exit(1); + na = ntfs_attr_open(ni, AT_DATA, Bad, 4); + if (!na) + perr_exit("ntfs_attr_open"); + if (ntfs_attr_map_whole_runlist(na)) + perr_exit("ntfs_attr_map_whole_runlist"); - if (!(rl_bad = ntfs_mapping_pairs_decompress(vol, ctx->attr, NULL))) - perr_exit("ntfs_mapping_pairs_decompress"); - - for (rl = rl_bad; rl->length; rl++) { + for (rl = na->rl; rl->length; rl++) { s64 lcn = rl->lcn; if (lcn == LCN_HOLE || lcn < 0) @@ -2529,9 +2492,7 @@ static void ignore_bad_clusters(ntfs_walk_clusters_ctx *image) if (nr_bad_clusters) Printf("WARNING: The disk has %lld or more bad sectors" " (hardware faults).\n", (long long)nr_bad_clusters); - free(rl_bad); - - ntfs_attr_put_search_ctx(ctx); + ntfs_attr_close(na); if (ntfs_inode_close(ni)) perr_exit("ntfs_inode_close failed for $BadClus"); } diff --git a/ntfsprogs/ntfsresize.c b/ntfsprogs/ntfsresize.c index 0e9b3a33..bf7db001 100644 --- a/ntfsprogs/ntfsresize.c +++ b/ntfsprogs/ntfsresize.c @@ -2569,8 +2569,13 @@ static int check_bad_sectors(ntfs_volume *vol) { ntfs_attr_search_ctx *ctx; ntfs_inode *base_ni; + ntfs_attr *na; runlist *rl; s64 i, badclusters = 0; + static le16 Bad[4] = { + const_cpu_to_le16('$'), const_cpu_to_le16('B'), + const_cpu_to_le16('a'), const_cpu_to_le16('d') + } ; ntfs_log_verbose("Checking for bad sectors ...\n"); @@ -2580,23 +2585,16 @@ static int check_bad_sectors(ntfs_volume *vol) if (!base_ni) base_ni = ctx->ntfs_ino; - if (NInoAttrList(base_ni)) { - err_printf("Too many bad sectors have been detected!\n"); - printf("%s", many_bad_sectors_msg); + na = ntfs_attr_open(base_ni, AT_DATA, Bad, 4); + if (!na) { + err_printf("Could not access the bad sector list\n"); exit(1); } - - if (!ctx->attr->non_resident) - err_exit("Resident attribute in $BadClust! Please report to " - "%s\n", NTFS_DEV_LIST); - /* - * FIXME: The below would be partial for non-base records in the - * not yet supported multi-record case. Alternatively use audited - * ntfs_attr_truncate after an umount & mount. - */ - if (!(rl = ntfs_mapping_pairs_decompress(vol, ctx->attr, NULL))) - perr_exit("Decompressing $BadClust:$Bad mapping pairs failed"); - + if (ntfs_attr_map_whole_runlist(na) || !na->rl) { + err_printf("Could not decode the bad sector list\n"); + exit(1); + } + rl = na->rl; for (i = 0; rl[i].length; i++) { /* CHECKME: LCN_RL_NOT_MAPPED check isn't needed */ if (rl[i].lcn == LCN_HOLE || rl[i].lcn == LCN_RL_NOT_MAPPED) @@ -2622,7 +2620,7 @@ static int check_bad_sectors(ntfs_volume *vol) "problems and massive data loss!!!\n"); } - free(rl); + ntfs_attr_close(na); #if CLEAN_EXIT close_inode_and_context(ctx); #else From f7bc5249bc717f0e912155f00f80077026e82b0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Pierre=20Andr=C3=A9?= Date: Mon, 9 Nov 2015 16:22:16 +0100 Subject: [PATCH 44/61] Alleviated constraints relative to reparse points Some constraints put on reparse points of unknown type (e.g. they cannot be deleted) are not acceptable to archivers. This patch removes some constraints. --- libntfs-3g/dir.c | 5 ----- libntfs-3g/index.c | 7 ------- 2 files changed, 12 deletions(-) diff --git a/libntfs-3g/dir.c b/libntfs-3g/dir.c index 8633c7d9..e95eff52 100644 --- a/libntfs-3g/dir.c +++ b/libntfs-3g/dir.c @@ -2157,11 +2157,6 @@ static int ntfs_link_i(ntfs_inode *ni, ntfs_inode *dir_ni, const ntfschar *name, goto err_out; } - if ((ni->flags & FILE_ATTR_REPARSE_POINT) - && !ntfs_possible_symlink(ni)) { - err = EOPNOTSUPP; - goto err_out; - } if (NVolHideDotFiles(dir_ni->vol)) { /* Set hidden flag according to the latest name */ if ((name_len > 1) diff --git a/libntfs-3g/index.c b/libntfs-3g/index.c index d93b1b3d..645b74d3 100644 --- a/libntfs-3g/index.c +++ b/libntfs-3g/index.c @@ -1856,13 +1856,6 @@ int ntfs_index_remove(ntfs_inode *dir_ni, ntfs_inode *ni, if (ntfs_index_lookup(key, keylen, icx)) goto err_out; - if ((((FILE_NAME_ATTR *)icx->data)->file_attributes & - FILE_ATTR_REPARSE_POINT) - && !ntfs_possible_symlink(ni)) { - errno = EOPNOTSUPP; - goto err_out; - } - ret = ntfs_index_rm(icx); if (ret == STATUS_ERROR) goto err_out; From 59c90f039db71241e2019e68b6e541c8c4c05752 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Pierre=20Andr=C3=A9?= Date: Thu, 12 Nov 2015 15:31:24 +0100 Subject: [PATCH 45/61] Defined the last logfile block as preceding block 4 in ntfsrecover When block 2 or block 3 points backward to block 4, it is not clear whether the log file only consists of block 2 or block 3 or the log file has just wrapped around. The latter is now assumed. --- ntfsprogs/ntfsrecover.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ntfsprogs/ntfsrecover.c b/ntfsprogs/ntfsrecover.c index ae92c896..97a87e8d 100644 --- a/ntfsprogs/ntfsrecover.c +++ b/ntfsprogs/ntfsrecover.c @@ -1102,6 +1102,14 @@ static const struct BUFFER *findprevious(CONTEXT *ctx, const struct BUFFER *buf) rph = &buf->block.record; prevblk = (le32_to_cpu(rph->copy.file_offset) >> blockbits) - 1; + /* + * If an initial block leads to block 4, it + * can mean the last block or no previous + * block at all. Using the last block is safer, + * its lsn will indicate whether it is stale. + */ + if (prevblk < BASEBLKS) + prevblk = (logfilesz >> blockbits) - 1; } /* No previous block if the log only consists of block 2 or 3 */ if (prevblk < BASEBLKS) { From 1aa9882810452d6682cdca95a176ed92cddd587f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Pierre=20Andr=C3=A9?= Date: Thu, 12 Nov 2015 15:39:20 +0100 Subject: [PATCH 46/61] Silenced a compiler warning (cosmetic) An argument to ntfs_index_remove() is now unused --- libntfs-3g/index.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libntfs-3g/index.c b/libntfs-3g/index.c index 645b74d3..8865a545 100644 --- a/libntfs-3g/index.c +++ b/libntfs-3g/index.c @@ -1841,7 +1841,8 @@ err_out: goto out; } -int ntfs_index_remove(ntfs_inode *dir_ni, ntfs_inode *ni, +int ntfs_index_remove(ntfs_inode *dir_ni, + ntfs_inode *ni __attribute__((unused)), const void *key, const int keylen) { int ret = STATUS_ERROR; From fe3e16243aa2b981dbcdb7f2605a92ac84acc075 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Pierre=20Andr=C3=A9?= Date: Thu, 12 Nov 2015 15:41:44 +0100 Subject: [PATCH 47/61] Fixed a memory leak in ntfsrecover A buffer was left leaking memory in reset_logfile() --- ntfsprogs/ntfsrecover.c | 1 + 1 file changed, 1 insertion(+) diff --git a/ntfsprogs/ntfsrecover.c b/ntfsprogs/ntfsrecover.c index 97a87e8d..567db151 100644 --- a/ntfsprogs/ntfsrecover.c +++ b/ntfsprogs/ntfsrecover.c @@ -2988,6 +2988,7 @@ static int reset_logfile(CONTEXT *ctx __attribute__((unused))) && (ntfs_attr_pwrite(log_na, (u64)1 << blockbits, blocksz, buffer) == blocksz)) err = 0; + free(buffer); } return (err); } From 22b59548d98b8d7cae83061590cce09cae5c1520 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Pierre=20Andr=C3=A9?= Date: Thu, 12 Nov 2015 15:44:47 +0100 Subject: [PATCH 48/61] Subdivided the replay functions in playlog Partially unnested play_redos() and play_undos() --- ntfsprogs/playlog.c | 801 ++++++++++++++++++++++---------------------- 1 file changed, 403 insertions(+), 398 deletions(-) diff --git a/ntfsprogs/playlog.c b/ntfsprogs/playlog.c index 46346fa0..76a6d140 100644 --- a/ntfsprogs/playlog.c +++ b/ntfsprogs/playlog.c @@ -4234,6 +4234,219 @@ static int distribute_redos(ntfs_volume *vol, return (err); } +int play_one_redo(ntfs_volume *vol, const struct ACTION_RECORD *action) +{ + MFT_RECORD *entry; + INDEX_BLOCK *indx; + char *buffer; + s64 this_lsn; + s64 data_lsn; + u32 xsize; + int err; + BOOL warn; + BOOL executed; + enum ACTION_KIND kind; + u16 rop; + u16 uop; + + err = 0; + rop = le16_to_cpu(action->record.redo_operation); + uop = le16_to_cpu(action->record.undo_operation); + this_lsn = le64_to_cpu(action->record.this_lsn); + if (optv) + printf("Redo action %d %s (%s) 0x%llx\n", + action->num, + actionname(rop), actionname(uop), + (long long)le64_to_cpu( + action->record.this_lsn)); + buffer = (char*)NULL; + switch (rop) { + /* Actions always acting on MFT */ + case AddIndexEntryRoot : + case CreateAttribute : + case DeallocateFileRecordSegment : + case DeleteAttribute : + case DeleteIndexEntryRoot : + case InitializeFileRecordSegment : + case SetIndexEntryVcnRoot : + case SetNewAttributeSizes : + case UpdateFileNameRoot : + case UpdateMappingPairs : + case UpdateResidentValue : + case Win10Action37 : + case WriteEndofFileRecordSegment : + kind = ON_MFT; + break; + /* Actions always acting on INDX */ + case AddIndexEntryAllocation : + case DeleteIndexEntryAllocation : + case SetIndexEntryVcnAllocation : + case UpdateFileNameAllocation : + case WriteEndOfIndexBuffer : + kind = ON_INDX; + break; + /* Actions never acting on MFT or INDX */ + case ClearBitsInNonResidentBitMap : + case SetBitsInNonResidentBitMap : + kind = ON_RAW; + break; + /* Actions which may act on MFT */ + case Noop : /* on MFT if DeallocateFileRecordSegment */ + kind = ON_NONE; + break; + /* Actions which may act on INDX */ + case UpdateNonResidentValue : + /* Known cases : INDX, $SDS, ATTR_LIST */ + kind = get_action_kind(action); + if (kind == ON_NONE) + err = 1; + break; + case CompensationlogRecord : + case OpenNonResidentAttribute : + /* probably not important */ + kind = ON_NONE; + break; + /* Actions currently ignored */ + case AttributeNamesDump : + case DirtyPageTableDump : + case ForgetTransaction : + case OpenAttributeTableDump : + case TransactionTableDump : + kind = ON_NONE; + break; + /* Actions with no known use case */ + case CommitTransaction : + case DeleteDirtyClusters : + case EndTopLevelAction : + case HotFix : + case PrepareTransaction : + case UpdateRecordDataAllocation : + case UpdateRecordDataRoot : + case Win10Action35 : + case Win10Action36 : + default : + err = 1; + kind = ON_NONE; + break; + } + executed = FALSE; + switch (kind) { + case ON_MFT : +/* + the check below cannot be used on WinXP +if (!(action->record.attribute_flags & const_cpu_to_le16(ACTS_ON_MFT))) +printf("** %s (action %d) not acting on MFT\n",actionname(rop),(int)action->num); +*/ + /* Check whether data is to be discarded */ + warn = (rop != InitializeFileRecordSegment) + || !check_full_mft(action,TRUE); + buffer = read_protected(vol, &action->record, + mftrecsz, warn); + entry = (MFT_RECORD*)buffer; + if (entry && (entry->magic == magic_FILE)) { + data_lsn = le64_to_cpu(entry->lsn); + /* + * Beware of records not updated + * during the last session which may + * have a stale lsn (consequence + * of ntfs-3g resetting the log) + */ + executed = ((s64)(data_lsn - this_lsn) >= 0) + && (((s64)(data_lsn - latest_lsn)) <= 0) + && !exception(action->num); + } else { + if (!warn) { + /* Old record not needed */ + if (!buffer) + buffer = (char*)calloc(1, mftrecsz); + if (buffer) + executed = FALSE; + else + err = 1; + } else { + printf("** %s (action %d) not" + " acting on MFT\n", + actionname(rop), + (int)action->num); + err = 1; + } + } + break; + case ON_INDX : +/* + the check below cannot be used on WinXP +if (!(action->record.attribute_flags & const_cpu_to_le16(ACTS_ON_INDX))) +printf("** %s (action %d) not acting on INDX\n",actionname(rop),(int)action->num); +*/ + xsize = vol->indx_record_size; + /* Check whether data is to be discarded */ + warn = (rop != UpdateNonResidentValue) + || !check_full_index(action,TRUE); + buffer = read_protected(vol, &action->record, + xsize, warn); + indx = (INDEX_BLOCK*)buffer; + if (indx && (indx->magic == magic_INDX)) { + data_lsn = le64_to_cpu(indx->lsn); + /* + * Beware of records not updated + * during the last session which may + * have a stale lsn (consequence + * of ntfs-3g resetting the log) + */ + executed = ((s64)(data_lsn - this_lsn) >= 0) + && (((s64)(data_lsn - latest_lsn)) <= 0) + && ! exception(action->num); + } else { + if (!warn) { + /* Old record not needed */ + if (!buffer) + buffer = (char*)calloc(1, xsize); + if (buffer) + executed = FALSE; + else + err = 1; + } else { + printf("** %s (action %d) not" + " acting on INDX\n", + actionname(rop), + (int)action->num); + err = 1; + } + } + break; + case ON_RAW : + if (action->record.attribute_flags + & (const_cpu_to_le16(ACTS_ON_INDX | ACTS_ON_MFT))) { + printf("** Error : action %s on MFT" + " or INDX\n", + actionname(rop)); + err = 1; + } else { + buffer = read_raw(vol, &action->record); + if (!buffer) + err = 1; + } + break; + default : + buffer = (char*)NULL; + break; + } + if (!err && (!executed || !opts)) { + err = distribute_redos(vol, action, buffer); + redocount++; + } else { + if (optv) + printf("Action %d %s (%s) not redone\n", + action->num, + actionname(rop), + actionname(uop)); + } + if (buffer) + free(buffer); + return (err); +} + + /* * Play the redo actions from earliest to latest * @@ -4244,226 +4457,15 @@ static int distribute_redos(ntfs_volume *vol, int play_redos(ntfs_volume *vol, const struct ACTION_RECORD *firstaction) { const struct ACTION_RECORD *action; - MFT_RECORD *entry; - INDEX_BLOCK *indx; - char *buffer; - s64 this_lsn; - s64 data_lsn; - u32 xsize; - u16 rop; - u16 uop; int err; - BOOL warn; - BOOL executed; - enum ACTION_KIND kind; err = 0; action = firstaction; while (action && !err) { - this_lsn = le64_to_cpu(action->record.this_lsn); /* Only committed actions should be redone */ if ((!optc || within_lcn_range(&action->record)) - && (action->flags & ACTION_TO_REDO)) { - rop = le16_to_cpu(action->record.redo_operation); - uop = le16_to_cpu(action->record.undo_operation); - if (optv) - printf("Redo action %d %s (%s) 0x%llx\n", - action->num, - actionname(rop), actionname(uop), - (long long)le64_to_cpu( - action->record.this_lsn)); - buffer = (char*)NULL; - switch (rop) { - /* Actions always acting on MFT */ - case AddIndexEntryRoot : - case CreateAttribute : - case DeallocateFileRecordSegment : - case DeleteAttribute : - case DeleteIndexEntryRoot : - case InitializeFileRecordSegment : - case SetIndexEntryVcnRoot : - case SetNewAttributeSizes : - case UpdateFileNameRoot : - case UpdateMappingPairs : - case UpdateResidentValue : - case Win10Action37 : - case WriteEndofFileRecordSegment : - kind = ON_MFT; - break; - /* Actions always acting on INDX */ - case AddIndexEntryAllocation : - case DeleteIndexEntryAllocation : - case SetIndexEntryVcnAllocation : - case UpdateFileNameAllocation : - case WriteEndOfIndexBuffer : - kind = ON_INDX; - break; - /* Actions never acting on MFT or INDX */ - case ClearBitsInNonResidentBitMap : - case SetBitsInNonResidentBitMap : - kind = ON_RAW; - break; - /* Actions which may act on MFT */ - case Noop : /* on MFT if DeallocateFileRecordSegment */ - kind = ON_NONE; - break; - /* Actions which may act on INDX */ - case UpdateNonResidentValue : - /* Known cases : INDX, $SDS, ATTR_LIST */ - kind = get_action_kind(action); - if (kind == ON_NONE) - err = 1; - break; - case CompensationlogRecord : - case OpenNonResidentAttribute : - /* probably not important */ - kind = ON_NONE; - break; - /* Actions currently ignored */ - case AttributeNamesDump : - case DirtyPageTableDump : - case ForgetTransaction : - case OpenAttributeTableDump : - case TransactionTableDump : - kind = ON_NONE; - break; - /* Actions with no known use case */ - case CommitTransaction : - case DeleteDirtyClusters : - case EndTopLevelAction : - case HotFix : - case PrepareTransaction : - case UpdateRecordDataAllocation : - case UpdateRecordDataRoot : - case Win10Action35 : - case Win10Action36 : - default : - err = 1; - kind = ON_NONE; - break; - } - executed = FALSE; - switch (kind) { - case ON_MFT : -/* - the check below cannot be used on WinXP -if (!(action->record.attribute_flags & const_cpu_to_le16(ACTS_ON_MFT))) -printf("** %s (action %d) not acting on MFT\n",actionname(rop),(int)action->num); -*/ - /* Check whether data is to be discarded */ - warn = (rop != InitializeFileRecordSegment) - || !check_full_mft(action,TRUE); - buffer = read_protected(vol, &action->record, - mftrecsz, warn); - entry = (MFT_RECORD*)buffer; - if (entry && (entry->magic == magic_FILE)) { - data_lsn = le64_to_cpu(entry->lsn); - /* - * Beware of records not updated - * during the last session which may - * have a stale lsn (consequence - * of ntfs-3g resetting the log) - */ - executed = ((s64)(data_lsn - - this_lsn) >= 0) - && (((s64)(data_lsn - - latest_lsn)) <= 0) - && !exception(action->num); - } else { - if (!warn) { - /* Old record not needed */ - if (!buffer) - buffer = - (char*)malloc(mftrecsz); - if (buffer) - executed = FALSE; - else - err = 1; - } else { - printf("** %s (action %d) not" - " acting on MFT\n", - actionname(rop), - (int)action->num); - err = 1; - } - } - break; - case ON_INDX : -/* - the check below cannot be used on WinXP -if (!(action->record.attribute_flags & const_cpu_to_le16(ACTS_ON_INDX))) -printf("** %s (action %d) not acting on INDX\n",actionname(rop),(int)action->num); -*/ - xsize = vol->indx_record_size; - /* Check whether data is to be discarded */ - warn = (rop != UpdateNonResidentValue) - || !check_full_index(action,TRUE); - buffer = read_protected(vol, &action->record, - xsize, warn); - indx = (INDEX_BLOCK*)buffer; - if (indx && (indx->magic == magic_INDX)) { - data_lsn = le64_to_cpu(indx->lsn); - /* - * Beware of records not updated - * during the last session which may - * have a stale lsn (consequence - * of ntfs-3g resetting the log) - */ - executed = ((s64)(data_lsn - - this_lsn) >= 0) - && (((s64)(data_lsn - - latest_lsn)) <= 0) - && ! exception(action->num); - } else { - if (!warn) { - /* Old record not needed */ - if (!buffer) - buffer = - (char*)malloc(xsize); - if (buffer) - executed = FALSE; - else - err = 1; - } else { - printf("** %s (action %d) not" - " acting on INDX\n", - actionname(rop), - (int)action->num); - err = 1; - } - } - break; - case ON_RAW : - if (action->record.attribute_flags - & (const_cpu_to_le16( - ACTS_ON_INDX | ACTS_ON_MFT))) { - printf("** Error : action %s on MFT" - " or INDX\n", - actionname(rop)); - err = 1; - } else { - buffer = read_raw(vol, &action->record); - if (!buffer) - err = 1; - } - break; - default : - buffer = (char*)NULL; - break; - } - if (!err && (!executed || !opts)) { - err = distribute_redos(vol, action, buffer); - redocount++; - } else { - if (optv) - printf("Action %d %s (%s) not redone\n", - action->num, - actionname(rop), - actionname(uop)); - } - if (buffer) - free(buffer); - } + && (action->flags & ACTION_TO_REDO)) + err = play_one_redo(vol, action); if (!err) action = action->next; } @@ -4616,6 +4618,192 @@ static int distribute_undos(ntfs_volume *vol, const struct ACTION_RECORD *action return (err); } +int play_one_undo(ntfs_volume *vol, const struct ACTION_RECORD *action) +{ + MFT_RECORD *entry; + INDEX_BLOCK *indx; + char *buffer; + u32 xsize; + u16 rop; + u16 uop; + int err; + BOOL executed; + enum ACTION_KIND kind; + + err = 0; + rop = le16_to_cpu(action->record.redo_operation); + uop = le16_to_cpu(action->record.undo_operation); + if (optv) + printf("Undo action %d %s (%s) lsn 0x%llx\n", + action->num, + actionname(rop), actionname(uop), + (long long)le64_to_cpu( + action->record.this_lsn)); + buffer = (char*)NULL; + executed = FALSE; + kind = ON_NONE; + switch (rop) { + /* Actions always acting on MFT */ + case AddIndexEntryRoot : + case CreateAttribute : + case DeallocateFileRecordSegment : + case DeleteAttribute : + case DeleteIndexEntryRoot : + case InitializeFileRecordSegment : + case SetIndexEntryVcnRoot : + case SetNewAttributeSizes : + case UpdateFileNameRoot : + case UpdateMappingPairs : + case UpdateResidentValue : + case Win10Action37 : + case WriteEndofFileRecordSegment : + kind = ON_MFT; + break; + /* Actions always acting on INDX */ + case AddIndexEntryAllocation : + case DeleteIndexEntryAllocation : + case SetIndexEntryVcnAllocation : + case UpdateFileNameAllocation : + case WriteEndOfIndexBuffer : + kind = ON_INDX; + break; + /* Actions never acting on MFT or INDX */ + case ClearBitsInNonResidentBitMap : + case SetBitsInNonResidentBitMap : + kind = ON_RAW; + break; + /* Actions which may act on MFT */ + case Noop : /* on MFT if DeallocateFileRecordSegment */ + break; + /* Actions which may act on INDX */ + case UpdateNonResidentValue : + /* Known cases : INDX, $SDS, ATTR_LIST */ + kind = get_action_kind(action); + if (kind == ON_NONE) + err = 1; + break; + case OpenNonResidentAttribute : + /* probably not important */ + kind = ON_NONE; + break; + /* Actions currently ignored */ + case AttributeNamesDump : + case CommitTransaction : + case CompensationlogRecord : + case DeleteDirtyClusters : + case DirtyPageTableDump : + case EndTopLevelAction : + case ForgetTransaction : + case HotFix : + case OpenAttributeTableDump : + case PrepareTransaction : + case TransactionTableDump : + case UpdateRecordDataAllocation : + case UpdateRecordDataRoot : + case Win10Action35 : + case Win10Action36 : + kind = ON_NONE; + break; + } + switch (kind) { + case ON_MFT : +/* + the check below cannot be used on WinXP +if (!(action->record.attribute_flags & const_cpu_to_le16(ACTS_ON_MFT))) +printf("** %s (action %d) not acting on MFT\n",actionname(rop),(int)action->num); +*/ + buffer = read_protected(vol, &action->record, mftrecsz, TRUE); + entry = (MFT_RECORD*)buffer; + if (entry) { + if (entry->magic == magic_FILE) { + executed = !older_record(entry, + &action->record); + if (!executed + && exception(action->num)) + executed = TRUE; +if (optv > 1) +printf("record lsn 0x%llx is %s than action %d lsn 0x%llx\n", +(long long)le64_to_cpu(entry->lsn), +(executed ? "not older" : "older"), +(int)action->num, +(long long)le64_to_cpu(action->record.this_lsn)); + } else { + printf("** %s (action %d) not acting on MFT\n", + actionname(rop), (int)action->num); + err = 1; + } + } else { + /* Undoing a record create which was not done ? */ +// TODO make sure this is about a newly allocated record (with bad fixup) +// TODO check this is inputting a full record (record lth == data lth) + buffer = (char*)calloc(1, mftrecsz); + } + break; + case ON_INDX : +/* + the check below cannot be used on WinXP +if (!(action->record.attribute_flags & const_cpu_to_le16(ACTS_ON_INDX))) +printf("** %s (action %d) not acting on INDX\n",actionname(rop),(int)action->num); +*/ + xsize = vol->indx_record_size; + buffer = read_protected(vol, &action->record, xsize, TRUE); + indx = (INDEX_BLOCK*)buffer; + if (indx) { + if (indx->magic == magic_INDX) { + executed = !older_record(indx, + &action->record); + if (!executed + && exception(action->num)) + executed = TRUE; +if (optv > 1) +printf("index lsn 0x%llx is %s than action %d lsn 0x%llx\n", +(long long)le64_to_cpu(indx->lsn), +(executed ? "not older" : "older"), +(int)action->num, +(long long)le64_to_cpu(action->record.this_lsn)); + } else { + printf("** %s (action %d) not acting on INDX\n", + actionname(rop), (int)action->num); + err = 1; + } + } else { + /* Undoing a record create which was not done ? */ +// TODO make sure this is about a newly allocated record (with bad fixup) +// TODO check this is inputting a full record (record lth == data lth) +// recreate an INDX record if this is the first entry + buffer = (char*)calloc(1, xsize); + err = create_indx(vol, action, buffer); + executed = TRUE; + } + break; + case ON_RAW : + if (action->record.attribute_flags + & (const_cpu_to_le16(ACTS_ON_INDX | ACTS_ON_MFT))) { + printf("** Error : action %s on MFT or INDX\n", + actionname(rop)); + err = 1; + } else { + buffer = read_raw(vol, &action->record); + if (!buffer) + err = 1; + } + executed = TRUE; + break; + default : + executed = TRUE; + buffer = (char*)NULL; + break; + } + if (!err && executed) { + err = distribute_undos(vol, action, buffer); + undocount++; + } + if (buffer) + free(buffer); + + return (err); +} + /* * Play the undo actions from latest to earliest * @@ -4629,196 +4817,13 @@ static int distribute_undos(ntfs_volume *vol, const struct ACTION_RECORD *action int play_undos(ntfs_volume *vol, const struct ACTION_RECORD *lastaction) { const struct ACTION_RECORD *action; - MFT_RECORD *entry; - INDEX_BLOCK *indx; - char *buffer; - u32 xsize; - u16 rop; - u16 uop; int err; - BOOL executed; - enum ACTION_KIND kind; err = 0; action = lastaction; while (action && !err) { - if (!optc || within_lcn_range(&action->record)) { - rop = le16_to_cpu(action->record.redo_operation); - uop = le16_to_cpu(action->record.undo_operation); - if (optv) - printf("Undo action %d %s (%s) lsn 0x%llx\n", - action->num, - actionname(rop), actionname(uop), - (long long)le64_to_cpu( - action->record.this_lsn)); - buffer = (char*)NULL; - executed = FALSE; - kind = ON_NONE; - switch (rop) { - /* Actions always acting on MFT */ - case AddIndexEntryRoot : - case CreateAttribute : - case DeallocateFileRecordSegment : - case DeleteAttribute : - case DeleteIndexEntryRoot : - case InitializeFileRecordSegment : - case SetIndexEntryVcnRoot : - case SetNewAttributeSizes : - case UpdateFileNameRoot : - case UpdateMappingPairs : - case UpdateResidentValue : - case Win10Action37 : - case WriteEndofFileRecordSegment : - kind = ON_MFT; - break; - /* Actions always acting on INDX */ - case AddIndexEntryAllocation : - case DeleteIndexEntryAllocation : - case SetIndexEntryVcnAllocation : - case UpdateFileNameAllocation : - case WriteEndOfIndexBuffer : - kind = ON_INDX; - break; - /* Actions never acting on MFT or INDX */ - case ClearBitsInNonResidentBitMap : - case SetBitsInNonResidentBitMap : - kind = ON_RAW; - break; - /* Actions which may act on MFT */ - case Noop : /* on MFT if DeallocateFileRecordSegment */ - break; - /* Actions which may act on INDX */ - case UpdateNonResidentValue : - /* Known cases : INDX, $SDS, ATTR_LIST */ - kind = get_action_kind(action); - if (kind == ON_NONE) - err = 1; - break; - case OpenNonResidentAttribute : - /* probably not important */ - kind = ON_NONE; - break; - /* Actions currently ignored */ - case AttributeNamesDump : - case CommitTransaction : - case CompensationlogRecord : - case DeleteDirtyClusters : - case DirtyPageTableDump : - case EndTopLevelAction : - case ForgetTransaction : - case HotFix : - case OpenAttributeTableDump : - case PrepareTransaction : - case TransactionTableDump : - case UpdateRecordDataAllocation : - case UpdateRecordDataRoot : - case Win10Action35 : - case Win10Action36 : - kind = ON_NONE; - break; - } - switch (kind) { - case ON_MFT : -/* - the check below cannot be used on WinXP -if (!(action->record.attribute_flags & const_cpu_to_le16(ACTS_ON_MFT))) -printf("** %s (action %d) not acting on MFT\n",actionname(rop),(int)action->num); -*/ - buffer = read_protected(vol, &action->record, - mftrecsz, TRUE); - entry = (MFT_RECORD*)buffer; - if (entry) { - if (entry->magic == magic_FILE) { - executed = !older_record(entry, - &action->record); - if (!executed - && exception(action->num)) - executed = TRUE; -if (optv > 1) -printf("record lsn 0x%llx is %s than action %d lsn 0x%llx\n", -(long long)le64_to_cpu(entry->lsn), -(executed ? "not older" : "older"), -(int)action->num, -(long long)le64_to_cpu(action->record.this_lsn)); - } else { - printf("** %s (action %d) not" - " acting on MFT\n", - actionname(rop), - (int)action->num); - err = 1; - } - } else { -// TODO make sure this is about a newly allocated record (with bad fixup) -// TODO check this is inputting a full record (record lth == data lth) - buffer = (char*)malloc(mftrecsz); - } - break; - case ON_INDX : -/* - the check below cannot be used on WinXP -if (!(action->record.attribute_flags & const_cpu_to_le16(ACTS_ON_INDX))) -printf("** %s (action %d) not acting on INDX\n",actionname(rop),(int)action->num); -*/ - xsize = vol->indx_record_size; - buffer = read_protected(vol, &action->record, - xsize, TRUE); - indx = (INDEX_BLOCK*)buffer; - if (indx) { - if (indx->magic == magic_INDX) { - executed = !older_record(indx, - &action->record); - if (!executed - && exception(action->num)) - executed = TRUE; -if (optv > 1) -printf("index lsn 0x%llx is %s than action %d lsn 0x%llx\n", -(long long)le64_to_cpu(indx->lsn), -(executed ? "not older" : "older"), -(int)action->num, -(long long)le64_to_cpu(action->record.this_lsn)); - } else { - printf("** %s (action %d) not" - " acting on INDX\n", - actionname(rop), - (int)action->num); - err = 1; - } - } else { -// TODO make sure this is about a newly allocated record (with bad fixup) -// TODO check this is inputting a full record (record lth == data lth) -// recreate an INDX record if this is the first entry - buffer = (char*)malloc(xsize); - err = create_indx(vol, action, buffer); - executed = TRUE; - } - break; - case ON_RAW : - if (action->record.attribute_flags - & (const_cpu_to_le16( - ACTS_ON_INDX | ACTS_ON_MFT))) { - printf("** Error : action %s on MFT" - " or INDX\n", - actionname(rop)); - err = 1; - } else { - buffer = read_raw(vol, &action->record); - if (!buffer) - err = 1; - } - executed = TRUE; - break; - default : - executed = TRUE; - buffer = (char*)NULL; - break; - } - if (!err && executed) { - err = distribute_undos(vol, action, buffer); - undocount++; - } - if (buffer) - free(buffer); - } + if (!optc || within_lcn_range(&action->record)) + err = play_one_undo(vol, action); if (!err) action = action->prev; } From ede1808ba669a289461cad2d3eef5797cc8fba2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Pierre=20Andr=C3=A9?= Date: Thu, 12 Nov 2015 15:46:33 +0100 Subject: [PATCH 49/61] Removed obsolete code from playlog The code for dealing with new MFT records had been made more general --- ntfsprogs/playlog.c | 23 +---------------------- 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/ntfsprogs/playlog.c b/ntfsprogs/playlog.c index 76a6d140..771f01f7 100644 --- a/ntfsprogs/playlog.c +++ b/ntfsprogs/playlog.c @@ -1991,7 +1991,6 @@ static int redo_create_file(ntfs_volume *vol, LCN lcn; const char *data; MFT_RECORD *record; - const MFT_RECORD *fullrec; u32 target; u32 length; int err; @@ -2034,27 +2033,7 @@ static int redo_create_file(ntfs_volume *vol, (changed ? "updated" : "unchanged")); } } else { -// TODO make sure this was caused by bad fixups - /* - * Could not read protected, assume newly allocated record. - * Check we are creating a full MFT record, and so - * existing data is meaningless. - */ - fullrec = (const MFT_RECORD*)data; - if ((length > offsetof(MFT_RECORD, bytes_in_use)) - && (le32_to_cpu(fullrec->bytes_in_use) <= length) - && (fullrec->magic == magic_FILE) - && !target - && (length <= mftrecsz)) { - buffer = (char*)malloc(mftrecsz); - memcpy(buffer, data, length); - if (optv > 1) { - printf("-> created MFT record :\n"); - dump(buffer, length); - } - err = write_protected(vol, &action->record, - buffer, mftrecsz); - } + err = 1; /* record overflows */ } return (err); } From 3c964b6af3d8172346cf4e8e9a4d593613cd1f13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Pierre=20Andr=C3=A9?= Date: Fri, 20 Nov 2015 16:04:47 +0100 Subject: [PATCH 50/61] Fixed truncating an extended bad cluster list in ntfsresize This fixes the case where the original bad cluster list requires extents. The list is processed globally, no relocation is done, and the list is truncated, possibly fitting into fewer extents. --- ntfsprogs/ntfsresize.c | 149 +++++++++++++++++------------------------ 1 file changed, 60 insertions(+), 89 deletions(-) diff --git a/ntfsprogs/ntfsresize.c b/ntfsprogs/ntfsresize.c index bf7db001..e8409729 100644 --- a/ntfsprogs/ntfsresize.c +++ b/ntfsprogs/ntfsresize.c @@ -2112,10 +2112,17 @@ static int handle_mftdata(ntfs_resize_t *resize, int do_mftdata) static void relocate_attributes(ntfs_resize_t *resize, int do_mftdata) { int ret; + leMFT_REF lemref; + MFT_REF base_mref; if (!(resize->ctx = attr_get_search_ctx(NULL, resize->mrec))) exit(1); + lemref = resize->mrec->base_mft_record; + if (lemref) + base_mref = MREF(le64_to_cpu(lemref)); + else + base_mref = resize->mref; while (!ntfs_attrs_walk(resize->ctx)) { if (resize->ctx->attr->type == AT_END) break; @@ -2133,6 +2140,11 @@ static void relocate_attributes(ntfs_resize_t *resize, int do_mftdata) resize->ctx->attr->type == AT_DATA) continue; + /* Do not relocate bad clusters */ + if ((base_mref == FILE_BadClus) + && (resize->ctx->attr->type == AT_DATA)) + continue; + relocate_attribute(resize); } @@ -2310,60 +2322,6 @@ static void advise_on_resize(ntfs_resize_t *resize) print_advise(vol, resize->last_unsupp); } -static void rl_expand(runlist **rl, const VCN last_vcn) -{ - int len; - runlist *p = *rl; - - len = rl_items(p) - 1; - if (len <= 0) - err_exit("rl_expand: bad runlist length: %d\n", len); - - if (p[len].vcn > last_vcn) - err_exit("rl_expand: length is already more than requested " - "(%lld > %lld)\n", - (long long)p[len].vcn, (long long)last_vcn); - - if (p[len - 1].lcn == LCN_HOLE) { - - p[len - 1].length += last_vcn - p[len].vcn; - p[len].vcn = last_vcn; - - } else if (p[len - 1].lcn >= 0) { - - p = realloc(*rl, (++len + 1) * sizeof(runlist_element)); - if (!p) - perr_exit("rl_expand: realloc"); - - p[len - 1].lcn = LCN_HOLE; - p[len - 1].length = last_vcn - p[len - 1].vcn; - rl_set(p + len, last_vcn, LCN_ENOENT, 0LL); - *rl = p; - - } else - err_exit("rl_expand: bad LCN: %lld\n", - (long long)p[len - 1].lcn); -} - -static void rl_truncate(runlist **rl, const VCN last_vcn) -{ - int len; - VCN vcn; - - len = rl_items(*rl) - 1; - if (len <= 0) - err_exit("rl_truncate: bad runlist length: %d\n", len); - - vcn = (*rl)[len].vcn; - - if (vcn < last_vcn) - rl_expand(rl, last_vcn); - - else if (vcn > last_vcn) - if (ntfs_rl_truncate(rl, last_vcn) == -1) - perr_exit("ntfs_rl_truncate"); -} - /** * bitmap_file_data_fixup * @@ -2376,6 +2334,37 @@ static void bitmap_file_data_fixup(s64 cluster, struct bitmap *bm) ntfs_bit_set(bm->bm, (u64)cluster, 1); } +/* + * Open the attribute $BadClust:$Bad and get its runlist + */ + +static ntfs_attr *open_badclust_bad_attr(ntfs_attr_search_ctx *ctx) +{ + ntfs_inode *base_ni; + ntfs_attr *na; + static ntfschar Bad[4] = { + const_cpu_to_le16('$'), const_cpu_to_le16('B'), + const_cpu_to_le16('a'), const_cpu_to_le16('d') + } ; + + base_ni = ctx->base_ntfs_ino; + if (!base_ni) + base_ni = ctx->ntfs_ino; + + na = ntfs_attr_open(base_ni, AT_DATA, Bad, 4); + if (!na) { + err_printf("Could not access the bad sector list\n"); + } else { + if (ntfs_attr_map_whole_runlist(na) || !na->rl) { + err_printf("Could not decode the bad sector list\n"); + ntfs_attr_close(na); + ntfs_inode_close(base_ni); + na = (ntfs_attr*)NULL; + } + } + return (na); +} + /** * truncate_badclust_bad_attr * @@ -2386,27 +2375,26 @@ static void bitmap_file_data_fixup(s64 cluster, struct bitmap *bm) */ static void truncate_badclust_bad_attr(ntfs_resize_t *resize) { - ATTR_RECORD *a; - runlist *rl_bad; + ntfs_inode *base_ni; + ntfs_attr *na; s64 nr_clusters = resize->new_volume_size; ntfs_volume *vol = resize->vol; - a = resize->ctx->attr; - if (!a->non_resident) - /* FIXME: handle resident attribute value */ - err_exit("Resident attribute in $BadClust isn't supported!\n"); + na = open_badclust_bad_attr(resize->ctx); + if (!na) { + err_printf("Could not access the bad sector list\n"); + exit(1); + } + base_ni = na->ni; + if (ntfs_attr_truncate(na,nr_clusters << vol->cluster_size_bits)) { + err_printf("Could not adjust the bad sector list\n"); + exit(1); + } + na->ni->flags |= FILE_ATTR_SPARSE_FILE; + NInoFileNameSetDirty(na->ni); - if (!(rl_bad = ntfs_mapping_pairs_decompress(vol, a, NULL))) - perr_exit("ntfs_mapping_pairs_decompress"); - - rl_truncate(&rl_bad, nr_clusters); - - a->highest_vcn = cpu_to_sle64(nr_clusters - 1LL); - a->allocated_size = cpu_to_sle64(nr_clusters * vol->cluster_size); - a->data_size = cpu_to_sle64(nr_clusters * vol->cluster_size); - - if (!replace_attribute_runlist(resize, rl_bad)) - free(rl_bad); + ntfs_attr_close(na); + ntfs_inode_mark_dirty(base_ni); } /** @@ -2568,32 +2556,19 @@ static void close_inode_and_context(ntfs_attr_search_ctx *ctx) static int check_bad_sectors(ntfs_volume *vol) { ntfs_attr_search_ctx *ctx; - ntfs_inode *base_ni; ntfs_attr *na; runlist *rl; s64 i, badclusters = 0; - static le16 Bad[4] = { - const_cpu_to_le16('$'), const_cpu_to_le16('B'), - const_cpu_to_le16('a'), const_cpu_to_le16('d') - } ; ntfs_log_verbose("Checking for bad sectors ...\n"); lookup_data_attr(vol, FILE_BadClus, "$Bad", &ctx); - base_ni = ctx->base_ntfs_ino; - if (!base_ni) - base_ni = ctx->ntfs_ino; - - na = ntfs_attr_open(base_ni, AT_DATA, Bad, 4); + na = open_badclust_bad_attr(ctx); if (!na) { err_printf("Could not access the bad sector list\n"); exit(1); } - if (ntfs_attr_map_whole_runlist(na) || !na->rl) { - err_printf("Could not decode the bad sector list\n"); - exit(1); - } rl = na->rl; for (i = 0; rl[i].length; i++) { /* CHECKME: LCN_RL_NOT_MAPPED check isn't needed */ @@ -2644,10 +2619,6 @@ static void truncate_badclust_file(ntfs_resize_t *resize) resize->mref = FILE_BadClus; truncate_badclust_bad_attr(resize); - if (write_mft_record(resize->vol, resize->ctx->ntfs_ino->mft_no, - resize->ctx->mrec)) - perr_exit("Couldn't update $BadClust"); - #if CLEAN_EXIT close_inode_and_context(resize->ctx); #else From aade4c46b67f8b36976b55fbf35b0d9808500a4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Pierre=20Andr=C3=A9?= Date: Fri, 20 Nov 2015 16:11:15 +0100 Subject: [PATCH 51/61] Fixed missing error return in playlog No error was returned from reading a protected record which is part of an unreadable raw cluster. --- ntfsprogs/playlog.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ntfsprogs/playlog.c b/ntfsprogs/playlog.c index 771f01f7..0481435d 100644 --- a/ntfsprogs/playlog.c +++ b/ntfsprogs/playlog.c @@ -691,7 +691,8 @@ static int write_protected(ntfs_volume *vol, const struct LOG_RECORD *logr, err = write_mirr(vol, logr, full); free(full); - } + } else + err = 1; } else { /* write full clusters */ err = write_raw(vol, logr, buffer); From f85b82c8e1fd4de3dc5fffd7c4ba5d0d8e79ba09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Pierre=20Andr=C3=A9?= Date: Fri, 20 Nov 2015 16:14:16 +0100 Subject: [PATCH 52/61] Fixed headers of log play functions in playlog The functions were made static with a textual description. --- ntfsprogs/playlog.c | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/ntfsprogs/playlog.c b/ntfsprogs/playlog.c index 0481435d..3bf83075 100644 --- a/ntfsprogs/playlog.c +++ b/ntfsprogs/playlog.c @@ -4214,7 +4214,18 @@ static int distribute_redos(ntfs_volume *vol, return (err); } -int play_one_redo(ntfs_volume *vol, const struct ACTION_RECORD *action) +/* + * Redo a single action + * + * The record the action acts on is read and, when it is an MFT or + * INDX one, the need for redoing is checked. + * + * When this is an action which creates a new MFT or INDX record + * and the old one cannot be read (usually because it was not + * initialized), a zeroed buffer is allocated. + */ + +static int play_one_redo(ntfs_volume *vol, const struct ACTION_RECORD *action) { MFT_RECORD *entry; INDEX_BLOCK *indx; @@ -4598,7 +4609,14 @@ static int distribute_undos(ntfs_volume *vol, const struct ACTION_RECORD *action return (err); } -int play_one_undo(ntfs_volume *vol, const struct ACTION_RECORD *action) +/* + * Undo a single action + * + * The record the action acts on is read and, when it is an MFT or + * INDX one, the need for undoing is checked. + */ + +static int play_one_undo(ntfs_volume *vol, const struct ACTION_RECORD *action) { MFT_RECORD *entry; INDEX_BLOCK *indx; From aeb1d7fb74b61ad5867638b4c8b6b8259b8e1ce1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Pierre=20Andr=C3=A9?= Date: Fri, 20 Nov 2015 16:17:48 +0100 Subject: [PATCH 53/61] Fixed special case of decompressing a runlist When the unreadable directory has an ATTRIBUTE_LIST attribute and an INDEX_ALLOCATION attribute occupying split over several extents, the first of which defines a single cluster, the first INDEX_ALLOCATION extent has lowest_vcn=0 and highest_vcn=0, and the second one has lowest_vcn=1. This unusual case, which can be created by the combination of a small volume and near-full MFT records, triggers some special-case behavior in ntfs_mapping_pairs_decompress_i(). That behavior is incorrect if the attribute's first extent only contains a single cluster, since in that case highest_vcn=0 as well. This configuration has been tested on Windows and it *is* able to successfully read the directory. This supports the hypothesis that the volume is valid and NTFS-3g has a bug on the read side. This bug could, in theory, occur with any non-resident attribute, not just INDEX_ALLOCATION attributes. (Contributed by Eric Biggers) --- libntfs-3g/runlist.c | 65 ++++++++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 30 deletions(-) diff --git a/libntfs-3g/runlist.c b/libntfs-3g/runlist.c index 7e158d44..c83c2b7d 100644 --- a/libntfs-3g/runlist.c +++ b/libntfs-3g/runlist.c @@ -939,40 +939,45 @@ mpa_err: "attribute.\n"); goto err_out; } - /* Setup not mapped runlist element if this is the base extent. */ - if (!attr->lowest_vcn) { - VCN max_cluster; - max_cluster = ((sle64_to_cpu(attr->allocated_size) + + /* + * If this is the base of runlist (if 'lowest_vcn' is 0), then + * 'allocated_size' is valid, and we can use it to compute the total + * number of clusters across all extents. If the runlist covers all + * clusters, then it fits into a single extent and we can terminate + * the runlist with LCN_NOENT. Otherwise, we must terminate the runlist + * with LCN_RL_NOT_MAPPED and let the caller look for more extents. + */ + if (!attr->lowest_vcn) { + VCN num_clusters; + + num_clusters = ((sle64_to_cpu(attr->allocated_size) + vol->cluster_size - 1) >> - vol->cluster_size_bits) - 1; - /* - * A highest_vcn of zero means this is a single extent - * attribute so simply terminate the runlist with LCN_ENOENT). - */ - if (deltaxcn) { + vol->cluster_size_bits); + + if (num_clusters > vcn) { /* - * If there is a difference between the highest_vcn and - * the highest cluster, the runlist is either corrupt - * or, more likely, there are more extents following - * this one. + * The runlist doesn't cover all the clusters, so there + * must be more extents. */ - if (deltaxcn < max_cluster) { - ntfs_log_debug("More extents to follow; deltaxcn = " - "0x%llx, max_cluster = 0x%llx\n", - (long long)deltaxcn, - (long long)max_cluster); - rl[rlpos].vcn = vcn; - vcn += rl[rlpos].length = max_cluster - deltaxcn; - rl[rlpos].lcn = (LCN)LCN_RL_NOT_MAPPED; - rlpos++; - } else if (deltaxcn > max_cluster) { - ntfs_log_debug("Corrupt attribute. deltaxcn = " - "0x%llx, max_cluster = 0x%llx\n", - (long long)deltaxcn, - (long long)max_cluster); - goto mpa_err; - } + ntfs_log_debug("More extents to follow; vcn = 0x%llx, " + "num_clusters = 0x%llx\n", + (long long)vcn, + (long long)num_clusters); + rl[rlpos].vcn = vcn; + vcn += rl[rlpos].length = num_clusters - vcn; + rl[rlpos].lcn = (LCN)LCN_RL_NOT_MAPPED; + rlpos++; + } else if (vcn > num_clusters) { + /* + * There are more VCNs in the runlist than expected, so + * the runlist is corrupt. + */ + ntfs_log_error("Corrupt attribute. vcn = 0x%llx, " + "num_clusters = 0x%llx\n", + (long long)vcn, + (long long)num_clusters); + goto mpa_err; } rl[rlpos].lcn = (LCN)LCN_ENOENT; } else /* Not the base extent. There may be more extents to follow. */ From 730776b0e5669e53bfdfcae9cf38e3958bdb773c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Pierre=20Andr=C3=A9?= Date: Tue, 1 Dec 2015 10:56:01 +0100 Subject: [PATCH 54/61] Defined reparse tag for system compression The new compression formats used by Windows 10 uses reparse data, and a new reparse tag which it is useful to define even though these formats is not yet supported by ntfs-3g. --- include/ntfs-3g/layout.h | 1 + ntfsprogs/ntfsinfo.c | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/include/ntfs-3g/layout.h b/include/ntfs-3g/layout.h index 5b5fff6e..58684f32 100644 --- a/include/ntfs-3g/layout.h +++ b/include/ntfs-3g/layout.h @@ -2409,6 +2409,7 @@ typedef enum { IO_REPARSE_TAG_SIS = const_cpu_to_le32(0x80000007), IO_REPARSE_TAG_SYMLINK = const_cpu_to_le32(0xA000000C), IO_REPARSE_TAG_WIM = const_cpu_to_le32(0x80000008), + IO_REPARSE_TAG_WOF = const_cpu_to_le32(0x80000017), IO_REPARSE_TAG_VALID_VALUES = const_cpu_to_le32(0xf000ffff), } PREDEFINED_REPARSE_TAGS; diff --git a/ntfsprogs/ntfsinfo.c b/ntfsprogs/ntfsinfo.c index 1db412a6..d688d00e 100644 --- a/ntfsprogs/ntfsinfo.c +++ b/ntfsprogs/ntfsinfo.c @@ -411,7 +411,6 @@ static char *ntfs_attr_get_name_mbs(ATTR_RECORD *attr) static const char *reparse_type_name(le32 tag) { const char *name; - le32 IO_REPARSE_TAG_WOF = const_cpu_to_le32(0x80000017); /* temporary */ if (tag == IO_REPARSE_TAG_MOUNT_POINT) name = " (mount point)"; From 4d5ce43ab93fe579093bcc71ce0c75e3352775cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Pierre=20Andr=C3=A9?= Date: Tue, 1 Dec 2015 11:00:24 +0100 Subject: [PATCH 55/61] Fixed returning the trimming count to fstrim(8) When used with the option -v, fstrim(8) reported the maximum trimming count because the correct value was not returned to the ioctl call. --- libntfs-3g/ioctl.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/libntfs-3g/ioctl.c b/libntfs-3g/ioctl.c index 3bd0c0cd..c350164b 100644 --- a/libntfs-3g/ioctl.c +++ b/libntfs-3g/ioctl.c @@ -3,8 +3,8 @@ * * This module is part of ntfs-3g library * - * Copyright (c) 2014 Jean-Pierre Andre - * Copyright (c) 2014 Red Hat, Inc. + * Copyright (c) 2014-2015 Jean-Pierre Andre + * Copyright (c) 2014 Red Hat, Inc. * * This program/include file is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as published @@ -232,7 +232,7 @@ not_found: * are found and TRIM requests are sent to the block device. 'minlen' * is the minimum continguous free range to discard. */ -static int fstrim(ntfs_volume *vol, void *data) +static int fstrim(ntfs_volume *vol, void *data, u64 *trimmed) { struct fstrim_range *range = data; u64 start = range->start; @@ -248,6 +248,8 @@ static int fstrim(ntfs_volume *vol, void *data) (unsigned long long) len, (unsigned long long) minlen); + *trimmed = 0; + /* Fail if user tries to use the fstrim -o/-l/-m options. * XXX We could fix these limitations in future. */ @@ -341,6 +343,8 @@ static int fstrim(ntfs_volume *vol, void *data) if (ret) goto free_out; + *trimmed += (end_lcn - start_lcn) + << vol->cluster_size_bits; start_lcn = end_lcn-1; } } @@ -364,8 +368,13 @@ int ntfs_ioctl(ntfs_inode *ni, int cmd, void *arg __attribute__((unused)), case FITRIM: if (!ni || !data) ret = -EINVAL; - else - ret = fstrim(ni->vol, data); + else { + u64 trimmed; + struct fstrim_range *range = (struct fstrim_range*)data; + + ret = fstrim(ni->vol, data, &trimmed); + range->len = trimmed; + } break; #else #warning Trimming not supported : FITRIM or BLKDISCARD not defined From f7cbf30d5451d6c184913214f54cf15da378eae8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Pierre=20Andr=C3=A9?= Date: Tue, 1 Dec 2015 11:06:11 +0100 Subject: [PATCH 56/61] Rejected invalid null reparse tag The null reparse tag is considered invalid by Windows, so do the same. --- libntfs-3g/reparse.c | 1 + 1 file changed, 1 insertion(+) diff --git a/libntfs-3g/reparse.c b/libntfs-3g/reparse.c index 2198b491..8293cc02 100644 --- a/libntfs-3g/reparse.c +++ b/libntfs-3g/reparse.c @@ -442,6 +442,7 @@ static BOOL valid_reparse_data(ntfs_inode *ni, ok = ni && reparse_attr && (size >= sizeof(REPARSE_POINT)) + && (reparse_attr->reparse_tag != IO_REPARSE_TAG_RESERVED_ZERO) && (((size_t)le16_to_cpu(reparse_attr->reparse_data_length) + sizeof(REPARSE_POINT) + ((reparse_attr->reparse_tag & From ca70766dc49d1ef6b61a8e5c6454a63bf8ceb1ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Pierre=20Andr=C3=A9?= Date: Tue, 1 Dec 2015 11:10:48 +0100 Subject: [PATCH 57/61] Fixed reporting action states when restart page appears outdated If start buffer is more recent than restart, we update committed LSN with last record LSN of block (last_end_lsn) while applying action but forget about it while printing records with -f for investigation purpose. Note that while applying actions we use start_buffer to calculate latest page out of block 2 and block 3 and then from latest take committed LSN. For -f we don't need buffers so we just compare directly with committed LSN from restart. (contributed by Rakesh Pandit) --- ntfsprogs/ntfsrecover.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/ntfsprogs/ntfsrecover.c b/ntfsprogs/ntfsrecover.c index 567db151..47e01d74 100644 --- a/ntfsprogs/ntfsrecover.c +++ b/ntfsprogs/ntfsrecover.c @@ -3720,6 +3720,20 @@ static int walk(CONTEXT *ctx) } if (!done) { buf = nextbuf; + if (blk >= RSTBLKS && blk < BASEBLKS) { + /* The latest buf may be more recent + than restart */ + rph = &buf->block.record; + if ((s64)(le64_to_cpu(rph->last_end_lsn) + - committed_lsn) > 0) { + committed_lsn = + le64_to_cpu(rph->last_end_lsn); + if (optv) + printf("* Restart page was " + "obsolete, updated " + "committed lsn\n"); + } + } if (optv) printf("\n* block %d at 0x%llx\n",(int)blk, (long long)loclogblk(ctx, blk)); From 5efc87cce89e46b73af5467da21a527fcc0f5043 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Pierre=20Andr=C3=A9?= Date: Tue, 1 Dec 2015 11:12:53 +0100 Subject: [PATCH 58/61] Fixed accessing next log buffer only when it exists Do not locate the next log buffer until it is known to exist. --- ntfsprogs/ntfsrecover.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ntfsprogs/ntfsrecover.c b/ntfsprogs/ntfsrecover.c index 47e01d74..5d3f4180 100644 --- a/ntfsprogs/ntfsrecover.c +++ b/ntfsprogs/ntfsrecover.c @@ -3381,7 +3381,6 @@ static TRISTATE backward_rcrd(CONTEXT *ctx, u32 blk, int skipped, if (prevbuf) prevrph = &prevbuf->block.record; data = buf->block.data; - nextdata = nextbuf->block.data; if (rph && (rph->head.magic == magic_RCRD) && (!prevrph || (prevrph->head.magic == magic_RCRD))) { if (optv) { @@ -3465,6 +3464,7 @@ static TRISTATE backward_rcrd(CONTEXT *ctx, u32 blk, int skipped, && ((k == endoff) || !endoff) && ((u32)(k + LOG_RECORD_HEAD_SZ) <= blocksz)) { if (nextbuf && (blk >= BASEBLKS)) { + nextdata = nextbuf->block.data; state = backoverlap(ctx, blk, data, nextdata, k); } From 81fce3b70ec1f59a4cbeca3063d6fdf644b2df95 Mon Sep 17 00:00:00 2001 From: Erik Larsson Date: Fri, 29 Jan 2016 10:49:25 +0100 Subject: [PATCH 59/61] playlog.c: Fix improper byteswapping macros used for sle64 members. --- ntfsprogs/playlog.c | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/ntfsprogs/playlog.c b/ntfsprogs/playlog.c index 3bf83075..a9932807 100644 --- a/ntfsprogs/playlog.c +++ b/ntfsprogs/playlog.c @@ -82,10 +82,10 @@ struct STORE { struct STORE *cluster_door = (struct STORE*)NULL; /* check whether a MFT or INDX record is older than action */ -#define older_record(rec, logr) ((s64)(le64_to_cpu((rec)->lsn) \ +#define older_record(rec, logr) ((s64)(sle64_to_cpu((rec)->lsn) \ - le64_to_cpu((logr)->this_lsn)) < 0) /* check whether a MFT or INDX record is newer than action */ -#define newer_record(rec, logr) ((s64)(le64_to_cpu((rec)->lsn) \ +#define newer_record(rec, logr) ((s64)(sle64_to_cpu((rec)->lsn) \ - le64_to_cpu((logr)->this_lsn)) > 0) /* @@ -634,8 +634,8 @@ static int write_protected(ntfs_volume *vol, const struct LOG_RECORD *logr, printf("update inode %ld lsn 0x%llx" " (record %s than action 0x%llx)\n", (long)le32_to_cpu(record->mft_record_number), - (long long)le64_to_cpu(record->lsn), - ((s64)(le64_to_cpu(record->lsn) + (long long)sle64_to_cpu(record->lsn), + ((s64)(sle64_to_cpu(record->lsn) - le64_to_cpu(logr->this_lsn)) < 0 ? "older" : "newer"), (long long)le64_to_cpu(logr->this_lsn)); @@ -659,8 +659,8 @@ static int write_protected(ntfs_volume *vol, const struct LOG_RECORD *logr, if (optv) printf("update index lsn 0x%llx" " (index %s than action 0x%llx)\n", - (long long)le64_to_cpu(indx->lsn), - ((s64)(le64_to_cpu(indx->lsn) + (long long)sle64_to_cpu(indx->lsn), + ((s64)(sle64_to_cpu(indx->lsn) - le64_to_cpu(logr->this_lsn)) < 0 ? "older" : "newer"), (long long)le64_to_cpu(logr->this_lsn)); @@ -790,14 +790,14 @@ static int adjust_high_vcn(ntfs_volume *vol, ATTR_RECORD *attr) int err; err = 1; - attr->highest_vcn = cpu_to_le64(0); + attr->highest_vcn = cpu_to_sle64(0); rl = ntfs_mapping_pairs_decompress(vol, attr, (runlist_element*)NULL); if (rl) { xrl = rl; while (xrl->length) xrl++; high_vcn = xrl->vcn - 1; - attr->highest_vcn = cpu_to_le64(high_vcn); + attr->highest_vcn = cpu_to_sle64(high_vcn); free(rl); err = 0; } else { @@ -1644,13 +1644,13 @@ static int insert_index_allocation(ntfs_volume *vol, char *buffer, u32 offs) attr->instance = record->next_attr_instance; instance = le16_to_cpu(record->next_attr_instance) + 1; record->next_attr_instance = cpu_to_le16(instance); - attr->lowest_vcn = const_cpu_to_le64(0); - attr->highest_vcn = const_cpu_to_le64(0); + attr->lowest_vcn = const_cpu_to_sle64(0); + attr->highest_vcn = const_cpu_to_sle64(0); attr->mapping_pairs_offset = cpu_to_le16( 2*namelength + 0x40); attr->compression_unit = 0; xsize = vol->indx_record_size; - attr->allocated_size = cpu_to_le64(xsize); + attr->allocated_size = cpu_to_sle64(xsize); attr->data_size = attr->allocated_size; attr->initialized_size = attr->allocated_size; /* @@ -1788,7 +1788,7 @@ static int create_indx(ntfs_volume *vol, const struct ACTION_RECORD *action, indx->lsn = action->record.this_lsn; vcn = le32_to_cpu(action->record.target_vcn); /* beware of size change on big-endian cpus */ - indx->index_block_vcn = cpu_to_le64(vcn); + indx->index_block_vcn = cpu_to_sle64(vcn); /* INDEX_HEADER */ indx->index.entries_offset = const_cpu_to_le32(0x28); indx->index.index_length = const_cpu_to_le32(0x38); @@ -4335,7 +4335,7 @@ printf("** %s (action %d) not acting on MFT\n",actionname(rop),(int)action->num) mftrecsz, warn); entry = (MFT_RECORD*)buffer; if (entry && (entry->magic == magic_FILE)) { - data_lsn = le64_to_cpu(entry->lsn); + data_lsn = sle64_to_cpu(entry->lsn); /* * Beware of records not updated * during the last session which may @@ -4377,7 +4377,7 @@ printf("** %s (action %d) not acting on INDX\n",actionname(rop),(int)action->num xsize, warn); indx = (INDEX_BLOCK*)buffer; if (indx && (indx->magic == magic_INDX)) { - data_lsn = le64_to_cpu(indx->lsn); + data_lsn = sle64_to_cpu(indx->lsn); /* * Beware of records not updated * during the last session which may @@ -4721,7 +4721,7 @@ printf("** %s (action %d) not acting on MFT\n",actionname(rop),(int)action->num) executed = TRUE; if (optv > 1) printf("record lsn 0x%llx is %s than action %d lsn 0x%llx\n", -(long long)le64_to_cpu(entry->lsn), +(long long)sle64_to_cpu(entry->lsn), (executed ? "not older" : "older"), (int)action->num, (long long)le64_to_cpu(action->record.this_lsn)); @@ -4755,7 +4755,7 @@ printf("** %s (action %d) not acting on INDX\n",actionname(rop),(int)action->num executed = TRUE; if (optv > 1) printf("index lsn 0x%llx is %s than action %d lsn 0x%llx\n", -(long long)le64_to_cpu(indx->lsn), +(long long)sle64_to_cpu(indx->lsn), (executed ? "not older" : "older"), (int)action->num, (long long)le64_to_cpu(action->record.this_lsn)); From ee4c48f4b14d75a3fbde3894ba8dc33f0343a909 Mon Sep 17 00:00:00 2001 From: Erik Larsson Date: Fri, 29 Jan 2016 10:49:52 +0100 Subject: [PATCH 60/61] playlog.c: Use const macro for byteswapping const expression. --- ntfsprogs/playlog.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ntfsprogs/playlog.c b/ntfsprogs/playlog.c index a9932807..642998b4 100644 --- a/ntfsprogs/playlog.c +++ b/ntfsprogs/playlog.c @@ -790,7 +790,7 @@ static int adjust_high_vcn(ntfs_volume *vol, ATTR_RECORD *attr) int err; err = 1; - attr->highest_vcn = cpu_to_sle64(0); + attr->highest_vcn = const_cpu_to_sle64(0); rl = ntfs_mapping_pairs_decompress(vol, attr, (runlist_element*)NULL); if (rl) { xrl = rl; From a2efc3ec9f7db1910cddb9ff688992113aec703b Mon Sep 17 00:00:00 2001 From: Erik Larsson Date: Fri, 29 Jan 2016 12:36:06 +0100 Subject: [PATCH 61/61] Change type of all LSN struct members in ntfsrecover.h to leLSN. This is done to match the type of the LSN struct members in layout.h. The effect of this change is that while these members were declared with the le64 type previously, leLSN resolves to sle64. I.e. what was previously unsigned fields are now signed. Following this change we also need to switch over a few macros from unsigned to signed versions in the code that uses these struct definitions. --- ntfsprogs/ntfsrecover.c | 100 ++++++++++++++++++++-------------------- ntfsprogs/ntfsrecover.h | 30 ++++++------ ntfsprogs/playlog.c | 24 +++++----- 3 files changed, 77 insertions(+), 77 deletions(-) diff --git a/ntfsprogs/ntfsrecover.c b/ntfsprogs/ntfsrecover.c index 5d3f4180..928f276c 100644 --- a/ntfsprogs/ntfsrecover.c +++ b/ntfsprogs/ntfsrecover.c @@ -1155,14 +1155,14 @@ void copy_attribute(struct ATTR *pa, const char *buf, int length) case sizeof(struct ATTR_NEW) : panew = (const struct ATTR_NEW*)buf; pa->type = panew->type; - pa->lsn = le64_to_cpu(panew->lsn); + pa->lsn = sle64_to_cpu(panew->lsn); pa->inode = MREF(le64_to_cpu(panew->inode)); break; case sizeof(struct ATTR_OLD) : /* Badly aligned, first realign */ memcpy(&old_aligned,buf,sizeof(old_aligned)); pa->type = old_aligned.type; - pa->lsn = le64_to_cpu(old_aligned.lsn); + pa->lsn = sle64_to_cpu(old_aligned.lsn); pa->inode = MREF(le64_to_cpu(old_aligned.inode)); break; default : @@ -2080,13 +2080,13 @@ static void detaillogr(CONTEXT *ctx, const struct LOG_RECORD *logr) printf("redo_length %04x\n", (int)le16_to_cpu(logr->redo_length)); printf("transaction_lsn %016llx\n", - (long long)le64_to_cpu(logr->transaction_lsn)); + (long long)sle64_to_cpu(logr->transaction_lsn)); printf("attributes_lsn %016llx\n", - (long long)le64_to_cpu(logr->attributes_lsn)); + (long long)sle64_to_cpu(logr->attributes_lsn)); printf("names_lsn %016llx\n", - (long long)le64_to_cpu(logr->names_lsn)); + (long long)sle64_to_cpu(logr->names_lsn)); printf("dirty_pages_lsn %016llx\n", - (long long)le64_to_cpu(logr->dirty_pages_lsn)); + (long long)sle64_to_cpu(logr->dirty_pages_lsn)); listsize = le32_to_cpu(logr->client_data_length) + LOG_RECORD_HEAD_SZ - offsetof(struct LOG_RECORD, unknown_list); @@ -2135,15 +2135,15 @@ static void showlogr(CONTEXT *ctx, int k, const struct LOG_RECORD *logr) s32 diff; if (optv && (!optc || within_lcn_range(logr))) { - diff = le64_to_cpu(logr->this_lsn) - synced_lsn; + diff = sle64_to_cpu(logr->this_lsn) - synced_lsn; printf("this_lsn %016llx (synced%s%ld) %s\n", - (long long)le64_to_cpu(logr->this_lsn), + (long long)sle64_to_cpu(logr->this_lsn), (diff < 0 ? "" : "+"),(long)diff, commitment(diff + synced_lsn)); printf("client_previous_lsn %016llx\n", - (long long)le64_to_cpu(logr->client_previous_lsn)); + (long long)sle64_to_cpu(logr->client_previous_lsn)); printf("client_undo_next_lsn %016llx\n", - (long long)le64_to_cpu(logr->client_undo_next_lsn)); + (long long)sle64_to_cpu(logr->client_undo_next_lsn)); printf("client_data_length %08lx\n", (long)le32_to_cpu(logr->client_data_length)); printf("seq_number %d\n", @@ -2168,28 +2168,28 @@ static void showlogr(CONTEXT *ctx, int k, const struct LOG_RECORD *logr) if (logr->record_type == const_cpu_to_le32(2)) state = "--checkpoint--"; else - state = commitment(le64_to_cpu(logr->this_lsn)); + state = commitment(sle64_to_cpu(logr->this_lsn)); printf(" at %04x %016llx %s (%ld) %s\n",k, - (long long)le64_to_cpu(logr->this_lsn), + (long long)sle64_to_cpu(logr->this_lsn), state, - (long)(le64_to_cpu(logr->this_lsn) - synced_lsn), + (long)(sle64_to_cpu(logr->this_lsn) - synced_lsn), actionname(le16_to_cpu(logr->redo_operation))); if (logr->client_previous_lsn || logr->client_undo_next_lsn) { if (logr->client_previous_lsn == logr->client_undo_next_lsn) { printf(" " " previous and undo %016llx\n", - (long long)le64_to_cpu( + (long long)sle64_to_cpu( logr->client_previous_lsn)); } else { printf(" " " previous %016llx", - (long long)le64_to_cpu( + (long long)sle64_to_cpu( logr->client_previous_lsn)); if (logr->client_undo_next_lsn) printf(" undo %016llx\n", - (long long)le64_to_cpu( + (long long)sle64_to_cpu( logr->client_undo_next_lsn)); else printf("\n"); @@ -2227,7 +2227,7 @@ static void mark_transactions(struct ACTION_RECORD *lastaction) printf("Marking transaction 0x%x\n", (int)le32_to_cpu(id)); } - committed = ((s64)(le64_to_cpu(logr->this_lsn) + committed = ((s64)(sle64_to_cpu(logr->this_lsn) - committed_lsn)) <= 0; if (!logr->transaction_id && committed) @@ -2334,7 +2334,7 @@ static TRISTATE enqueue_action(CONTEXT *ctx, const struct LOG_RECORD *logr, ctx->lastaction = (struct ACTION_RECORD*)NULL; } if (opts - && ((s64)(le64_to_cpu(logr->this_lsn) - synced_lsn) <= 0)) { + && ((s64)(sle64_to_cpu(logr->this_lsn) - synced_lsn) <= 0)) { if (optv) printf("* Refreshing attributes\n"); // should refresh backward ? @@ -2373,10 +2373,10 @@ static void showheadrcrd(u32 blk, const struct RECORD_PAGE_HEADER *rph) printf("file_offset %08lx\n", (long)le32_to_cpu(rph->copy.file_offset)); else { - diff = le64_to_cpu(rph->copy.last_lsn) - synced_lsn; + diff = sle64_to_cpu(rph->copy.last_lsn) - synced_lsn; printf("last_lsn %016llx" " (synced%s%ld)\n", - (long long)le64_to_cpu(rph->copy.last_lsn), + (long long)sle64_to_cpu(rph->copy.last_lsn), (diff < 0 ? "" : "+"),(long)diff); } printf("flags %08lx\n", @@ -2391,9 +2391,9 @@ static void showheadrcrd(u32 blk, const struct RECORD_PAGE_HEADER *rph) (int)le16_to_cpu(rph->reserved4[0]), (int)le16_to_cpu(rph->reserved4[1]), (int)le16_to_cpu(rph->reserved4[2])); - diff = le64_to_cpu(rph->last_end_lsn) - synced_lsn; + diff = sle64_to_cpu(rph->last_end_lsn) - synced_lsn; printf("last_end_lsn %016llx (synced%s%ld)\n", - (long long)le64_to_cpu(rph->last_end_lsn), + (long long)sle64_to_cpu(rph->last_end_lsn), (diff < 0 ? "" : "+"),(long)diff); printf("usn %04x\n", (int)getle16(rph,le16_to_cpu(rph->head.usa_ofs))); @@ -2402,15 +2402,15 @@ static void showheadrcrd(u32 blk, const struct RECORD_PAGE_HEADER *rph) if (optt) { const char *state; - state = commitment(le64_to_cpu(rph->copy.last_lsn)); - diff = le64_to_cpu(rph->copy.last_lsn) - synced_lsn; + state = commitment(sle64_to_cpu(rph->copy.last_lsn)); + diff = sle64_to_cpu(rph->copy.last_lsn) - synced_lsn; printf(" last %016llx (synced%s%ld) %s\n", - (long long)le64_to_cpu(rph->copy.last_lsn), + (long long)sle64_to_cpu(rph->copy.last_lsn), (diff < 0 ? "" : "+"),(long)diff, state); - state = commitment(le64_to_cpu(rph->last_end_lsn)); - diff = le64_to_cpu(rph->last_end_lsn) - synced_lsn; + state = commitment(sle64_to_cpu(rph->last_end_lsn)); + diff = sle64_to_cpu(rph->last_end_lsn) - synced_lsn; printf(" last_end %016llx (synced%s%ld) %s\n", - (long long)le64_to_cpu(rph->last_end_lsn), + (long long)sle64_to_cpu(rph->last_end_lsn), (diff < 0 ? "" : "+"),(long)diff, state); } } @@ -2700,7 +2700,7 @@ static void showrest(const struct RESTART_PAGE_HEADER *rest) printf("usa_count %04x\n", (int)le16_to_cpu(rest->head.usa_count)); printf("chkdsk_lsn %016llx\n", - (long long)le64_to_cpu(rest->chkdsk_lsn)); + (long long)sle64_to_cpu(rest->chkdsk_lsn)); printf("system_page_size %08lx\n", (long)le32_to_cpu(rest->system_page_size)); printf("log_page_size %08lx\n", @@ -2717,13 +2717,13 @@ static void showrest(const struct RESTART_PAGE_HEADER *rest) } else { if (optt) printf(" chkdsk %016llx\n", - (long long)le64_to_cpu(rest->chkdsk_lsn)); + (long long)sle64_to_cpu(rest->chkdsk_lsn)); } resa = (const struct RESTART_AREA*) &data[le16_to_cpu(rest->restart_offset)]; if (optv) { printf("current_lsn %016llx\n", - (long long)le64_to_cpu(resa->current_lsn)); + (long long)sle64_to_cpu(resa->current_lsn)); printf("log_clients %04x\n", (int)le16_to_cpu(resa->log_clients)); printf("client_free_list %04x\n", @@ -2752,7 +2752,7 @@ static void showrest(const struct RESTART_PAGE_HEADER *rest) } else { if (optt) printf(" latest %016llx\n", - (long long)le64_to_cpu(resa->current_lsn)); + (long long)sle64_to_cpu(resa->current_lsn)); } rcli = (const struct RESTART_CLIENT*) @@ -2760,9 +2760,9 @@ static void showrest(const struct RESTART_PAGE_HEADER *rest) + le16_to_cpu(resa->client_array_offset)]; if (optv) { printf("oldest_lsn %016llx\n", - (long long)le64_to_cpu(rcli->oldest_lsn)); + (long long)sle64_to_cpu(rcli->oldest_lsn)); printf("client_restart_lsn %016llx\n", - (long long)le64_to_cpu(rcli->client_restart_lsn)); + (long long)sle64_to_cpu(rcli->client_restart_lsn)); printf("prev_client %04x\n", (int)le16_to_cpu(rcli->prev_client)); printf("next_client %04x\n", @@ -2777,10 +2777,10 @@ static void showrest(const struct RESTART_PAGE_HEADER *rest) } else { if (optt) { printf(" synced %016llx\n", - (long long)le64_to_cpu( + (long long)sle64_to_cpu( rcli->oldest_lsn)); printf(" committed %016llx\n", - (long long)le64_to_cpu( + (long long)sle64_to_cpu( rcli->client_restart_lsn)); } } @@ -2808,9 +2808,9 @@ static BOOL dorest(CONTEXT *ctx, unsigned long blk, + le16_to_cpu(resa->client_array_offset)]; if (initial) { /* Information from block initially found best */ - latest_lsn = le64_to_cpu(resa->current_lsn); - committed_lsn = le64_to_cpu(rcli->client_restart_lsn); - synced_lsn = le64_to_cpu(rcli->oldest_lsn); + latest_lsn = sle64_to_cpu(resa->current_lsn); + committed_lsn = sle64_to_cpu(rcli->client_restart_lsn); + synced_lsn = sle64_to_cpu(rcli->oldest_lsn); memcpy(&log_header, rph, sizeof(struct RESTART_PAGE_HEADER)); offs = le16_to_cpu(log_header.restart_offset); @@ -2847,16 +2847,16 @@ static BOOL dorest(CONTEXT *ctx, unsigned long blk, showrest(rph); /* Information from an older restart block if requested */ dirty = !(restart.flags & RESTART_VOLUME_IS_CLEAN); - diff = le64_to_cpu(rcli->client_restart_lsn) - committed_lsn; + diff = sle64_to_cpu(rcli->client_restart_lsn) - committed_lsn; if (ctx->vol) { change = (opts > 1) && (diff < 0); } else { change = (opts > 1 ? diff < 0 : diff > 0); } if (change) { - committed_lsn = le64_to_cpu(rcli->client_restart_lsn); - synced_lsn = le64_to_cpu(rcli->oldest_lsn); - latest_lsn = le64_to_cpu(resa->current_lsn); + committed_lsn = sle64_to_cpu(rcli->client_restart_lsn); + synced_lsn = sle64_to_cpu(rcli->oldest_lsn); + latest_lsn = sle64_to_cpu(resa->current_lsn); memcpy(&log_header, rph, sizeof(struct RESTART_PAGE_HEADER)); offs = le16_to_cpu(log_header.restart_offset); @@ -2973,7 +2973,7 @@ static int reset_logfile(CONTEXT *ctx __attribute__((unused))) memset(buffer, 0, blocksz); restart.client_in_use_list = LOGFILE_NO_CLIENT; restart.flags |= RESTART_VOLUME_IS_CLEAN; - client.oldest_lsn = cpu_to_le64(restart_lsn); + client.oldest_lsn = cpu_to_sle64(restart_lsn); memcpy(buffer, &log_header, sizeof(struct RESTART_PAGE_HEADER)); off = le16_to_cpu(log_header.restart_offset); @@ -3011,8 +3011,8 @@ static const struct BUFFER *best_start(const struct BUFFER *buf, head = &buf->block.record; althead = &altbuf->block.record; /* determine most recent, caring for wraparounds */ - diff = le64_to_cpu(althead->last_end_lsn) - - le64_to_cpu(head->last_end_lsn); + diff = sle64_to_cpu(althead->last_end_lsn) + - sle64_to_cpu(head->last_end_lsn); if (diff > 0) best = altbuf; else @@ -3724,10 +3724,10 @@ static int walk(CONTEXT *ctx) /* The latest buf may be more recent than restart */ rph = &buf->block.record; - if ((s64)(le64_to_cpu(rph->last_end_lsn) + if ((s64)(sle64_to_cpu(rph->last_end_lsn) - committed_lsn) > 0) { committed_lsn = - le64_to_cpu(rph->last_end_lsn); + sle64_to_cpu(rph->last_end_lsn); if (optv) printf("* Restart page was " "obsolete, updated " @@ -3779,9 +3779,9 @@ static int walk(CONTEXT *ctx) } /* The latest buf may be more recent than restart */ rph = &buf->block.record; - if ((s64)(le64_to_cpu(rph->last_end_lsn) + if ((s64)(sle64_to_cpu(rph->last_end_lsn) - committed_lsn) > 0) { - committed_lsn = le64_to_cpu(rph->last_end_lsn); + committed_lsn = sle64_to_cpu(rph->last_end_lsn); if (optv) printf("* Restart page was obsolete\n"); } diff --git a/ntfsprogs/ntfsrecover.h b/ntfsprogs/ntfsrecover.h index ed8d9d97..5da42c66 100644 --- a/ntfsprogs/ntfsrecover.h +++ b/ntfsprogs/ntfsrecover.h @@ -109,7 +109,7 @@ typedef le16 LOG_RECORD_FLAGS; typedef struct RESTART_PAGE_HEADER { /* size 32 */ NTFS_RECORD head; - le64 chkdsk_lsn; + leLSN chkdsk_lsn; le32 system_page_size; le32 log_page_size; le16 restart_offset; @@ -121,7 +121,7 @@ typedef struct RESTART_PAGE_HEADER { /* size 32 */ /* ntfsdoc p 40 (48), not in layout.h */ struct RESTART_AREA { /* size 44 */ - le64 current_lsn; + leLSN current_lsn; le16 log_clients; le16 client_free_list; le16 client_in_use_list; @@ -138,9 +138,9 @@ struct RESTART_AREA { /* size 44 */ typedef struct RESTART_CLIENT { /* size 160 */ /*Ofs*/ -/* 0*/ le64 oldest_lsn; /* Oldest LSN needed by this client. On create +/* 0*/ leLSN oldest_lsn; /* Oldest LSN needed by this client. On create set to 0. */ -/* 8*/ le64 client_restart_lsn;/* LSN at which this client needs to restart +/* 8*/ leLSN client_restart_lsn;/* LSN at which this client needs to restart the volume, i.e. the current position within the log file. At present, if clean this should = current_lsn in restart area but it @@ -176,7 +176,7 @@ typedef struct RESTART_CLIENT { /* size 160 */ struct RECORD_PAGE_HEADER { /* size 40 */ NTFS_RECORD head; /* the magic is "RCRD" */ union { - le64 last_lsn; + leLSN last_lsn; le32 file_offset; } __attribute__((__packed__)) copy; le32 flags; @@ -184,7 +184,7 @@ struct RECORD_PAGE_HEADER { /* size 40 */ le16 page_position; le16 next_record_offset; le16 reserved4[3]; - le64 last_end_lsn; + leLSN last_end_lsn; } __attribute__((__packed__)) ; /* ntfsdoc p 42 (50), not in layout.h */ @@ -192,9 +192,9 @@ struct RECORD_PAGE_HEADER { /* size 40 */ #define LOG_RECORD_HEAD_SZ 0x30 /* size of header of struct LOG_RECORD */ typedef struct LOG_RECORD { /* size 80 */ - le64 this_lsn; - le64 client_previous_lsn; - le64 client_undo_next_lsn; + leLSN this_lsn; + leLSN client_previous_lsn; + leLSN client_undo_next_lsn; le32 client_data_length; struct { le16 seq_number; @@ -223,10 +223,10 @@ typedef struct LOG_RECORD { /* size 80 */ le64 lcn_list[0]; } __attribute__((__packed__)); struct { - le64 transaction_lsn; - le64 attributes_lsn; - le64 names_lsn; - le64 dirty_pages_lsn; + leLSN transaction_lsn; + leLSN attributes_lsn; + leLSN names_lsn; + leLSN dirty_pages_lsn; le64 unknown_list[0]; } __attribute__((__packed__)); } __attribute__((__packed__)); @@ -275,7 +275,7 @@ typedef struct ATTR_OLD { /* Format up to Win10 (44 bytes) */ le64 unknown1; le64 unknown2; le64 inode; - le64 lsn; + leLSN lsn; le32 unknown3; le32 type; le32 unknown4; @@ -287,7 +287,7 @@ typedef struct ATTR_NEW { /* Format since Win10 (40 bytes) */ le32 type; le32 unknown3; le64 inode; - le64 lsn; + leLSN lsn; } __attribute__((__packed__)) ATTR_NEW; extern u32 clustersz; diff --git a/ntfsprogs/playlog.c b/ntfsprogs/playlog.c index 642998b4..00fa8c67 100644 --- a/ntfsprogs/playlog.c +++ b/ntfsprogs/playlog.c @@ -83,10 +83,10 @@ struct STORE *cluster_door = (struct STORE*)NULL; /* check whether a MFT or INDX record is older than action */ #define older_record(rec, logr) ((s64)(sle64_to_cpu((rec)->lsn) \ - - le64_to_cpu((logr)->this_lsn)) < 0) + - sle64_to_cpu((logr)->this_lsn)) < 0) /* check whether a MFT or INDX record is newer than action */ #define newer_record(rec, logr) ((s64)(sle64_to_cpu((rec)->lsn) \ - - le64_to_cpu((logr)->this_lsn)) > 0) + - sle64_to_cpu((logr)->this_lsn)) > 0) /* * A few functions for debugging @@ -636,9 +636,9 @@ static int write_protected(ntfs_volume *vol, const struct LOG_RECORD *logr, (long)le32_to_cpu(record->mft_record_number), (long long)sle64_to_cpu(record->lsn), ((s64)(sle64_to_cpu(record->lsn) - - le64_to_cpu(logr->this_lsn)) < 0 ? + - sle64_to_cpu(logr->this_lsn)) < 0 ? "older" : "newer"), - (long long)le64_to_cpu(logr->this_lsn)); + (long long)sle64_to_cpu(logr->this_lsn)); if (optv > 1) printf("mft vcn %ld index %d\n", (long)le32_to_cpu(logr->target_vcn), @@ -661,9 +661,9 @@ static int write_protected(ntfs_volume *vol, const struct LOG_RECORD *logr, " (index %s than action 0x%llx)\n", (long long)sle64_to_cpu(indx->lsn), ((s64)(sle64_to_cpu(indx->lsn) - - le64_to_cpu(logr->this_lsn)) < 0 ? + - sle64_to_cpu(logr->this_lsn)) < 0 ? "older" : "newer"), - (long long)le64_to_cpu(logr->this_lsn)); + (long long)sle64_to_cpu(logr->this_lsn)); err = sanity_indx(vol, buffer); /* Should set to some previous lsn for undos */ if (opts) @@ -1979,7 +1979,7 @@ static int redo_compensate(ntfs_volume *vol __attribute__((unused)), if (optv > 1) printf("-> %s()\n",__func__); - lsn = le64_to_cpu(action->record.this_lsn); + lsn = sle64_to_cpu(action->record.this_lsn); diff = lsn - restart_lsn; if (diff > 0) restart_lsn = lsn; @@ -4243,12 +4243,12 @@ static int play_one_redo(ntfs_volume *vol, const struct ACTION_RECORD *action) err = 0; rop = le16_to_cpu(action->record.redo_operation); uop = le16_to_cpu(action->record.undo_operation); - this_lsn = le64_to_cpu(action->record.this_lsn); + this_lsn = sle64_to_cpu(action->record.this_lsn); if (optv) printf("Redo action %d %s (%s) 0x%llx\n", action->num, actionname(rop), actionname(uop), - (long long)le64_to_cpu( + (long long)sle64_to_cpu( action->record.this_lsn)); buffer = (char*)NULL; switch (rop) { @@ -4635,7 +4635,7 @@ static int play_one_undo(ntfs_volume *vol, const struct ACTION_RECORD *action) printf("Undo action %d %s (%s) lsn 0x%llx\n", action->num, actionname(rop), actionname(uop), - (long long)le64_to_cpu( + (long long)sle64_to_cpu( action->record.this_lsn)); buffer = (char*)NULL; executed = FALSE; @@ -4724,7 +4724,7 @@ printf("record lsn 0x%llx is %s than action %d lsn 0x%llx\n", (long long)sle64_to_cpu(entry->lsn), (executed ? "not older" : "older"), (int)action->num, -(long long)le64_to_cpu(action->record.this_lsn)); +(long long)sle64_to_cpu(action->record.this_lsn)); } else { printf("** %s (action %d) not acting on MFT\n", actionname(rop), (int)action->num); @@ -4758,7 +4758,7 @@ printf("index lsn 0x%llx is %s than action %d lsn 0x%llx\n", (long long)sle64_to_cpu(indx->lsn), (executed ? "not older" : "older"), (int)action->num, -(long long)le64_to_cpu(action->record.this_lsn)); +(long long)sle64_to_cpu(action->record.this_lsn)); } else { printf("** %s (action %d) not acting on INDX\n", actionname(rop), (int)action->num);