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/include/ntfs-3g/layout.h b/include/ntfs-3g/layout.h index 0cdd3630..3c9b5890 100644 --- a/include/ntfs-3g/layout.h +++ b/include/ntfs-3g/layout.h @@ -2557,6 +2557,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), #if !ENABLE_STRICT_ENDIANNESS_CHECKING diff --git a/include/ntfs-3g/param.h b/include/ntfs-3g/param.h index da794abb..3210fab9 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 */ @@ -102,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 @@ -116,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/include/ntfs-3g/security.h b/include/ntfs-3g/security.h index 10de638c..d28bc89b 100644 --- a/include/ntfs-3g/security.h +++ b/include/ntfs-3g/security.h @@ -211,22 +211,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/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); } 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); } 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); diff --git a/libntfs-3g/acls.c b/libntfs-3g/acls.c index fe479bdc..96c49554 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 @@ -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; } /* @@ -2314,10 +2316,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 +2587,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/libntfs-3g/attrib.c b/libntfs-3g/attrib.c index 32a09c3b..8a5aa1fb 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, le32_to_cpu(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 = !le16_eq(le16_and(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, le32_to_cpu(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; diff --git a/libntfs-3g/compress.c b/libntfs-3g/compress.c index 5b333086..a9c82ea0 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) { /* diff --git a/libntfs-3g/dir.c b/libntfs-3g/dir.c index 4a9a4fd3..34dabc16 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 (!le32_andz(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 c6f78e5f..f31aed6b 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, @@ -1842,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; @@ -1857,13 +1857,6 @@ int ntfs_index_remove(ntfs_inode *dir_ni, ntfs_inode *ni, if (ntfs_index_lookup(key, keylen, icx)) goto err_out; - if (!le32_andz(((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; diff --git a/libntfs-3g/ioctl.c b/libntfs-3g/ioctl.c index eb7c8e7b..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,11 +368,16 @@ 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 FITRIM or BLKDISCARD not defined +#warning Trimming not supported : FITRIM or BLKDISCARD not defined #endif default : ret = -EINVAL; diff --git a/libntfs-3g/mft.c b/libntfs-3g/mft.c index 1d76c0a1..1a58b24b 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 @@ -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; @@ -1628,6 +1627,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 +1754,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); diff --git a/libntfs-3g/reparse.c b/libntfs-3g/reparse.c index a4bdbf66..db8e233b 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. * @@ -440,8 +442,11 @@ static BOOL valid_reparse_data(ntfs_inode *ni, ok = ni && reparse_attr && (size >= sizeof(REPARSE_POINT)) + && !le32_eq(reparse_attr->reparse_tag, IO_REPARSE_TAG_RESERVED_ZERO) && (((size_t)le16_to_cpu(reparse_attr->reparse_data_length) - + sizeof(REPARSE_POINT)) == size); + + sizeof(REPARSE_POINT) + + (!le32_andz(reparse_attr->reparse_tag, + IO_REPARSE_TAG_IS_MICROSOFT) ? 0 : sizeof(GUID))) == size); if (ok) { /* switch (reparse_attr->reparse_tag) { */ if (le32_eq(reparse_attr->reparse_tag, IO_REPARSE_TAG_MOUNT_POINT)) { diff --git a/libntfs-3g/runlist.c b/libntfs-3g/runlist.c index a55b6aac..5ed408d1 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 (sle64_cmpz(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 (sle64_cmpz(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. */ diff --git a/libntfs-3g/security.c b/libntfs-3g/security.c index 4f8c95fe..63c54e37 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 @@ -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; } @@ -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 = le32_and(ni->flags, le32_not(FILE_ATTR_READONLY)); + else + ni->flags = le32_or(ni->flags, FILE_ATTR_READONLY); + NInoFileNameSetDirty(ni); + } } } else cached = (struct CACHED_SECURID*)NULL; diff --git a/libntfs-3g/unistr.c b/libntfs-3g/unistr.c index 0db73960..80e57f6e 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); + } + } } /* @@ -1533,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; @@ -1556,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 */ diff --git a/libntfs-3g/volume.c b/libntfs-3g/volume.c index 657aa487..a281935a 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; 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: diff --git a/ntfsprogs/Makefile.am b/ntfsprogs/Makefile.am index cbc7ce50..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 @@ -149,6 +153,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 +165,6 @@ install-data-hook: uninstall-local: $(RM) -f $(DESTDIR)/sbin/mkfs.ntfs $(RM) -f $(DESTDIR)$(man8dir)/mkfs.ntfs.8 +endif endif 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 0f825741..48b3ab7f 100644 --- a/ntfsprogs/mkntfs.c +++ b/ntfsprogs/mkntfs.c @@ -3636,10 +3636,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; } diff --git a/ntfsprogs/ntfscat.c b/ntfsprogs/ntfscat.c index 019830cc..5b938ab6 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/ntfsclone.c b/ntfsprogs/ntfsclone.c index 65384e1d..1ebb33d5 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/ntfscp.c b/ntfsprogs/ntfscp.c index 2049150b..5f4f8dda 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/ntfsdecrypt.c b/ntfsprogs/ntfsdecrypt.c index 441ee3e2..f9e215a7 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. @@ -117,6 +117,7 @@ typedef enum { typedef struct { u64 in_whitening, out_whitening; u8 des_key[8]; + u64 prev_blk; } ntfs_desx_ctx; /** @@ -164,7 +165,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); } @@ -895,39 +896,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 @@ -1300,8 +1301,9 @@ static int ntfs_fek_decrypt_sector(ntfs_fek *fek, u8 *data, const u64 offset) if (le32_eq(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); @@ -1350,8 +1352,9 @@ static int ntfs_fek_encrypt_sector(ntfs_fek *fek, u8 *data, const u64 offset) if (le32_eq(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); diff --git a/ntfsprogs/ntfsfallocate.c b/ntfsprogs/ntfsfallocate.c index 3f321a7a..179d867c 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/ntfsfix.c b/ntfsprogs/ntfsfix.c index d584eda4..61db0668 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); @@ -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 (!le32_eq(a->type, AT_END) - && (!le32_eq(a->type, type) || a->name_length) - && (offset < le32_to_cpu(mrec->bytes_in_use))) { + while ((offset < le32_to_cpu(mrec->bytes_in_use)) + && !le32_eq(a->type, AT_END) + && (!le32_eq(a->type, type) || a->name_length)) { offset += le32_to_cpu(a->length); a = (ATTR_RECORD*)((char*)mrec + offset); } - if (!le32_eq(a->type, type) + if ((offset >= le32_to_cpu(mrec->bytes_in_use)) + || !le32_eq(a->type, type) || a->name_length) a = (ATTR_RECORD*)NULL; return (a); @@ -1117,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) @@ -1146,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); @@ -1377,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) @@ -1646,8 +1650,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; diff --git a/ntfsprogs/ntfsinfo.c b/ntfsprogs/ntfsinfo.c index 7ad9c505..f9335b5a 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,22 @@ static char *ntfs_attr_get_name_mbs(ATTR_RECORD *attr) return NULL; } +static const char *reparse_type_name(le32 tag) +{ + const char *name; + + if (le32_eq(tag, IO_REPARSE_TAG_MOUNT_POINT)) + name = " (mount point)"; + else + if (le32_eq(tag, IO_REPARSE_TAG_SYMLINK)) + name = " (symlink)"; + else + if (le32_eq(tag, IO_REPARSE_TAG_WOF)) + name = " (Wof compressed)"; + else + name = ""; + return (name); +} /* *************** functions for dumping global info ******************** */ /** @@ -776,6 +792,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 +831,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 (!le32_andz(file_name_attr->file_attributes, FILE_ATTR_REPARSE_POINT) && - !le32_cmpz(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 (!le32_cmpz(file_name_attr->reparse_point_tag)) { + !le32_cmpz(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 (!le32_cmpz(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 +1451,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 +1475,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 +1969,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 +2363,7 @@ static void ntfs_dump_file_attributes(ntfs_inode *inode) ntfs_dump_attr_bitmap(ctx->attr); } else if (le32_eq(ctx->attr->type, AT_REPARSE_POINT)) { - ntfs_dump_attr_reparse_point(ctx->attr); + ntfs_dump_attr_reparse_point(ctx->attr, inode); } else if (le32_eq(ctx->attr->type, AT_EA_INFORMATION)) { ntfs_dump_attr_ea_information(ctx->attr); @@ -2379,8 +2442,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/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..928f276c --- /dev/null +++ b/ntfsprogs/ntfsrecover.c @@ -0,0 +1,4157 @@ +/* + * 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; + /* + * 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) { + 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 = 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 = sle64_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)sle64_to_cpu(logr->transaction_lsn)); + printf("attributes_lsn %016llx\n", + (long long)sle64_to_cpu(logr->attributes_lsn)); + printf("names_lsn %016llx\n", + (long long)sle64_to_cpu(logr->names_lsn)); + printf("dirty_pages_lsn %016llx\n", + (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); + 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 = sle64_to_cpu(logr->this_lsn) - synced_lsn; + printf("this_lsn %016llx (synced%s%ld) %s\n", + (long long)sle64_to_cpu(logr->this_lsn), + (diff < 0 ? "" : "+"),(long)diff, + commitment(diff + synced_lsn)); + printf("client_previous_lsn %016llx\n", + (long long)sle64_to_cpu(logr->client_previous_lsn)); + printf("client_undo_next_lsn %016llx\n", + (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", + (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(sle64_to_cpu(logr->this_lsn)); + printf(" at %04x %016llx %s (%ld) %s\n",k, + (long long)sle64_to_cpu(logr->this_lsn), + state, + (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)sle64_to_cpu( + logr->client_previous_lsn)); + } else { + printf(" " + " previous %016llx", + (long long)sle64_to_cpu( + logr->client_previous_lsn)); + + if (logr->client_undo_next_lsn) + printf(" undo %016llx\n", + (long long)sle64_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)(sle64_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)(sle64_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 = sle64_to_cpu(rph->copy.last_lsn) - synced_lsn; + printf("last_lsn %016llx" + " (synced%s%ld)\n", + (long long)sle64_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 = sle64_to_cpu(rph->last_end_lsn) - synced_lsn; + printf("last_end_lsn %016llx (synced%s%ld)\n", + (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))); + printf("\n"); + } else { + if (optt) { + const char *state; + + 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)sle64_to_cpu(rph->copy.last_lsn), + (diff < 0 ? "" : "+"),(long)diff, state); + 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)sle64_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)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", + (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)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)sle64_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)sle64_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)sle64_to_cpu(rcli->oldest_lsn)); + printf("client_restart_lsn %016llx\n", + (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", + (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)sle64_to_cpu( + rcli->oldest_lsn)); + printf(" committed %016llx\n", + (long long)sle64_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 = 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); + 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 = 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 = 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); + 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_sle64(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; + free(buffer); + } + 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 = sle64_to_cpu(althead->last_end_lsn) + - sle64_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; + 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)) { + nextdata = nextbuf->block.data; + 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 (blk >= RSTBLKS && blk < BASEBLKS) { + /* The latest buf may be more recent + than restart */ + rph = &buf->block.record; + if ((s64)(sle64_to_cpu(rph->last_end_lsn) + - committed_lsn) > 0) { + committed_lsn = + sle64_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)); + } + } + 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)(sle64_to_cpu(rph->last_end_lsn) + - committed_lsn) > 0) { + committed_lsn = sle64_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; itype; 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 = (le32_eq(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) && le32_eq(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 = sle64_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"); } @@ -1983,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 (!le64_cmpz(lemref)) + base_mref = MREF(le64_to_cpu(lemref)); + else + base_mref = resize->mref; while (!ntfs_attrs_walk(resize->ctx)) { if (le32_eq(resize->ctx->attr->type, AT_END)) break; @@ -2004,6 +2140,11 @@ static void relocate_attributes(ntfs_resize_t *resize, int do_mftdata) le32_eq(resize->ctx->attr->type, AT_DATA)) continue; + /* Do not relocate bad clusters */ + if ((base_mref == FILE_BadClus) + && (le32_eq(resize->ctx->attr->type, AT_DATA))) + continue; + relocate_attribute(resize); } @@ -2181,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 * @@ -2247,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 * @@ -2257,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 = le32_or(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); } /** @@ -2439,7 +2556,7 @@ 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; @@ -2447,27 +2564,12 @@ static int check_bad_sectors(ntfs_volume *vol) lookup_data_attr(vol, FILE_BadClus, "$Bad", &ctx); - base_ni = ctx->base_ntfs_ino; - 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 = open_badclust_bad_attr(ctx); + 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"); - + 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) @@ -2493,7 +2595,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 @@ -2514,12 +2616,9 @@ 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, - resize->ctx->mrec)) - perr_exit("Couldn't update $BadClust"); - #if CLEAN_EXIT close_inode_and_context(resize->ctx); #else @@ -2539,6 +2638,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) { 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 diff --git a/ntfsprogs/ntfswipe.c b/ntfsprogs/ntfswipe.c index 1e26a97c..7beffb54 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; @@ -881,7 +884,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; } @@ -920,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; @@ -974,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 > (int)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; @@ -1233,6 +1246,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; @@ -1333,6 +1349,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; @@ -1720,14 +1737,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; @@ -2246,9 +2267,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: diff --git a/ntfsprogs/playlog.c b/ntfsprogs/playlog.c new file mode 100644 index 00000000..00fa8c67 --- /dev/null +++ b/ntfsprogs/playlog.c @@ -0,0 +1,4829 @@ +/* + * Redo or undo a list of logged actions + * + * Copyright (c) 2014-2015 Jean-Pierre Andre + * + */ + +/* + * 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 + */ + +#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_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)(sle64_to_cpu((rec)->lsn) \ + - 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) \ + - sle64_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)sle64_to_cpu(record->lsn), + ((s64)(sle64_to_cpu(record->lsn) + - sle64_to_cpu(logr->this_lsn)) < 0 ? + "older" : "newer"), + (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), + (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)sle64_to_cpu(indx->lsn), + ((s64)(sle64_to_cpu(indx->lsn) + - sle64_to_cpu(logr->this_lsn)) < 0 ? + "older" : "newer"), + (long long)sle64_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 + err = 1; + } 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 = const_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_sle64(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_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_sle64(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_sle64(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 = sle64_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; + 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 { + err = 1; /* record overflows */ + } + 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); +} + +/* + * 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; + 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 = 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)sle64_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 = sle64_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 = sle64_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 + * + * 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; + int err; + + err = 0; + action = firstaction; + while (action && !err) { + /* Only committed actions should be redone */ + if ((!optc || within_lcn_range(&action->record)) + && (action->flags & ACTION_TO_REDO)) + err = play_one_redo(vol, action); + 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); +} + +/* + * 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; + 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)sle64_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)sle64_to_cpu(entry->lsn), +(executed ? "not older" : "older"), +(int)action->num, +(long long)sle64_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)sle64_to_cpu(indx->lsn), +(executed ? "not older" : "older"), +(int)action->num, +(long long)sle64_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 + * + * 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; + int err; + + err = 0; + action = lastaction; + while (action && !err) { + if (!optc || within_lcn_range(&action->record)) + err = play_one_undo(vol, action); + if (!err) + action = action->prev; + } + return (err); +} diff --git a/ntfsprogs/utils.c b/ntfsprogs/utils.c index cb117309..e7dba1f1 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..6335924e 100644 --- a/ntfsprogs/utils.h +++ b/ntfsprogs/utils.h @@ -107,18 +107,19 @@ 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) + 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 /** 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 diff --git a/src/lowntfs-3g.c b/src/lowntfs-3g.c index 39c358d1..a3dbeeed 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 */ @@ -170,6 +174,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 +1057,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 +1068,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 +1130,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; } @@ -1171,8 +1178,19 @@ 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 */ + current = (ntfs_fuse_fill_item_t*)NULL; first = (ntfs_fuse_fill_item_t*)ntfs_malloc (sizeof(ntfs_fuse_fill_item_t) + size); if (first) { @@ -1182,6 +1200,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; @@ -1196,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 { @@ -1212,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; diff --git a/src/secaudit.c b/src/secaudit.c index 25516264..29236c6d 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 @@ -212,6 +212,15 @@ * - 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 + * + * 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 */ /* @@ -235,7 +244,7 @@ * General parameters which may have to be adapted to needs */ -#define AUDT_VERSION "1.4.2" +#define AUDT_VERSION "1.4.6" #define GET_FILE_SECURITY "ntfs_get_file_security" #define SET_FILE_SECURITY "ntfs_set_file_security" @@ -3737,14 +3746,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; @@ -3886,7 +3895,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++; } @@ -3900,7 +3910,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++; } @@ -4844,9 +4855,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"); @@ -7282,9 +7293,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 } } @@ -7374,7 +7391,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); } diff --git a/src/secaudit.h b/src/secaudit.h index 9edaf02b..7589efd7 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"