commit ba63b7daca9be1eeba3c9d3f5cb250075b7b0e8d Author: szaka Date: Mon Oct 30 22:32:48 2006 +0000 initial CVS import diff --git a/CREDITS b/CREDITS new file mode 100644 index 00000000..709a9838 --- /dev/null +++ b/CREDITS @@ -0,0 +1,38 @@ +The following people have contributed directly or indirectly +to the ntfs-3g project. + +Please let ntfs-3g-devel@lists.sf.net know if you believe +someone is missing, or if you prefer not to be listed. + +Dominique L Bouix +Gergely Erdelyi +Anton Altaparmakov +Peter Boross +Don Bright +Mario Emmenlauer +Yuval Fledel +Kano from Kanotix +Roland Kletzing +Maarten Lankhorst +Gergely Madarasz +Patrick McLean +Florent Mertens +Yura Pakhuchiy +Miklos Szeredi +Bartosz Taudul +Zhanglinbao +Wade Fitzpatrick +Carsten Einig +Adam Cecile +Bruno Damour +Ales Fruman +Curt McDowell +Thomas Franken +Jonatan Lambert +Klaus Knopper +Zhanglinbao +Ismail Donmez +Laszlo Dvornik +Pallaghy Ajtony +Szabolcs Szakacsits + diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 00000000..44fb1f6c --- /dev/null +++ b/Makefile.am @@ -0,0 +1,17 @@ + +SUBDIRS = include libntfs-3g src + +EXTRA_DIST = AUTHORS CREDITS COPYING INSTALL NEWS README autogen.sh +AUTOMAKE_OPTIONS = gnu + +MAINTAINERCLEANFILES = configure Makefile.in aclocal.m4 compile depcomp \ + install-sh ltmain.sh missing config.guess config.sub config.h.in INSTALL + +libtool: $(LIBTOOL_DEPS) + $(SHELL) ./config.status --recheck + +strip: + (cd src && $(MAKE) strip) || exit 1; + +libs: + (cd libntfs-3g && $(MAKE) libs) || exit 1; diff --git a/README b/README new file mode 100644 index 00000000..142d718f --- /dev/null +++ b/README @@ -0,0 +1,63 @@ + +INTRODUCTION +============ + +The ntfs-3g driver is an open source, freely available read/write NTFS +driver, which provides safe and fast handling of the Windows XP, Windows +Server 2003 and Windows 2000 filesystems. Almost the full POSIX filesystem +functionality is supported, the major exceptions are changing the file +ownerships and the access rights. + +The purpose of the project is to develop, continuously quality test and +support a trustable, featureful and high performance solution for hardware +platforms and operating systems whose users need to reliably interoperate +with NTFS. Besides this practical goal, the project also aims to explore +the limits of the hybrid, kernel/user space filesystem driver approach, +performance, reliability and feature richness per invested effort wise. + +The driver currently is in BETA status, which means that we weren't +reported and haven't found any data corruption or loss during ordinary +driver use and in our extensive quality testing before release of the +latest version of ntfs-3g, however we are aware of some usability issues +and driver restrictions which are all documented and planned to be resolved +in the future. + +You can find news, technical answers, problem submission instructions, +performance evaluation and other informations on the project web site: + + http://www.ntfs-3g.org + + +QUICK INSTALLATION +================== + +Make sure you have the basic Linux development tools and the full FUSE +package (http://fuse.sourceforge.net) is already installed correctly on +the computer. Then type: + + ./configure + make + make install # or 'sudo make install' if you aren't root. + + +USAGE +===== + +If there was no error during installation then the NTFS volume can be +read-write mounted for everybody the following way (unmount the volume if +it was already mounted, and replace /dev/hda1 and /mnt/windows, if needed): + + ntfs-3g /dev/hda1 /mnt/windows + +You may also need to set the 'locale' option to make all files with national +characters visible. Replace the below hu_HU.utf8 with the appropriate setting. + + ntfs-3g /dev/hda1 /mnt/windows -o locale=hu_HU.utf8 + +Please see the ntfs-3g manual page for more options and examples. + +You can also make NTFS to be mounted during boot by putting the below +line at the end of the /etc/fstab file: + + /dev/hda1 /mnt/windows ntfs-3g defaults 0 0 + diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 00000000..d1a2e9d2 --- /dev/null +++ b/autogen.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# Run this to generate configure, Makefile.in's, etc + +(autoreconf --version) < /dev/null > /dev/null 2>&1 || { + (autoconf --version) < /dev/null > /dev/null 2>&1 || { + echo + echo "**Error**: You must have the GNU Build System (autoconf, automake, " + echo "libtool, etc) to update the ntfs-3g build system. Download the " + echo "appropriate packages for your distribution, or get the source " + echo "tar balls from ftp://ftp.gnu.org/pub/gnu/." + exit 1 + } + echo + echo "**Error**: Your version of autoconf is too old (you need at least 2.57)" + echo "to update the ntfs-3g build system. Download the appropriate " + echo "updated package for your distribution, or get the source tar ball " + echo "from ftp://ftp.gnu.org/pub/gnu/." + exit 1 +} + +echo Running autoreconf --verbose --install +autoreconf --force --verbose --install diff --git a/configure.ac b/configure.ac new file mode 100644 index 00000000..87d87413 --- /dev/null +++ b/configure.ac @@ -0,0 +1,169 @@ +# +# configure.ac - Source file to generate "./configure" to prepare package for +# compilation. +# +# Copyright (c) 2000-2006 Anton Altaparmakov +# Copyright (c) 2003 Jan Kratochvil +# Copyright (c) 2005-2006 Szabolcs Szakacsits +# +# 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 +# by the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program/include file 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 +# + +AC_PREREQ(2.59) +AC_INIT([ntfs-3g],[0.20061031-BETA],[ntfs-3g-devel@lists.sf.net]) + +AC_CANONICAL_HOST([]) +AC_CANONICAL_TARGET([]) +AC_CONFIG_SRCDIR([config.h.in]) +AC_CONFIG_HEADERS([config.h]) +AM_INIT_AUTOMAKE +AM_MAINTAINER_MODE +AM_ENABLE_SHARED +AM_ENABLE_STATIC + +AC_PREFIX_DEFAULT(/usr/local) +if test "x$prefix" = "xNONE"; then + prefix=$ac_default_prefix + ac_configure_args="$ac_configure_args --prefix $prefix" +fi + +# Command-line options. +AC_ARG_ENABLE(debug, + AS_HELP_STRING(--enable-debug,enable additional debugging code and + output), , + enable_debug=no +) + +AC_ARG_ENABLE(really-static, + AS_HELP_STRING(--enable-really-static,create static binaries + for the utilities), , + enable_really_static=no +) +AM_CONDITIONAL(REALLYSTATIC, test "$enable_really_static" = yes) + +AC_ARG_ENABLE(warnings, + AS_HELP_STRING(--enable-warnings,enable additional compiler warnings), , + enable_warnings=no +) + +# Use GNU extensions if available. +AC_GNU_SOURCE + +# Checks for programs. +AC_PROG_CC +AC_PROG_GCC_TRADITIONAL +AC_PROG_INSTALL +AC_PATH_PROG(RM, rm, rm) +AC_PROG_LN_S +AC_PROG_MAKE_SET +AC_PROG_LIBTOOL + +# Libraries often install their metadata .pc files in directories not searched +# by pkg-config. Let's workaround this. +export PKG_CONFIG_PATH=${PKG_CONFIG_PATH}:/usr/lib/pkgconfig:/opt/gnome/lib/pkgconfig:/usr/share/pkgconfig:/usr/local/lib/pkgconfig:$prefix/lib/pkgconfig:/opt/gnome/share/pkgconfig:/usr/local/share/pkgconfig + +# Enable large file support. +AC_SYS_LARGEFILE + +case "$target_os" in +linux*) + PKG_CHECK_MODULES(FUSE_MODULE, fuse >= 2.5.0, [ compile_fuse_module=true ], + [ + AC_MSG_ERROR([ntfs-3g requires FUSE >= 2.5.0. Please see http://fuse.sf.net/ or install __all__ the precompiled fuse packages.]) + ]);; +*) + AC_MSG_ERROR([ntfs-3g can be built only under Linux.]) + ;; +esac + +# add --with-extra-includes and --with-extra-libs switch to ./configure +all_libraries="$all_libraries $USER_LDFLAGS" +all_includes="$all_includes $USER_INCLUDES" +AC_SUBST(all_includes) +AC_SUBST(all_libraries) + +# Add our compiler switches not discarding 'CFLAGS' as they may have been +# passed to us by rpmbuild(8). +# We add -Wall to enable some compiler warnings. +CFLAGS="$CFLAGS -Wall" + +# Add lots of extra warnings if --enable-warnings was specified. +if test "$enable_warnings" = "yes"; then + CFLAGS="$CFLAGS -W -Wall -Waggregate-return -Wbad-function-cast -Wcast-align -Wcast-qual -Wdisabled-optimization -Wdiv-by-zero -Wfloat-equal -Winline -Wmissing-declarations -Wmissing-format-attribute -Wmissing-noreturn -Wmissing-prototypes -Wmultichar -Wnested-externs -Wpointer-arith -Wredundant-decls -Wshadow -Wsign-compare -Wstrict-prototypes -Wundef -Wwrite-strings" +fi + +# Add debugging switches if --enable-debug was specified. +if test "$enable_debug" = "yes"; then + CFLAGS="$CFLAGS -ggdb3 -DDEBUG" +fi + +AC_SUBST(CFLAGS) +AC_SUBST(CPPFLAGS) +AC_SUBST(LDFLAGS) +AC_SUBST(LIBS) + +AC_SUBST(LIBNTFS_3G_CFLAGS) + +AC_SUBST(AUTODIRS) + +# Checks for libraries. + +# Checks for header files. +AC_HEADER_STDC +AC_CHECK_HEADERS([ctype.h fcntl.h libgen.h libintl.h limits.h locale.h \ + mntent.h stddef.h stdint.h stdlib.h stdio.h stdarg.h string.h \ + strings.h errno.h time.h unistd.h utime.h wchar.h getopt.h features.h \ + endian.h byteswap.h sys/byteorder.h sys/endian.h sys/param.h \ + sys/ioctl.h sys/mount.h sys/stat.h sys/types.h sys/vfs.h \ + sys/statvfs.h sys/sysmacros.h linux/major.h linux/fd.h linux/hdreg.h \ + machine/endian.h gcrypt.h windows.h gnutls/pkcs12.h syslog.h]) + +# Checks for typedefs, structures, and compiler characteristics. +AC_HEADER_STDBOOL +AC_C_BIGENDIAN(, + [AC_DEFINE([WORDS_LITTLEENDIAN], 1, + [Define to 1 if your processor stores words with the least significant + byte first (like Intel and VAX, unlike Motorola and SPARC).])] + ,) +AC_C_CONST +AC_C_INLINE +AC_TYPE_OFF_T +AC_TYPE_SIZE_T +AC_STRUCT_ST_BLOCKS +AC_CHECK_MEMBERS([struct stat.st_rdev]) + +# Checks for library functions. +AC_FUNC_GETMNTENT +AC_FUNC_MBRTOWC +AC_FUNC_MEMCMP +AC_FUNC_STAT +AC_FUNC_STRFTIME +AC_FUNC_UTIME_NULL +AC_FUNC_VPRINTF +AC_CHECK_FUNCS([atexit basename dup2 fdatasync getopt_long hasmntopt mbsinit \ + memmove memset realpath regcomp setlocale setxattr strcasecmp strchr \ + strdup strerror strnlen strtol strtoul sysconf utime]) + +# Makefiles to be created by configure. +AC_CONFIG_FILES([ + Makefile + include/Makefile + include/ntfs-3g/Makefile + libntfs-3g/Makefile + src/Makefile + src/ntfs-3g.8 +]) +AC_OUTPUT diff --git a/include/Makefile.am b/include/Makefile.am new file mode 100644 index 00000000..8c73deb8 --- /dev/null +++ b/include/Makefile.am @@ -0,0 +1,3 @@ +SUBDIRS = ntfs-3g + +MAINTAINERCLEANFILES = Makefile.in diff --git a/include/ntfs-3g/Makefile.am b/include/ntfs-3g/Makefile.am new file mode 100644 index 00000000..e58428d9 --- /dev/null +++ b/include/ntfs-3g/Makefile.am @@ -0,0 +1,34 @@ + +linux_ntfsincludedir = $(includedir)/ntfs-3g +linux_ntfsinclude_HEADERS = \ + attrib.h \ + attrlist.h \ + bitmap.h \ + bootsect.h \ + collate.h \ + compat.h \ + compress.h \ + debug.h \ + device.h \ + device_io.h \ + dir.h \ + endians.h \ + index.h \ + inode.h \ + layout.h \ + lcnalloc.h \ + list.h \ + logfile.h \ + logging.h \ + mft.h \ + mst.h \ + ntfstime.h \ + runlist.h \ + security.h \ + support.h \ + types.h \ + unistr.h \ + version.h \ + volume.h + +MAINTAINERCLEANFILES = Makefile.in diff --git a/include/ntfs-3g/attrib.h b/include/ntfs-3g/attrib.h new file mode 100644 index 00000000..a35d978a --- /dev/null +++ b/include/ntfs-3g/attrib.h @@ -0,0 +1,359 @@ +/* + * attrib.h - Exports for attribute handling. Originated from the Linux-NTFS project. + * + * Copyright (c) 2000-2004 Anton Altaparmakov + * Copyright (c) 2004-2005 Yura Pakhuchiy + * Copyright (c) 2006 Szabolcs Szakacsits + * + * 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 + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program/include file 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 + */ + +#ifndef _NTFS_ATTRIB_H +#define _NTFS_ATTRIB_H + +/* Forward declarations */ +typedef struct _ntfs_attr ntfs_attr; +typedef struct _ntfs_attr_search_ctx ntfs_attr_search_ctx; + +#include "types.h" +#include "inode.h" +#include "unistr.h" +#include "runlist.h" +#include "volume.h" +#include "debug.h" +#include "logging.h" + +extern ntfschar AT_UNNAMED[]; + +/** + * enum ntfs_lcn_special_values - special return values for ntfs_*_vcn_to_lcn() + * + * Special return values for ntfs_rl_vcn_to_lcn() and ntfs_attr_vcn_to_lcn(). + * + * TODO: Describe them. + */ +typedef enum { + LCN_HOLE = -1, /* Keep this as highest value or die! */ + LCN_RL_NOT_MAPPED = -2, + LCN_ENOENT = -3, + LCN_EINVAL = -4, + LCN_EIO = -5, +} ntfs_lcn_special_values; + +/** + * struct ntfs_attr_search_ctx - search context used in attribute search functions + * @mrec: buffer containing mft record to search + * @attr: attribute record in @mrec where to begin/continue search + * @is_first: if true lookup_attr() begins search with @attr, else after @attr + * + * Structure must be initialized to zero before the first call to one of the + * attribute search functions. Initialize @mrec to point to the mft record to + * search, and @attr to point to the first attribute within @mrec (not necessary + * if calling the _first() functions), and set @is_first to TRUE (not necessary + * if calling the _first() functions). + * + * If @is_first is TRUE, the search begins with @attr. If @is_first is FALSE, + * the search begins after @attr. This is so that, after the first call to one + * of the search attribute functions, we can call the function again, without + * any modification of the search context, to automagically get the next + * matching attribute. + */ +struct _ntfs_attr_search_ctx { + MFT_RECORD *mrec; + ATTR_RECORD *attr; + BOOL is_first; + ntfs_inode *ntfs_ino; + ATTR_LIST_ENTRY *al_entry; + ntfs_inode *base_ntfs_ino; + MFT_RECORD *base_mrec; + ATTR_RECORD *base_attr; +}; + +extern void ntfs_attr_reinit_search_ctx(ntfs_attr_search_ctx *ctx); +extern ntfs_attr_search_ctx *ntfs_attr_get_search_ctx(ntfs_inode *ni, + MFT_RECORD *mrec); +extern void ntfs_attr_put_search_ctx(ntfs_attr_search_ctx *ctx); + +extern int ntfs_attr_lookup(const ATTR_TYPES type, const ntfschar *name, + const u32 name_len, const IGNORE_CASE_BOOL ic, + const VCN lowest_vcn, const u8 *val, const u32 val_len, + ntfs_attr_search_ctx *ctx); + +extern ATTR_DEF *ntfs_attr_find_in_attrdef(const ntfs_volume *vol, + const ATTR_TYPES type); + +/** + * ntfs_attrs_walk - syntactic sugar for walking all attributes in an inode + * @ctx: initialised attribute search context + * + * Syntactic sugar for walking attributes in an inode. + * + * Return 0 on success and -1 on error with errno set to the error code from + * ntfs_attr_lookup(). + * + * Example: When you want to enumerate all attributes in an open ntfs inode + * @ni, you can simply do: + * + * int err; + * ntfs_attr_search_ctx *ctx = ntfs_attr_get_search_ctx(ni, NULL); + * if (!ctx) + * // Error code is in errno. Handle this case. + * while (!(err = ntfs_attrs_walk(ctx))) { + * ATTR_RECORD *attr = ctx->attr; + * // attr now contains the next attribute. Do whatever you want + * // with it and then just continue with the while loop. + * } + * if (err && errno != ENOENT) + * // Ooops. An error occurred! You should handle this case. + * // Now finished with all attributes in the inode. + */ +static __inline__ int ntfs_attrs_walk(ntfs_attr_search_ctx *ctx) +{ + return ntfs_attr_lookup(AT_UNUSED, NULL, 0, CASE_SENSITIVE, 0, + NULL, 0, ctx); +} + +/** + * struct ntfs_attr - ntfs in memory non-resident attribute structure + * @rl: if not NULL, the decompressed runlist + * @ni: base ntfs inode to which this attribute belongs + * @type: attribute type + * @name: Unicode name of the attribute + * @name_len: length of @name in Unicode characters + * @state: NTFS attribute specific flags describing this attribute + * @allocated_size: copy from the attribute record + * @data_size: copy from the attribute record + * @initialized_size: copy from the attribute record + * @compressed_size: copy from the attribute record + * @compression_block_size: size of a compression block (cb) + * @compression_block_size_bits: log2 of the size of a cb + * @compression_block_clusters: number of clusters per cb + * + * This structure exists purely to provide a mechanism of caching the runlist + * of an attribute. If you want to operate on a particular attribute extent, + * you should not be using this structure at all. If you want to work with a + * resident attribute, you should not be using this structure at all. As a + * fail-safe check make sure to test NAttrNonResident() and if it is false, you + * know you shouldn't be using this structure. + * + * If you want to work on a resident attribute or on a specific attribute + * extent, you should use ntfs_lookup_attr() to retrieve the attribute (extent) + * record, edit that, and then write back the mft record (or set the + * corresponding ntfs inode dirty for delayed write back). + * + * @rl is the decompressed runlist of the attribute described by this + * structure. Obviously this only makes sense if the attribute is not resident, + * i.e. NAttrNonResident() is true. If the runlist hasn't been decompressed yet + * @rl is NULL, so be prepared to cope with @rl == NULL. + * + * @ni is the base ntfs inode of the attribute described by this structure. + * + * @type is the attribute type (see layout.h for the definition of ATTR_TYPES), + * @name and @name_len are the little endian Unicode name and the name length + * in Unicode characters of the attribute, respectively. + * + * @state contains NTFS attribute specific flags describing this attribute + * structure. See ntfs_attr_state_bits above. + */ +struct _ntfs_attr { + runlist_element *rl; + ntfs_inode *ni; + ATTR_TYPES type; + ntfschar *name; + u32 name_len; + unsigned long state; + s64 allocated_size; + s64 data_size; + s64 initialized_size; + s64 compressed_size; + u32 compression_block_size; + u8 compression_block_size_bits; + u8 compression_block_clusters; +}; + +/** + * enum ntfs_attr_state_bits - bits for the state field in the ntfs_attr structure + */ +typedef enum { + NA_Initialized, /* 1: structure is initialized. */ + NA_NonResident, /* 1: Attribute is not resident. */ +} ntfs_attr_state_bits; + +#define test_nattr_flag(na, flag) test_bit(NA_##flag, (na)->state) +#define set_nattr_flag(na, flag) set_bit(NA_##flag, (na)->state) +#define clear_nattr_flag(na, flag) clear_bit(NA_##flag, (na)->state) + +#define NAttrInitialized(na) test_nattr_flag(na, Initialized) +#define NAttrSetInitialized(na) set_nattr_flag(na, Initialized) +#define NAttrClearInitialized(na) clear_nattr_flag(na, Initialized) + +#define NAttrNonResident(na) test_nattr_flag(na, NonResident) +#define NAttrSetNonResident(na) set_nattr_flag(na, NonResident) +#define NAttrClearNonResident(na) clear_nattr_flag(na, NonResident) + +#define GenNAttrIno(func_name,flag) \ +static inline int NAttr##func_name(ntfs_attr *na) \ +{ \ + if (na->type == AT_DATA && na->name == AT_UNNAMED) \ + return (na->ni->flags & FILE_ATTR_##flag); \ + return 0; \ +} \ +static inline void NAttrSet##func_name(ntfs_attr *na) \ +{ \ + if (na->type == AT_DATA && na->name == AT_UNNAMED) \ + na->ni->flags |= FILE_ATTR_##flag; \ + else \ + ntfs_log_trace("BUG! Should be called only for "\ + "unnamed data attribute.\n"); \ +} \ +static inline void NAttrClear##func_name(ntfs_attr *na) \ +{ \ + if (na->type == AT_DATA && na->name == AT_UNNAMED) \ + na->ni->flags &= ~FILE_ATTR_##flag; \ +} + +GenNAttrIno(Compressed, COMPRESSED) +GenNAttrIno(Encrypted, ENCRYPTED) +GenNAttrIno(Sparse, SPARSE_FILE) + +/** + * union attr_val - Union of all known attribute values + * + * For convenience. Used in the attr structure. + */ +typedef union { + u8 _default; /* Unnamed u8 to serve as default when just using + a_val without specifying any of the below. */ + STANDARD_INFORMATION std_inf; + ATTR_LIST_ENTRY al_entry; + FILE_NAME_ATTR filename; + OBJECT_ID_ATTR obj_id; + SECURITY_DESCRIPTOR_ATTR sec_desc; + VOLUME_NAME vol_name; + VOLUME_INFORMATION vol_inf; + DATA_ATTR data; + INDEX_ROOT index_root; + INDEX_BLOCK index_blk; + BITMAP_ATTR bmp; + REPARSE_POINT reparse; + EA_INFORMATION ea_inf; + EA_ATTR ea; + PROPERTY_SET property_set; + LOGGED_UTILITY_STREAM logged_util_stream; + EFS_ATTR_HEADER efs; +} attr_val; + +extern void ntfs_attr_init(ntfs_attr *na, const BOOL non_resident, + const BOOL compressed, const BOOL encrypted, const BOOL sparse, + const s64 allocated_size, const s64 data_size, + const s64 initialized_size, const s64 compressed_size, + const u8 compression_unit); + +extern ntfs_attr *ntfs_attr_open(ntfs_inode *ni, const ATTR_TYPES type, + ntfschar *name, u32 name_len); +extern void ntfs_attr_close(ntfs_attr *na); + +extern s64 ntfs_attr_pread(ntfs_attr *na, const s64 pos, s64 count, + void *b); +extern s64 ntfs_attr_pwrite(ntfs_attr *na, const s64 pos, s64 count, + const void *b); + +extern void *ntfs_attr_readall(ntfs_inode *ni, const ATTR_TYPES type, + ntfschar *name, u32 name_len, s64 *data_size); + +extern s64 ntfs_attr_mst_pread(ntfs_attr *na, const s64 pos, + const s64 bk_cnt, const u32 bk_size, void *dst); +extern s64 ntfs_attr_mst_pwrite(ntfs_attr *na, const s64 pos, + s64 bk_cnt, const u32 bk_size, void *src); + +extern int ntfs_attr_map_runlist(ntfs_attr *na, VCN vcn); +extern int ntfs_attr_map_whole_runlist(ntfs_attr *na); + +extern LCN ntfs_attr_vcn_to_lcn(ntfs_attr *na, const VCN vcn); +extern runlist_element *ntfs_attr_find_vcn(ntfs_attr *na, const VCN vcn); + +extern int ntfs_attr_size_bounds_check(const ntfs_volume *vol, + const ATTR_TYPES type, const s64 size); +extern int ntfs_attr_can_be_non_resident(const ntfs_volume *vol, + const ATTR_TYPES type); +extern int ntfs_attr_can_be_resident(const ntfs_volume *vol, + const ATTR_TYPES type); + +extern int ntfs_make_room_for_attr(MFT_RECORD *m, u8 *pos, u32 size); + +extern int ntfs_resident_attr_record_add(ntfs_inode *ni, ATTR_TYPES type, + ntfschar *name, u8 name_len, u8 *val, u32 size, + ATTR_FLAGS flags); +extern int ntfs_non_resident_attr_record_add(ntfs_inode *ni, ATTR_TYPES type, + ntfschar *name, u8 name_len, VCN lowest_vcn, int dataruns_size, + ATTR_FLAGS flags); +extern int ntfs_attr_record_rm(ntfs_attr_search_ctx *ctx); + +extern int ntfs_attr_add(ntfs_inode *ni, ATTR_TYPES type, + ntfschar *name, u8 name_len, u8 *val, s64 size); +extern int ntfs_attr_rm(ntfs_attr *na); + +extern int ntfs_attr_record_resize(MFT_RECORD *m, ATTR_RECORD *a, u32 new_size); + +extern int ntfs_resident_attr_value_resize(MFT_RECORD *m, ATTR_RECORD *a, + const u32 new_size); + +extern int ntfs_attr_record_move_to(ntfs_attr_search_ctx *ctx, ntfs_inode *ni); +extern int ntfs_attr_record_move_away(ntfs_attr_search_ctx *ctx, int extra); + +extern int ntfs_attr_update_mapping_pairs(ntfs_attr *na, VCN from_vcn); + +extern int ntfs_attr_truncate(ntfs_attr *na, const s64 newsize); + +// FIXME / TODO: Above here the file is cleaned up. (AIA) +/** + * get_attribute_value_length - return the length of the value of an attribute + * @a: pointer to a buffer containing the attribute record + * + * Return the byte size of the attribute value of the attribute @a (as it + * would be after eventual decompression and filling in of holes if sparse). + * If we return 0, check errno. If errno is 0 the actual length was 0, + * otherwise errno describes the error. + * + * FIXME: Describe possible errnos. + */ +s64 ntfs_get_attribute_value_length(const ATTR_RECORD *a); + +/** + * get_attribute_value - return the attribute value of an attribute + * @vol: volume on which the attribute is present + * @a: attribute to get the value of + * @b: destination buffer for the attribute value + * + * Make a copy of the attribute value of the attribute @a into the destination + * buffer @b. Note, that the size of @b has to be at least equal to the value + * returned by get_attribute_value_length(@a). + * + * Return number of bytes copied. If this is zero check errno. If errno is 0 + * then nothing was read due to a zero-length attribute value, otherwise + * errno describes the error. + */ +s64 ntfs_get_attribute_value(const ntfs_volume *vol, const ATTR_RECORD *a, + u8 *b); + +void ntfs_attr_name_free(char **name); +char *ntfs_attr_name_get(const ntfschar *uname, const int uname_len); + +int ntfs_attr_exist(ntfs_inode *ni, const ATTR_TYPES type, ntfschar *name, + u32 name_len); + +#endif /* defined _NTFS_ATTRIB_H */ + diff --git a/include/ntfs-3g/attrlist.h b/include/ntfs-3g/attrlist.h new file mode 100644 index 00000000..2952e48b --- /dev/null +++ b/include/ntfs-3g/attrlist.h @@ -0,0 +1,51 @@ +/* + * attrlist.h - Exports for attribute list attribute handling. + * Originated from Linux-NTFS project. + * + * Copyright (c) 2004 Anton Altaparmakov + * Copyright (c) 2004 Yura Pakhuchiy + * + * 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 + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program/include file 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 + */ + +#ifndef _NTFS_ATTRLIST_H +#define _NTFS_ATTRLIST_H + +#include "attrib.h" + +extern int ntfs_attrlist_need(ntfs_inode *ni); + +extern int ntfs_attrlist_entry_add(ntfs_inode *ni, ATTR_RECORD *attr); +extern int ntfs_attrlist_entry_rm(ntfs_attr_search_ctx *ctx); + +/** + * ntfs_attrlist_mark_dirty - set the attribute list dirty + * @ni: ntfs inode which base inode contain dirty attribute list + * + * Set the attribute list dirty so it is written out later (at the latest at + * ntfs_inode_close() time). + * + * This function cannot fail. + */ +static __inline__ void ntfs_attrlist_mark_dirty(ntfs_inode *ni) +{ + if (ni->nr_extents == -1) + NInoAttrListSetDirty(ni->base_ni); + else + NInoAttrListSetDirty(ni); +} + +#endif /* defined _NTFS_ATTRLIST_H */ diff --git a/include/ntfs-3g/bitmap.h b/include/ntfs-3g/bitmap.h new file mode 100644 index 00000000..56d1f64e --- /dev/null +++ b/include/ntfs-3g/bitmap.h @@ -0,0 +1,74 @@ +/* + * bitmap.h - Exports for bitmap handling. Originated from the Linux-NTFS project. + * + * Copyright (c) 2000-2004 Anton Altaparmakov + * Copyright (c) 2004-2005 Richard Russon + * + * 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 + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program/include file 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 + */ + +#ifndef _NTFS_BITMAP_H +#define _NTFS_BITMAP_H + +#include "types.h" +#include "attrib.h" + +/* + * NOTES: + * + * - Operations are 8-bit only to ensure the functions work both on little + * and big endian machines! So don't make them 32-bit ops! + * - bitmap starts at bit = 0 and ends at bit = bitmap size - 1. + * - _Caller_ has to make sure that the bit to operate on is less than the + * size of the bitmap. + */ + +extern void ntfs_bit_set(u8 *bitmap, const u64 bit, const u8 new_value); +extern char ntfs_bit_get(const u8 *bitmap, const u64 bit); +extern char ntfs_bit_get_and_set(u8 *bitmap, const u64 bit, const u8 new_value); +extern int ntfs_bitmap_set_run(ntfs_attr *na, s64 start_bit, s64 count); +extern int ntfs_bitmap_clear_run(ntfs_attr *na, s64 start_bit, s64 count); + +/** + * ntfs_bitmap_set_bit - set a bit in a bitmap + * @na: attribute containing the bitmap + * @bit: bit to set + * + * Set the @bit in the bitmap described by the attribute @na. + * + * On success return 0 and on error return -1 with errno set to the error code. + */ +static __inline__ int ntfs_bitmap_set_bit(ntfs_attr *na, s64 bit) +{ + return ntfs_bitmap_set_run(na, bit, 1); +} + +/** + * ntfs_bitmap_clear_bit - clear a bit in a bitmap + * @na: attribute containing the bitmap + * @bit: bit to clear + * + * Clear @bit in the bitmap described by the attribute @na. + * + * On success return 0 and on error return -1 with errno set to the error code. + */ +static __inline__ int ntfs_bitmap_clear_bit(ntfs_attr *na, s64 bit) +{ + return ntfs_bitmap_clear_run(na, bit, 1); +} + +#endif /* defined _NTFS_BITMAP_H */ + diff --git a/include/ntfs-3g/bootsect.h b/include/ntfs-3g/bootsect.h new file mode 100644 index 00000000..e3a5e798 --- /dev/null +++ b/include/ntfs-3g/bootsect.h @@ -0,0 +1,47 @@ +/* + * bootsect.h - Exports for bootsector record handling. Originated from the Linux-NTFS + * project. + * + * Copyright (c) 2000-2002 Anton Altaparmakov + * + * 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 + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program/include file 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 + */ + +#ifndef _NTFS_BOOTSECT_H +#define _NTFS_BOOTSECT_H + +#include "types.h" +#include "volume.h" +#include "layout.h" + +/** + * is_boot_sector_ntfs - check a boot sector for describing an ntfs volume + * @b: buffer containing the boot sector + * @silent: if 1 don't display progress information + * + * This function checks the boot sector in @b for describing a valid ntfs + * volume. Return TRUE if @b is a valid NTFS boot sector or FALSE otherwise. + * If silent is FALSE, progress output will be output to stdout. If silent is + * TRUE no output to stdout will occur. Errors/warnings to stderr will occur + * disregarding the value of silent (but only if configure was run with + * --enable-debug). + */ +extern BOOL ntfs_boot_sector_is_ntfs(NTFS_BOOT_SECTOR *b, BOOL silent); +extern int ntfs_boot_sector_parse(ntfs_volume *vol, + const NTFS_BOOT_SECTOR *bs); + +#endif /* defined _NTFS_BOOTSECT_H */ + diff --git a/include/ntfs-3g/collate.h b/include/ntfs-3g/collate.h new file mode 100644 index 00000000..9c0ec9bc --- /dev/null +++ b/include/ntfs-3g/collate.h @@ -0,0 +1,37 @@ +/* + * collate.h - Defines for NTFS collation handling. Originated from the Linux-NTFS + * project. + * + * Copyright (c) 2004 Anton Altaparmakov + * Copyright (c) 2005 Yura Pakhuchiy + * + * 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 + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program/include file 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 + */ + +#ifndef _NTFS_COLLATE_H +#define _NTFS_COLLATE_H + +#include "types.h" +#include "volume.h" + +#define NTFS_COLLATION_ERROR -2 + +extern BOOL ntfs_is_collation_rule_supported(COLLATION_RULES cr); +extern int ntfs_collate(ntfs_volume *vol, COLLATION_RULES cr, + const void *data1, const int data1_len, + const void *data2, const int data2_len); + +#endif /* _NTFS_COLLATE_H */ diff --git a/include/ntfs-3g/compat.h b/include/ntfs-3g/compat.h new file mode 100644 index 00000000..74d555cf --- /dev/null +++ b/include/ntfs-3g/compat.h @@ -0,0 +1,54 @@ +/* + * compat.h - Tweaks for Windows compatibility. + * + * Copyright (c) 2002 Richard Russon + * Copyright (c) 2002-2004 Anton Altaparmakov + * + * 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 + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program/include file 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 + */ + +#ifndef _NTFS_COMPAT_H +#define _NTFS_COMPAT_H + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef WINDOWS + +#ifndef HAVE_FFS +#define HAVE_FFS +extern int ffs(int i); +#endif /* HAVE_FFS */ + +#define HAVE_STDIO_H /* mimic config.h */ +#define HAVE_STDARG_H + +#define atoll _atoi64 +#define fdatasync commit +#define __inline__ inline +#define __attribute__(X) /*nothing*/ + +#else /* !defined WINDOWS */ + +#ifndef O_BINARY +#define O_BINARY 0 /* unix is binary by default */ +#endif + +#endif /* defined WINDOWS */ + +#endif /* defined _NTFS_COMPAT_H */ + diff --git a/include/ntfs-3g/compress.h b/include/ntfs-3g/compress.h new file mode 100644 index 00000000..83eb490d --- /dev/null +++ b/include/ntfs-3g/compress.h @@ -0,0 +1,33 @@ +/* + * compress.h - Exports for compressed attribute handling. + * Originated from the Linux-NTFS project. + * + * Copyright (c) 2004 Anton Altaparmakov + * + * 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 + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program/include file 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 + */ + +#ifndef _NTFS_COMPRESS_H +#define _NTFS_COMPRESS_H + +#include "types.h" +#include "attrib.h" + +extern s64 ntfs_compressed_attr_pread(ntfs_attr *na, s64 pos, s64 count, + void *b); + +#endif /* defined _NTFS_COMPRESS_H */ + diff --git a/include/ntfs-3g/debug.h b/include/ntfs-3g/debug.h new file mode 100644 index 00000000..cf39b625 --- /dev/null +++ b/include/ntfs-3g/debug.h @@ -0,0 +1,47 @@ +/* + * debug.h - Debugging output functions. Originated from the Linux-NTFS project. + * + * Copyright (c) 2002-2004 Anton Altaparmakov + * + * 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 + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program/include file 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 + */ + +#ifndef _NTFS_DEBUG_H +#define _NTFS_DEBUG_H + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "logging.h" + +struct _runlist_element; + +#ifdef DEBUG +extern void ntfs_debug_runlist_dump(const struct _runlist_element *rl); +#else +static __inline__ void ntfs_debug_runlist_dump(const struct _runlist_element *rl __attribute__((unused))) {} +#endif + +#define NTFS_BUG(msg) \ +{ \ + int ___i; \ + ntfs_log_critical("Bug in %s(): %s\n", __FUNCTION__, msg); \ + ntfs_log_debug("Forcing segmentation fault!"); \ + ___i = ((int*)NULL)[1]; \ +} + +#endif /* defined _NTFS_DEBUG_H */ diff --git a/include/ntfs-3g/device.h b/include/ntfs-3g/device.h new file mode 100644 index 00000000..a19d29c4 --- /dev/null +++ b/include/ntfs-3g/device.h @@ -0,0 +1,128 @@ +/* + * device.h - Exports for low level device io. Originated from the Linux-NTFS project. + * + * Copyright (c) 2000-2006 Anton Altaparmakov + * + * 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 + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program/include file 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 + */ + +#ifndef _NTFS_DEVICE_H +#define _NTFS_DEVICE_H + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "device_io.h" +#include "types.h" +#include "support.h" +#include "volume.h" + +/** + * enum ntfs_device_state_bits - + * + * Defined bits for the state field in the ntfs_device structure. + */ +typedef enum { + ND_Open, /* 1: Device is open. */ + ND_ReadOnly, /* 1: Device is read-only. */ + ND_Dirty, /* 1: Device is dirty, needs sync. */ + ND_Block, /* 1: Device is a block device. */ +} ntfs_device_state_bits; + +#define test_ndev_flag(nd, flag) test_bit(ND_##flag, (nd)->d_state) +#define set_ndev_flag(nd, flag) set_bit(ND_##flag, (nd)->d_state) +#define clear_ndev_flag(nd, flag) clear_bit(ND_##flag, (nd)->d_state) + +#define NDevOpen(nd) test_ndev_flag(nd, Open) +#define NDevSetOpen(nd) set_ndev_flag(nd, Open) +#define NDevClearOpen(nd) clear_ndev_flag(nd, Open) + +#define NDevReadOnly(nd) test_ndev_flag(nd, ReadOnly) +#define NDevSetReadOnly(nd) set_ndev_flag(nd, ReadOnly) +#define NDevClearReadOnly(nd) clear_ndev_flag(nd, ReadOnly) + +#define NDevDirty(nd) test_ndev_flag(nd, Dirty) +#define NDevSetDirty(nd) set_ndev_flag(nd, Dirty) +#define NDevClearDirty(nd) clear_ndev_flag(nd, Dirty) + +#define NDevBlock(nd) test_ndev_flag(nd, Block) +#define NDevSetBlock(nd) set_ndev_flag(nd, Block) +#define NDevClearBlock(nd) clear_ndev_flag(nd, Block) + +/** + * struct ntfs_device - + * + * The ntfs device structure defining all operations needed to access the low + * level device underlying the ntfs volume. + */ +struct ntfs_device { + struct ntfs_device_operations *d_ops; /* Device operations. */ + unsigned long d_state; /* State of the device. */ + char *d_name; /* Name of device. */ + void *d_private; /* Private data used by the + device operations. */ +}; + +struct stat; + +/** + * struct ntfs_device_operations - + * + * The ntfs device operations defining all operations that can be performed on + * the low level device described by an ntfs device structure. + */ +struct ntfs_device_operations { + int (*open)(struct ntfs_device *dev, int flags); + int (*close)(struct ntfs_device *dev); + s64 (*seek)(struct ntfs_device *dev, s64 offset, int whence); + s64 (*read)(struct ntfs_device *dev, void *buf, s64 count); + s64 (*write)(struct ntfs_device *dev, const void *buf, s64 count); + s64 (*pread)(struct ntfs_device *dev, void *buf, s64 count, s64 offset); + s64 (*pwrite)(struct ntfs_device *dev, const void *buf, s64 count, + s64 offset); + int (*sync)(struct ntfs_device *dev); + int (*stat)(struct ntfs_device *dev, struct stat *buf); + int (*ioctl)(struct ntfs_device *dev, int request, void *argp); +}; + +extern struct ntfs_device *ntfs_device_alloc(const char *name, const long state, + struct ntfs_device_operations *dops, void *priv_data); +extern int ntfs_device_free(struct ntfs_device *dev); + +extern s64 ntfs_pread(struct ntfs_device *dev, const s64 pos, s64 count, + void *b); +extern s64 ntfs_pwrite(struct ntfs_device *dev, const s64 pos, s64 count, + const void *b); + +extern s64 ntfs_mst_pread(struct ntfs_device *dev, const s64 pos, s64 count, + const u32 bksize, void *b); +extern s64 ntfs_mst_pwrite(struct ntfs_device *dev, const s64 pos, s64 count, + const u32 bksize, void *b); + +extern s64 ntfs_cluster_read(const ntfs_volume *vol, const s64 lcn, + const s64 count, void *b); +extern s64 ntfs_cluster_write(const ntfs_volume *vol, const s64 lcn, + const s64 count, const void *b); + +extern s64 ntfs_device_size_get(struct ntfs_device *dev, int block_size); +extern s64 ntfs_device_partition_start_sector_get(struct ntfs_device *dev); +extern int ntfs_device_heads_get(struct ntfs_device *dev); +extern int ntfs_device_sectors_per_track_get(struct ntfs_device *dev); +extern int ntfs_device_sector_size_get(struct ntfs_device *dev); +extern int ntfs_device_block_size_set(struct ntfs_device *dev, int block_size); + +#endif /* defined _NTFS_DEVICE_H */ diff --git a/include/ntfs-3g/device_io.h b/include/ntfs-3g/device_io.h new file mode 100644 index 00000000..8437cc2d --- /dev/null +++ b/include/ntfs-3g/device_io.h @@ -0,0 +1,77 @@ +/* + * device_io.h - Exports for default device io. Originated from the Linux-NTFS project. + * + * Copyright (c) 2000-2006 Anton Altaparmakov + * + * 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 + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program/include file 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 + */ + +#ifndef _NTFS_DEVICE_IO_H +#define _NTFS_DEVICE_IO_H + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifndef NO_NTFS_DEVICE_DEFAULT_IO_OPS + +#ifndef __CYGWIN32__ + +/* Not on Cygwin; use standard Unix style low level device operations. */ +#define ntfs_device_default_io_ops ntfs_device_unix_io_ops + +#else /* __CYGWIN32__ */ + +#ifndef HDIO_GETGEO +# define HDIO_GETGEO 0x301 +/** + * struct hd_geometry - + */ +struct hd_geometry { + unsigned char heads; + unsigned char sectors; + unsigned short cylinders; + unsigned long start; +}; +#endif +#ifndef BLKGETSIZE +# define BLKGETSIZE 0x1260 +#endif +#ifndef BLKSSZGET +# define BLKSSZGET 0x1268 +#endif +#ifndef BLKGETSIZE64 +# define BLKGETSIZE64 0x80041272 +#endif +#ifndef BLKBSZSET +# define BLKBSZSET 0x40041271 +#endif + +/* On Cygwin; use Win32 low level device operations. */ +#define ntfs_device_default_io_ops ntfs_device_win32_io_ops + +#endif /* __CYGWIN32__ */ + + +/* Forward declaration. */ +struct ntfs_device_operations; + +extern struct ntfs_device_operations ntfs_device_default_io_ops; + +#endif /* NO_NTFS_DEVICE_DEFAULT_IO_OPS */ + +#endif /* defined _NTFS_DEVICE_IO_H */ + diff --git a/include/ntfs-3g/dir.h b/include/ntfs-3g/dir.h new file mode 100644 index 00000000..33e1b2e1 --- /dev/null +++ b/include/ntfs-3g/dir.h @@ -0,0 +1,111 @@ +/* + * dir.h - Exports for directory handling. Originated from the Linux-NTFS project. + * + * Copyright (c) 2002 Anton Altaparmakov + * Copyright (c) 2005-2006 Yura Pakhuchiy + * Copyright (c) 2004-2005 Richard Russon + * + * 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 + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program/include file 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 + */ + +#ifndef _NTFS_DIR_H +#define _NTFS_DIR_H + +#include "types.h" + +#define PATH_SEP '/' + +#ifndef MAX_PATH +#define MAX_PATH 1024 +#endif + +/* + * We do not have these under DJGPP, so define our version that do not conflict + * with other S_IFs defined under DJGPP. + */ +#ifdef DJGPP +#ifndef S_IFLNK +#define S_IFLNK 0120000 +#endif +#ifndef S_ISLNK +#define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK) +#endif +#ifndef S_IFSOCK +#define S_IFSOCK 0140000 +#endif +#ifndef S_ISSOCK +#define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK) +#endif +#endif + +/* + * The little endian Unicode strings $I30, $SII, $SDH, $O, $Q, $R + * as a global constant. + */ +extern ntfschar NTFS_INDEX_I30[5]; +extern ntfschar NTFS_INDEX_SII[5]; +extern ntfschar NTFS_INDEX_SDH[5]; +extern ntfschar NTFS_INDEX_O[3]; +extern ntfschar NTFS_INDEX_Q[3]; +extern ntfschar NTFS_INDEX_R[3]; + +extern u64 ntfs_inode_lookup_by_name(ntfs_inode *dir_ni, + const ntfschar *uname, const int uname_len); + +extern ntfs_inode *ntfs_pathname_to_inode(ntfs_volume *vol, ntfs_inode *parent, + const char *pathname); + +extern ntfs_inode *ntfs_create(ntfs_inode *dir_ni, ntfschar *name, u8 name_len, + dev_t type); +extern ntfs_inode *ntfs_create_device(ntfs_inode *dir_ni, + ntfschar *name, u8 name_len, dev_t type, dev_t dev); +extern ntfs_inode *ntfs_create_symlink(ntfs_inode *dir_ni, + ntfschar *name, u8 name_len, ntfschar *target, u8 target_len); +extern int ntfs_check_empty_dir(ntfs_inode *ni); +extern int ntfs_delete(ntfs_inode *ni, ntfs_inode *dir_ni, ntfschar *name, + u8 name_len); + +extern int ntfs_link(ntfs_inode *ni, ntfs_inode *dir_ni, ntfschar *name, + u8 name_len); + +/* + * File types (adapted from include ) + */ +#define NTFS_DT_UNKNOWN 0 +#define NTFS_DT_FIFO 1 +#define NTFS_DT_CHR 2 +#define NTFS_DT_DIR 4 +#define NTFS_DT_BLK 6 +#define NTFS_DT_REG 8 +#define NTFS_DT_LNK 10 +#define NTFS_DT_SOCK 12 +#define NTFS_DT_WHT 14 + +/* + * This is the "ntfs_filldir" function type, used by ntfs_readdir() to let + * the caller specify what kind of dirent layout it wants to have. + * This allows the caller to read directories into their application or + * to have different dirent layouts depending on the binary type. + */ +typedef int (*ntfs_filldir_t)(void *dirent, const ntfschar *name, + const int name_len, const int name_type, const s64 pos, + const MFT_REF mref, const unsigned dt_type); + +extern int ntfs_readdir(ntfs_inode *dir_ni, s64 *pos, + void *dirent, ntfs_filldir_t filldir); + +#endif /* defined _NTFS_DIR_H */ + diff --git a/include/ntfs-3g/endians.h b/include/ntfs-3g/endians.h new file mode 100644 index 00000000..397f1c20 --- /dev/null +++ b/include/ntfs-3g/endians.h @@ -0,0 +1,203 @@ +/* + * endians.h - Definitions related to handling of byte ordering. + * Originated from the Linux-NTFS project. + * + * Copyright (c) 2000-2005 Anton Altaparmakov + * + * 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 + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program/include file 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 + */ + +#ifndef _NTFS_ENDIANS_H +#define _NTFS_ENDIANS_H + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/* + * Notes: + * We define the conversion functions including typecasts since the + * defaults don't necessarily perform appropriate typecasts. + * Also, using our own functions means that we can change them if it + * turns out that we do need to use the unaligned access macros on + * architectures requiring aligned memory accesses... + */ + +#ifdef HAVE_ENDIAN_H +#include +#endif +#ifdef HAVE_SYS_ENDIAN_H +#include +#endif +#ifdef HAVE_MACHINE_ENDIAN_H +#include +#endif +#ifdef HAVE_SYS_BYTEORDER_H +#include +#endif +#ifdef HAVE_SYS_PARAM_H +#include +#endif + +#ifndef __BYTE_ORDER +# if defined(_BYTE_ORDER) +# define __BYTE_ORDER _BYTE_ORDER +# define __LITTLE_ENDIAN _LITTLE_ENDIAN +# define __BIG_ENDIAN _BIG_ENDIAN +# elif defined(BYTE_ORDER) +# define __BYTE_ORDER BYTE_ORDER +# define __LITTLE_ENDIAN LITTLE_ENDIAN +# define __BIG_ENDIAN BIG_ENDIAN +# elif defined(__BYTE_ORDER__) +# define __BYTE_ORDER __BYTE_ORDER__ +# define __LITTLE_ENDIAN __LITTLE_ENDIAN__ +# define __BIG_ENDIAN __BIG_ENDIAN__ +# elif (defined(_LITTLE_ENDIAN) && !defined(_BIG_ENDIAN)) || \ + defined(WORDS_LITTLEENDIAN) +# define __BYTE_ORDER 1 +# define __LITTLE_ENDIAN 1 +# define __BIG_ENDIAN 0 +# elif (!defined(_LITTLE_ENDIAN) && defined(_BIG_ENDIAN)) || \ + defined(WORDS_BIGENDIAN) +# define __BYTE_ORDER 0 +# define __LITTLE_ENDIAN 1 +# define __BIG_ENDIAN 0 +# else +# error "__BYTE_ORDER is not defined." +# endif +#endif + +#define __ntfs_bswap_constant_16(x) \ + (u16)((((u16)(x) & 0xff00) >> 8) | \ + (((u16)(x) & 0x00ff) << 8)) + +#define __ntfs_bswap_constant_32(x) \ + (u32)((((u32)(x) & 0xff000000u) >> 24) | \ + (((u32)(x) & 0x00ff0000u) >> 8) | \ + (((u32)(x) & 0x0000ff00u) << 8) | \ + (((u32)(x) & 0x000000ffu) << 24)) + +#define __ntfs_bswap_constant_64(x) \ + (u64)((((u64)(x) & 0xff00000000000000ull) >> 56) | \ + (((u64)(x) & 0x00ff000000000000ull) >> 40) | \ + (((u64)(x) & 0x0000ff0000000000ull) >> 24) | \ + (((u64)(x) & 0x000000ff00000000ull) >> 8) | \ + (((u64)(x) & 0x00000000ff000000ull) << 8) | \ + (((u64)(x) & 0x0000000000ff0000ull) << 24) | \ + (((u64)(x) & 0x000000000000ff00ull) << 40) | \ + (((u64)(x) & 0x00000000000000ffull) << 56)) + +#ifdef HAVE_BYTESWAP_H +# include +#else +# define bswap_16(x) __ntfs_bswap_constant_16(x) +# define bswap_32(x) __ntfs_bswap_constant_32(x) +# define bswap_64(x) __ntfs_bswap_constant_64(x) +#endif + +#if defined(__LITTLE_ENDIAN) && (__BYTE_ORDER == __LITTLE_ENDIAN) + +#define __le16_to_cpu(x) (x) +#define __le32_to_cpu(x) (x) +#define __le64_to_cpu(x) (x) + +#define __cpu_to_le16(x) (x) +#define __cpu_to_le32(x) (x) +#define __cpu_to_le64(x) (x) + +#define __constant_le16_to_cpu(x) (x) +#define __constant_le32_to_cpu(x) (x) +#define __constant_le64_to_cpu(x) (x) + +#define __constant_cpu_to_le16(x) (x) +#define __constant_cpu_to_le32(x) (x) +#define __constant_cpu_to_le64(x) (x) + +#elif defined(__BIG_ENDIAN) && (__BYTE_ORDER == __BIG_ENDIAN) + +#define __le16_to_cpu(x) bswap_16(x) +#define __le32_to_cpu(x) bswap_32(x) +#define __le64_to_cpu(x) bswap_64(x) + +#define __cpu_to_le16(x) bswap_16(x) +#define __cpu_to_le32(x) bswap_32(x) +#define __cpu_to_le64(x) bswap_64(x) + +#define __constant_le16_to_cpu(x) __ntfs_bswap_constant_16((u16)(x)) +#define __constant_le32_to_cpu(x) __ntfs_bswap_constant_32((u32)(x)) +#define __constant_le64_to_cpu(x) __ntfs_bswap_constant_64((u64)(x)) + +#define __constant_cpu_to_le16(x) __ntfs_bswap_constant_16((u16)(x)) +#define __constant_cpu_to_le32(x) __ntfs_bswap_constant_32((u32)(x)) +#define __constant_cpu_to_le64(x) __ntfs_bswap_constant_64((u64)(x)) + +#else + +#error "You must define __BYTE_ORDER to be __LITTLE_ENDIAN or __BIG_ENDIAN." + +#endif + +/* Unsigned from LE to CPU conversion. */ + +#define le16_to_cpu(x) (u16)__le16_to_cpu((u16)(x)) +#define le32_to_cpu(x) (u32)__le32_to_cpu((u32)(x)) +#define le64_to_cpu(x) (u64)__le64_to_cpu((u64)(x)) + +#define le16_to_cpup(x) (u16)__le16_to_cpu(*(const u16*)(x)) +#define le32_to_cpup(x) (u32)__le32_to_cpu(*(const u32*)(x)) +#define le64_to_cpup(x) (u64)__le64_to_cpu(*(const u64*)(x)) + +/* Signed from LE to CPU conversion. */ + +#define sle16_to_cpu(x) (s16)__le16_to_cpu((s16)(x)) +#define sle32_to_cpu(x) (s32)__le32_to_cpu((s32)(x)) +#define sle64_to_cpu(x) (s64)__le64_to_cpu((s64)(x)) + +#define sle16_to_cpup(x) (s16)__le16_to_cpu(*(s16*)(x)) +#define sle32_to_cpup(x) (s32)__le32_to_cpu(*(s32*)(x)) +#define sle64_to_cpup(x) (s64)__le64_to_cpu(*(s64*)(x)) + +/* Unsigned from CPU to LE conversion. */ + +#define cpu_to_le16(x) (u16)__cpu_to_le16((u16)(x)) +#define cpu_to_le32(x) (u32)__cpu_to_le32((u32)(x)) +#define cpu_to_le64(x) (u64)__cpu_to_le64((u64)(x)) + +#define cpu_to_le16p(x) (u16)__cpu_to_le16(*(u16*)(x)) +#define cpu_to_le32p(x) (u32)__cpu_to_le32(*(u32*)(x)) +#define cpu_to_le64p(x) (u64)__cpu_to_le64(*(u64*)(x)) + +/* Signed from CPU to LE conversion. */ + +#define cpu_to_sle16(x) (s16)__cpu_to_le16((s16)(x)) +#define cpu_to_sle32(x) (s32)__cpu_to_le32((s32)(x)) +#define cpu_to_sle64(x) (s64)__cpu_to_le64((s64)(x)) + +#define cpu_to_sle16p(x) (s16)__cpu_to_le16(*(s16*)(x)) +#define cpu_to_sle32p(x) (s32)__cpu_to_le32(*(s32*)(x)) +#define cpu_to_sle64p(x) (s64)__cpu_to_le64(*(s64*)(x)) + +/* Constant endianness conversion defines. */ + +#define const_le16_to_cpu(x) __constant_le16_to_cpu(x) +#define const_le32_to_cpu(x) __constant_le32_to_cpu(x) +#define const_le64_to_cpu(x) __constant_le64_to_cpu(x) + +#define const_cpu_to_le16(x) __constant_cpu_to_le16(x) +#define const_cpu_to_le32(x) __constant_cpu_to_le32(x) +#define const_cpu_to_le64(x) __constant_cpu_to_le64(x) + +#endif /* defined _NTFS_ENDIANS_H */ diff --git a/include/ntfs-3g/index.h b/include/ntfs-3g/index.h new file mode 100644 index 00000000..9d7fd88b --- /dev/null +++ b/include/ntfs-3g/index.h @@ -0,0 +1,131 @@ +/* + * index.h - Defines for NTFS index handling. Originated from the Linux-NTFS project. + * + * Copyright (c) 2004 Anton Altaparmakov + * Copyright (c) 2004-2005 Richard Russon + * Copyright (c) 2005 Yura Pakhuchiy + * Copyright (c) 2006 Szabolcs Szakacsits + * + * 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 + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program/include file 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 + */ + +#ifndef _NTFS_INDEX_H +#define _NTFS_INDEX_H + +#include "attrib.h" +#include "types.h" +#include "layout.h" +#include "inode.h" +#include "mft.h" + +#define VCN_INDEX_ROOT_PARENT ((VCN)-2) + +#define MAX_PARENT_VCN 32 + +/** + * struct ntfs_index_context - + * @ni: inode containing the @entry described by this context + * @name: name of the index described by this context + * @name_len: length of the index name + * @entry: index entry (points into @ir or @ia) + * @data: index entry data (points into @entry) + * @data_len: length in bytes of @data + * @is_in_root: TRUE if @entry is in @ir or FALSE if it is in @ia + * @ir: index root if @is_in_root or NULL otherwise + * @actx: attribute search context if in root or NULL otherwise + * @ia: index block if @is_in_root is FALSE or NULL otherwise + * @ia_na: opened INDEX_ALLOCATION attribute + * @ib_vcn: VCN from which @ia where read from + * @parent_pos: parent entries' positions in the index block + * @parent_vcn: entry's parent node or VCN_INDEX_ROOT_PARENT + * @new_vcn: new VCN if we need to create a new index block + * @median: move to the parent if splitting index blocks + * @ib_dirty: TRUE if index block was changed + * @block_size: index block size + * @vcn_size_bits: VCN size bits for this index block + * + * @ni is the inode this context belongs to. + * + * @entry is the index entry described by this context. @data and @data_len + * are the index entry data and its length in bytes, respectively. @data + * simply points into @entry. This is probably what the user is interested in. + * + * If @is_in_root is TRUE, @entry is in the index root attribute @ir described + * by the attribute search context @actx and inode @ni. @ia, @ib_vcn and + * @ib_dirty are undefined in this case. + * + * If @is_in_root is FALSE, @entry is in the index allocation attribute and @ia + * and @ib_vcn point to the index allocation block and VCN where it's placed, + * respectively. @ir and @actx are NULL in this case. @ia_na is opened + * INDEX_ALLOCATION attribute. @ib_dirty is TRUE if index block was changed and + * FALSE otherwise. + * + * To obtain a context call ntfs_index_ctx_get(). + * + * When finished with the @entry and its @data, call ntfs_index_ctx_put() to + * free the context and other associated resources. + * + * If the index entry was modified, call ntfs_index_entry_mark_dirty() before + * the call to ntfs_index_ctx_put() to ensure that the changes are written + * to disk. + */ +typedef struct { + ntfs_inode *ni; + ntfschar *name; + u32 name_len; + INDEX_ENTRY *entry; + void *data; + u16 data_len; + COLLATION_RULES cr; + BOOL is_in_root; + INDEX_ROOT *ir; + ntfs_attr_search_ctx *actx; + INDEX_BLOCK *ib; + ntfs_attr *ia_na; + int parent_pos[MAX_PARENT_VCN]; /* parent entries' positions */ + VCN ib_vcn; + VCN parent_vcn[MAX_PARENT_VCN]; /* entry's parent nodes */ + int max_depth; /* number of the parent nodes */ + int pindex; /* maximum it's the number of the parent nodes */ + BOOL ib_dirty; + u32 block_size; + u8 vcn_size_bits; +} ntfs_index_context; + +extern ntfs_index_context *ntfs_index_ctx_get(ntfs_inode *ni, + ntfschar *name, u32 name_len); +extern void ntfs_index_ctx_put(ntfs_index_context *ictx); +extern void ntfs_index_ctx_reinit(ntfs_index_context *ictx); + +extern int ntfs_index_lookup(const void *key, const int key_len, + ntfs_index_context *ictx); + +extern int ntfs_index_add_filename(ntfs_inode *ni, FILE_NAME_ATTR *fn, + MFT_REF mref); +extern int ntfs_index_rm(ntfs_index_context *ictx); + +extern INDEX_ROOT *ntfs_index_root_get(ntfs_inode *ni, ATTR_RECORD *attr); + +extern VCN ntfs_ie_get_vcn(INDEX_ENTRY *ie); + +extern void ntfs_index_entry_mark_dirty(ntfs_index_context *ictx); + +extern char *ntfs_ie_filename_get(INDEX_ENTRY *ie); +extern void ntfs_ie_filename_dump(INDEX_ENTRY *ie); +extern void ntfs_ih_filename_dump(INDEX_HEADER *ih); + +#endif /* _NTFS_INDEX_H */ + diff --git a/include/ntfs-3g/inode.h b/include/ntfs-3g/inode.h new file mode 100644 index 00000000..1dba1737 --- /dev/null +++ b/include/ntfs-3g/inode.h @@ -0,0 +1,176 @@ +/* + * inode.h - Defines for NTFS inode handling. Originated from the Linux-NTFS project. + * + * Copyright (c) 2001,2002 Anton Altaparmakov + * Copyright (c) 2004-2005 Yura Pakhuchiy + * Copyright (c) 2004-2005 Richard Russon + * Copyright (c) 2006 Szabolcs Szakacsits + * + * 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 + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program/include file 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 + */ + +#ifndef _NTFS_INODE_H +#define _NTFS_INODE_H + +/* Forward declaration */ +typedef struct _ntfs_inode ntfs_inode; + +#include "types.h" +#include "layout.h" +#include "support.h" +#include "volume.h" + +/** + * enum ntfs_inode_state_bits - + * + * Defined bits for the state field in the ntfs_inode structure. + * (f) = files only, (d) = directories only + */ +typedef enum { + NI_Dirty, /* 1: Mft record needs to be written to disk. */ + + /* The NI_AttrList* tests only make sense for base inodes. */ + NI_AttrList, /* 1: Mft record contains an attribute list. */ + NI_AttrListDirty, /* 1: Attribute list needs to be written to the + mft record and then to disk. */ + NI_FileNameDirty, /* 1: FILE_NAME attributes need to be updated + in the index. */ +} ntfs_inode_state_bits; + +#define test_nino_flag(ni, flag) test_bit(NI_##flag, (ni)->state) +#define set_nino_flag(ni, flag) set_bit(NI_##flag, (ni)->state) +#define clear_nino_flag(ni, flag) clear_bit(NI_##flag, (ni)->state) + +#define test_and_set_nino_flag(ni, flag) \ + test_and_set_bit(NI_##flag, (ni)->state) +#define test_and_clear_nino_flag(ni, flag) \ + test_and_clear_bit(NI_##flag, (ni)->state) + +#define NInoDirty(ni) test_nino_flag(ni, Dirty) +#define NInoSetDirty(ni) set_nino_flag(ni, Dirty) +#define NInoClearDirty(ni) clear_nino_flag(ni, Dirty) +#define NInoTestAndSetDirty(ni) test_and_set_nino_flag(ni, Dirty) +#define NInoTestAndClearDirty(ni) test_and_clear_nino_flag(ni, Dirty) + +#define NInoAttrList(ni) test_nino_flag(ni, AttrList) +#define NInoSetAttrList(ni) set_nino_flag(ni, AttrList) +#define NInoClearAttrList(ni) clear_nino_flag(ni, AttrList) + + +#define test_nino_al_flag(ni, flag) test_nino_flag(ni, AttrList##flag) +#define set_nino_al_flag(ni, flag) set_nino_flag(ni, AttrList##flag) +#define clear_nino_al_flag(ni, flag) clear_nino_flag(ni, AttrList##flag) + +#define test_and_set_nino_al_flag(ni, flag) \ + test_and_set_nino_flag(ni, AttrList##flag) +#define test_and_clear_nino_al_flag(ni, flag) \ + test_and_clear_nino_flag(ni, AttrList##flag) + +#define NInoAttrListDirty(ni) test_nino_al_flag(ni, Dirty) +#define NInoAttrListSetDirty(ni) set_nino_al_flag(ni, Dirty) +#define NInoAttrListClearDirty(ni) clear_nino_al_flag(ni, Dirty) +#define NInoAttrListTestAndSetDirty(ni) test_and_set_nino_al_flag(ni, Dirty) +#define NInoAttrListTestAndClearDirty(ni) test_and_clear_nino_al_flag(ni, Dirty) + +#define NInoFileNameDirty(ni) \ + test_nino_flag(ni, FileNameDirty) +#define NInoFileNameSetDirty(ni) \ + set_nino_flag(ni, FileNameDirty) +#define NInoFileNameClearDirty(ni) \ + clear_nino_flag(ni, FileNameDirty) +#define NInoFileNameTestAndSetDirty(ni) \ + test_and_set_nino_flag(ni, FileNameDirty) +#define NInoFileNameTestAndClearDirty(ni) \ + test_and_clear_nino_flag(ni, FileNameDirty) + +/** + * struct _ntfs_inode - The NTFS in-memory inode structure. + * + * It is just used as an extension to the fields already provided in the VFS + * inode. + */ +struct _ntfs_inode { + u64 mft_no; /* Inode / mft record number. */ + MFT_RECORD *mrec; /* The actual mft record of the inode. */ + ntfs_volume *vol; /* Pointer to the ntfs volume of this inode. */ + unsigned long state; /* NTFS specific flags describing this inode. + See ntfs_inode_state_bits above. */ + FILE_ATTR_FLAGS flags; /* Flags describing the file. + (Copy from STANDARD_INFORMATION) */ + /* + * Attribute list support (for use by the attribute lookup functions). + * Setup during ntfs_open_inode() for all inodes with attribute lists. + * Only valid if NI_AttrList is set in state. + */ + u32 attr_list_size; /* Length of attribute list value in bytes. */ + u8 *attr_list; /* Attribute list value itself. */ + /* Below fields are always valid. */ + s32 nr_extents; /* For a base mft record, the number of + attached extent inodes (0 if none), for + extent records this is -1. */ + union { /* This union is only used if nr_extents != 0. */ + ntfs_inode **extent_nis;/* For nr_extents > 0, array of the + ntfs inodes of the extent mft + records belonging to this base + inode which have been loaded. */ + ntfs_inode *base_ni; /* For nr_extents == -1, the ntfs + inode of the base mft record. */ + }; + + /* Temp: for directory handling */ + void *private_data; /* ntfs_dt containing this inode */ + int ref_count; + + /* Below fields are valid only for base inode. */ + s64 data_size; /* Data size stored in the filename index. */ + s64 allocated_size; /* Allocated size stored in the filename + index. (NOTE: Equal to allocated size of + the unnamed data attribute for normal or + encrypted files and to compressed size + of the unnamed data attribute for sparse or + compressed files.) */ + + time_t creation_time; + time_t last_data_change_time; + time_t last_mft_change_time; + time_t last_access_time; +}; + +extern ntfs_inode *ntfs_inode_allocate(ntfs_volume *vol); + +extern ntfs_inode *ntfs_inode_open(ntfs_volume *vol, const MFT_REF mref); + +extern int ntfs_inode_close(ntfs_inode *ni); + +extern ntfs_inode *ntfs_extent_inode_open(ntfs_inode *base_ni, + const MFT_REF mref); + +extern int ntfs_inode_attach_all_extents(ntfs_inode *ni); + +extern void ntfs_inode_mark_dirty(ntfs_inode *ni); + +extern void ntfs_inode_update_atime(ntfs_inode *ni); +extern void ntfs_inode_update_time(ntfs_inode *ni); + +extern int ntfs_inode_sync(ntfs_inode *ni); + +extern int ntfs_inode_add_attrlist(ntfs_inode *ni); + +extern int ntfs_inode_free_space(ntfs_inode *ni, int size); + +extern int ntfs_inode_badclus_bad(u64 mft_no, ATTR_RECORD *a); + +#endif /* defined _NTFS_INODE_H */ diff --git a/include/ntfs-3g/layout.h b/include/ntfs-3g/layout.h new file mode 100644 index 00000000..165dbf82 --- /dev/null +++ b/include/ntfs-3g/layout.h @@ -0,0 +1,2664 @@ +/* + * layout.h - Ntfs on-disk layout structures. Originated from the Linux-NTFS project. + * + * Copyright (c) 2000-2005 Anton Altaparmakov + * Copyright (c) 2005 Yura Pakhuchiy + * Copyright (c) 2005-2006 Szabolcs Szakacsits + * + * 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 + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program/include file 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 + */ + +#ifndef _NTFS_LAYOUT_H +#define _NTFS_LAYOUT_H + +#include "types.h" +#include "endians.h" +#include "support.h" + +/* The NTFS oem_id */ +#define magicNTFS const_cpu_to_le64(0x202020205346544e) /* "NTFS " */ +#define NTFS_SB_MAGIC 0x5346544e /* 'NTFS' */ + +/* + * Location of bootsector on partition: + * The standard NTFS_BOOT_SECTOR is on sector 0 of the partition. + * On NT4 and above there is one backup copy of the boot sector to + * be found on the last sector of the partition (not normally accessible + * from within Windows as the bootsector contained number of sectors + * value is one less than the actual value!). + * On versions of NT 3.51 and earlier, the backup copy was located at + * number of sectors/2 (integer divide), i.e. in the middle of the volume. + */ + +/** + * struct BIOS_PARAMETER_BLOCK - BIOS parameter block (bpb) structure. + */ +typedef struct { + u16 bytes_per_sector; /* Size of a sector in bytes. */ + u8 sectors_per_cluster; /* Size of a cluster in sectors. */ + u16 reserved_sectors; /* zero */ + u8 fats; /* zero */ + u16 root_entries; /* zero */ + u16 sectors; /* zero */ + u8 media_type; /* 0xf8 = hard disk */ + u16 sectors_per_fat; /* zero */ +/*0x0d*/u16 sectors_per_track; /* Required to boot Windows. */ +/*0x0f*/u16 heads; /* Required to boot Windows. */ +/*0x11*/u32 hidden_sectors; /* Offset to the start of the partition + relative to the disk in sectors. + Required to boot Windows. */ +/*0x15*/u32 large_sectors; /* zero */ +/* sizeof() = 25 (0x19) bytes */ +} __attribute__((__packed__)) BIOS_PARAMETER_BLOCK; + +/** + * struct NTFS_BOOT_SECTOR - NTFS boot sector structure. + */ +typedef struct { + u8 jump[3]; /* Irrelevant (jump to boot up code).*/ + u64 oem_id; /* Magic "NTFS ". */ +/*0x0b*/BIOS_PARAMETER_BLOCK bpb; /* See BIOS_PARAMETER_BLOCK. */ + u8 physical_drive; /* 0x00 floppy, 0x80 hard disk */ + u8 current_head; /* zero */ + u8 extended_boot_signature; /* 0x80 */ + u8 reserved2; /* zero */ +/*0x28*/s64 number_of_sectors; /* Number of sectors in volume. Gives + maximum volume size of 2^63 sectors. + Assuming standard sector size of 512 + bytes, the maximum byte size is + approx. 4.7x10^21 bytes. (-; */ + s64 mft_lcn; /* Cluster location of mft data. */ + s64 mftmirr_lcn; /* Cluster location of copy of mft. */ + s8 clusters_per_mft_record; /* Mft record size in clusters. */ + u8 reserved0[3]; /* zero */ + s8 clusters_per_index_record; /* Index block size in clusters. */ + u8 reserved1[3]; /* zero */ + u64 volume_serial_number; /* Irrelevant (serial number). */ + u32 checksum; /* Boot sector checksum. */ +/*0x54*/u8 bootstrap[426]; /* Irrelevant (boot up code). */ + u16 end_of_sector_marker; /* End of bootsector magic. Always is + 0xaa55 in little endian. */ +/* sizeof() = 512 (0x200) bytes */ +} __attribute__((__packed__)) NTFS_BOOT_SECTOR; + +/** + * enum NTFS_RECORD_TYPES - + * + * Magic identifiers present at the beginning of all ntfs record containing + * records (like mft records for example). + */ +typedef enum { + /* Found in $MFT/$DATA. */ + magic_FILE = const_cpu_to_le32(0x454c4946), /* Mft entry. */ + magic_INDX = const_cpu_to_le32(0x58444e49), /* Index buffer. */ + magic_HOLE = const_cpu_to_le32(0x454c4f48), /* ? (NTFS 3.0+?) */ + + /* Found in $LogFile/$DATA. */ + magic_RSTR = const_cpu_to_le32(0x52545352), /* Restart page. */ + magic_RCRD = const_cpu_to_le32(0x44524352), /* Log record page. */ + + /* Found in $LogFile/$DATA. (May be found in $MFT/$DATA, also?) */ + magic_CHKD = const_cpu_to_le32(0x444b4843), /* Modified by chkdsk. */ + + /* Found in all ntfs record containing records. */ + magic_BAAD = const_cpu_to_le32(0x44414142), /* Failed multi sector + transfer was detected. */ + + /* + * Found in $LogFile/$DATA when a page is full or 0xff bytes and is + * thus not initialized. User has to initialize the page before using + * it. + */ + magic_empty = const_cpu_to_le32(0xffffffff),/* Record is empty and has + to be initialized before + it can be used. */ +} NTFS_RECORD_TYPES; + +/* + * Generic magic comparison macros. Finally found a use for the ## preprocessor + * operator! (-8 + */ +#define ntfs_is_magic(x, m) ( (u32)(x) == (u32)magic_##m ) +#define ntfs_is_magicp(p, m) ( *(u32*)(p) == (u32)magic_##m ) + +/* + * Specialised magic comparison macros for the NTFS_RECORD_TYPES defined above. + */ +#define ntfs_is_file_record(x) ( ntfs_is_magic (x, FILE) ) +#define ntfs_is_file_recordp(p) ( ntfs_is_magicp(p, FILE) ) +#define ntfs_is_mft_record(x) ( ntfs_is_file_record(x) ) +#define ntfs_is_mft_recordp(p) ( ntfs_is_file_recordp(p) ) +#define ntfs_is_indx_record(x) ( ntfs_is_magic (x, INDX) ) +#define ntfs_is_indx_recordp(p) ( ntfs_is_magicp(p, INDX) ) +#define ntfs_is_hole_record(x) ( ntfs_is_magic (x, HOLE) ) +#define ntfs_is_hole_recordp(p) ( ntfs_is_magicp(p, HOLE) ) + +#define ntfs_is_rstr_record(x) ( ntfs_is_magic (x, RSTR) ) +#define ntfs_is_rstr_recordp(p) ( ntfs_is_magicp(p, RSTR) ) +#define ntfs_is_rcrd_record(x) ( ntfs_is_magic (x, RCRD) ) +#define ntfs_is_rcrd_recordp(p) ( ntfs_is_magicp(p, RCRD) ) + +#define ntfs_is_chkd_record(x) ( ntfs_is_magic (x, CHKD) ) +#define ntfs_is_chkd_recordp(p) ( ntfs_is_magicp(p, CHKD) ) + +#define ntfs_is_baad_record(x) ( ntfs_is_magic (x, BAAD) ) +#define ntfs_is_baad_recordp(p) ( ntfs_is_magicp(p, BAAD) ) + +#define ntfs_is_empty_record(x) ( ntfs_is_magic (x, empty) ) +#define ntfs_is_empty_recordp(p) ( ntfs_is_magicp(p, empty) ) + + +#define NTFS_BLOCK_SIZE 512 +#define NTFS_BLOCK_SIZE_BITS 9 + +/** + * struct NTFS_RECORD - + * + * The Update Sequence Array (usa) is an array of the u16 values which belong + * to the end of each sector protected by the update sequence record in which + * this array is contained. Note that the first entry is the Update Sequence + * Number (usn), a cyclic counter of how many times the protected record has + * been written to disk. The values 0 and -1 (ie. 0xffff) are not used. All + * last u16's of each sector have to be equal to the usn (during reading) or + * are set to it (during writing). If they are not, an incomplete multi sector + * transfer has occurred when the data was written. + * The maximum size for the update sequence array is fixed to: + * maximum size = usa_ofs + (usa_count * 2) = 510 bytes + * The 510 bytes comes from the fact that the last u16 in the array has to + * (obviously) finish before the last u16 of the first 512-byte sector. + * This formula can be used as a consistency check in that usa_ofs + + * (usa_count * 2) has to be less than or equal to 510. + */ +typedef struct { + NTFS_RECORD_TYPES magic;/* A four-byte magic identifying the + record type and/or status. */ + u16 usa_ofs; /* Offset to the Update Sequence Array (usa) + from the start of the ntfs record. */ + u16 usa_count; /* Number of u16 sized entries in the usa + including the Update Sequence Number (usn), + thus the number of fixups is the usa_count + minus 1. */ +} __attribute__((__packed__)) NTFS_RECORD; + +/** + * enum NTFS_SYSTEM_FILES - System files mft record numbers. + * + * All these files are always marked as used in the bitmap attribute of the + * mft; presumably in order to avoid accidental allocation for random other + * mft records. Also, the sequence number for each of the system files is + * always equal to their mft record number and it is never modified. + */ +typedef enum { + FILE_MFT = 0, /* Master file table (mft). Data attribute + contains the entries and bitmap attribute + records which ones are in use (bit==1). */ + FILE_MFTMirr = 1, /* Mft mirror: copy of first four mft records + in data attribute. If cluster size > 4kiB, + copy of first N mft records, with + N = cluster_size / mft_record_size. */ + FILE_LogFile = 2, /* Journalling log in data attribute. */ + FILE_Volume = 3, /* Volume name attribute and volume information + attribute (flags and ntfs version). Windows + refers to this file as volume DASD (Direct + Access Storage Device). */ + FILE_AttrDef = 4, /* Array of attribute definitions in data + attribute. */ + FILE_root = 5, /* Root directory. */ + FILE_Bitmap = 6, /* Allocation bitmap of all clusters (lcns) in + data attribute. */ + FILE_Boot = 7, /* Boot sector (always at cluster 0) in data + attribute. */ + FILE_BadClus = 8, /* Contains all bad clusters in the non-resident + data attribute. */ + FILE_Secure = 9, /* Shared security descriptors in data attribute + and two indexes into the descriptors. + Appeared in Windows 2000. Before that, this + file was named $Quota but was unused. */ + FILE_UpCase = 10, /* Uppercase equivalents of all 65536 Unicode + characters in data attribute. */ + FILE_Extend = 11, /* Directory containing other system files (eg. + $ObjId, $Quota, $Reparse and $UsnJrnl). This + is new to NTFS3.0. */ + FILE_reserved12 = 12, /* Reserved for future use (records 12-15). */ + FILE_reserved13 = 13, + FILE_reserved14 = 14, + FILE_reserved15 = 15, + FILE_first_user = 16, /* First user file, used as test limit for + whether to allow opening a file or not. */ +} NTFS_SYSTEM_FILES; + +/** + * enum MFT_RECORD_FLAGS - + * + * These are the so far known MFT_RECORD_* flags (16-bit) which contain + * information about the mft record in which they are present. + * + * MFT_RECORD_IS_4 exists on all $Extend sub-files. + * It seems that it marks it is a metadata file with MFT record >24, however, + * it is unknown if it is limited to metadata files only. + * + * MFT_RECORD_IS_VIEW_INDEX exists on every metafile with a non directory + * index, that means an INDEX_ROOT and an INDEX_ALLOCATION with a name other + * than "$I30". It is unknown if it is limited to metadata files only. + */ +typedef enum { + MFT_RECORD_IN_USE = const_cpu_to_le16(0x0001), + MFT_RECORD_IS_DIRECTORY = const_cpu_to_le16(0x0002), + MFT_RECORD_IS_4 = const_cpu_to_le16(0x0004), + MFT_RECORD_IS_VIEW_INDEX = const_cpu_to_le16(0x0008), + MFT_REC_SPACE_FILLER = 0xffff, /* Just to make flags + 16-bit. */ +} __attribute__((__packed__)) MFT_RECORD_FLAGS; + +/* + * mft references (aka file references or file record segment references) are + * used whenever a structure needs to refer to a record in the mft. + * + * A reference consists of a 48-bit index into the mft and a 16-bit sequence + * number used to detect stale references. + * + * For error reporting purposes we treat the 48-bit index as a signed quantity. + * + * The sequence number is a circular counter (skipping 0) describing how many + * times the referenced mft record has been (re)used. This has to match the + * sequence number of the mft record being referenced, otherwise the reference + * is considered stale and removed (FIXME: only ntfsck or the driver itself?). + * + * If the sequence number is zero it is assumed that no sequence number + * consistency checking should be performed. + * + * FIXME: Since inodes are 32-bit as of now, the driver needs to always check + * for high_part being 0 and if not either BUG(), cause a panic() or handle + * the situation in some other way. This shouldn't be a problem as a volume has + * to become HUGE in order to need more than 32-bits worth of mft records. + * Assuming the standard mft record size of 1kb only the records (never mind + * the non-resident attributes, etc.) would require 4Tb of space on their own + * for the first 32 bits worth of records. This is only if some strange person + * doesn't decide to foul play and make the mft sparse which would be a really + * horrible thing to do as it would trash our current driver implementation. )-: + * Do I hear screams "we want 64-bit inodes!" ?!? (-; + * + * FIXME: The mft zone is defined as the first 12% of the volume. This space is + * reserved so that the mft can grow contiguously and hence doesn't become + * fragmented. Volume free space includes the empty part of the mft zone and + * when the volume's free 88% are used up, the mft zone is shrunk by a factor + * of 2, thus making more space available for more files/data. This process is + * repeated every time there is no more free space except for the mft zone until + * there really is no more free space. + */ + +/* + * Typedef the MFT_REF as a 64-bit value for easier handling. + * Also define two unpacking macros to get to the reference (MREF) and + * sequence number (MSEQNO) respectively. + * The _LE versions are to be applied on little endian MFT_REFs. + * Note: The _LE versions will return a CPU endian formatted value! + */ +#define MFT_REF_MASK_CPU 0x0000ffffffffffffULL +#define MFT_REF_MASK_LE const_cpu_to_le64(MFT_REF_MASK_CPU) + +typedef u64 MFT_REF; + +#define MK_MREF(m, s) ((MFT_REF)(((MFT_REF)(s) << 48) | \ + ((MFT_REF)(m) & MFT_REF_MASK_CPU))) +#define MK_LE_MREF(m, s) const_cpu_to_le64(((MFT_REF)(((MFT_REF)(s) << 48) | \ + ((MFT_REF)(m) & MFT_REF_MASK_CPU)))) + +#define MREF(x) ((u64)((x) & MFT_REF_MASK_CPU)) +#define MSEQNO(x) ((u16)(((x) >> 48) & 0xffff)) +#define MREF_LE(x) ((u64)(const_le64_to_cpu(x) & MFT_REF_MASK_CPU)) +#define MSEQNO_LE(x) ((u16)((const_le64_to_cpu(x) >> 48) & 0xffff)) + +#define IS_ERR_MREF(x) (((x) & 0x0000800000000000ULL) ? 1 : 0) +#define ERR_MREF(x) ((u64)((s64)(x))) +#define MREF_ERR(x) ((int)((s64)(x))) + +/** + * struct MFT_RECORD - An MFT record layout (NTFS 3.1+) + * + * The mft record header present at the beginning of every record in the mft. + * This is followed by a sequence of variable length attribute records which + * is terminated by an attribute of type AT_END which is a truncated attribute + * in that it only consists of the attribute type code AT_END and none of the + * other members of the attribute structure are present. + */ +typedef struct { +/*Ofs*/ +/* 0 NTFS_RECORD; -- Unfolded here as gcc doesn't like unnamed structs. */ + NTFS_RECORD_TYPES magic;/* Usually the magic is "FILE". */ + u16 usa_ofs; /* See NTFS_RECORD definition above. */ + u16 usa_count; /* See NTFS_RECORD definition above. */ + +/* 8*/ LSN lsn; /* $LogFile sequence number for this record. + Changed every time the record is modified. */ +/* 16*/ u16 sequence_number; /* Number of times this mft record has been + reused. (See description for MFT_REF + above.) NOTE: The increment (skipping zero) + is done when the file is deleted. NOTE: If + this is zero it is left zero. */ +/* 18*/ u16 link_count; /* Number of hard links, i.e. the number of + directory entries referencing this record. + NOTE: Only used in mft base records. + NOTE: When deleting a directory entry we + check the link_count and if it is 1 we + delete the file. Otherwise we delete the + FILE_NAME_ATTR being referenced by the + directory entry from the mft record and + decrement the link_count. + FIXME: Careful with Win32 + DOS names! */ +/* 20*/ u16 attrs_offset; /* Byte offset to the first attribute in this + mft record from the start of the mft record. + NOTE: Must be aligned to 8-byte boundary. */ +/* 22*/ MFT_RECORD_FLAGS flags; /* Bit array of MFT_RECORD_FLAGS. When a file + is deleted, the MFT_RECORD_IN_USE flag is + set to zero. */ +/* 24*/ u32 bytes_in_use; /* Number of bytes used in this mft record. + NOTE: Must be aligned to 8-byte boundary. */ +/* 28*/ u32 bytes_allocated; /* Number of bytes allocated for this mft + record. This should be equal to the mft + record size. */ +/* 32*/ MFT_REF base_mft_record; /* This is zero for base mft records. + When it is not zero it is a mft reference + pointing to the base mft record to which + this record belongs (this is then used to + locate the attribute list attribute present + in the base record which describes this + extension record and hence might need + modification when the extension record + itself is modified, also locating the + attribute list also means finding the other + potential extents, belonging to the non-base + mft record). */ +/* 40*/ u16 next_attr_instance; /* The instance number that will be + assigned to the next attribute added to this + mft record. NOTE: Incremented each time + after it is used. NOTE: Every time the mft + record is reused this number is set to zero. + NOTE: The first instance number is always 0. + */ +/* The below fields are specific to NTFS 3.1+ (Windows XP and above): */ +/* 42*/ u16 reserved; /* Reserved/alignment. */ +/* 44*/ u32 mft_record_number; /* Number of this mft record. */ +/* sizeof() = 48 bytes */ +/* + * When (re)using the mft record, we place the update sequence array at this + * offset, i.e. before we start with the attributes. This also makes sense, + * otherwise we could run into problems with the update sequence array + * containing in itself the last two bytes of a sector which would mean that + * multi sector transfer protection wouldn't work. As you can't protect data + * by overwriting it since you then can't get it back... + * When reading we obviously use the data from the ntfs record header. + */ +} __attribute__((__packed__)) MFT_RECORD; + +/** + * struct MFT_RECORD_OLD - An MFT record layout (NTFS <=3.0) + * + * This is the version without the NTFS 3.1+ specific fields. + */ +typedef struct { +/*Ofs*/ +/* 0 NTFS_RECORD; -- Unfolded here as gcc doesn't like unnamed structs. */ + NTFS_RECORD_TYPES magic;/* Usually the magic is "FILE". */ + u16 usa_ofs; /* See NTFS_RECORD definition above. */ + u16 usa_count; /* See NTFS_RECORD definition above. */ + +/* 8*/ LSN lsn; /* $LogFile sequence number for this record. + Changed every time the record is modified. */ +/* 16*/ u16 sequence_number; /* Number of times this mft record has been + reused. (See description for MFT_REF + above.) NOTE: The increment (skipping zero) + is done when the file is deleted. NOTE: If + this is zero it is left zero. */ +/* 18*/ u16 link_count; /* Number of hard links, i.e. the number of + directory entries referencing this record. + NOTE: Only used in mft base records. + NOTE: When deleting a directory entry we + check the link_count and if it is 1 we + delete the file. Otherwise we delete the + FILE_NAME_ATTR being referenced by the + directory entry from the mft record and + decrement the link_count. + FIXME: Careful with Win32 + DOS names! */ +/* 20*/ u16 attrs_offset; /* Byte offset to the first attribute in this + mft record from the start of the mft record. + NOTE: Must be aligned to 8-byte boundary. */ +/* 22*/ MFT_RECORD_FLAGS flags; /* Bit array of MFT_RECORD_FLAGS. When a file + is deleted, the MFT_RECORD_IN_USE flag is + set to zero. */ +/* 24*/ u32 bytes_in_use; /* Number of bytes used in this mft record. + NOTE: Must be aligned to 8-byte boundary. */ +/* 28*/ u32 bytes_allocated; /* Number of bytes allocated for this mft + record. This should be equal to the mft + record size. */ +/* 32*/ MFT_REF base_mft_record; /* This is zero for base mft records. + When it is not zero it is a mft reference + pointing to the base mft record to which + this record belongs (this is then used to + locate the attribute list attribute present + in the base record which describes this + extension record and hence might need + modification when the extension record + itself is modified, also locating the + attribute list also means finding the other + potential extents, belonging to the non-base + mft record). */ +/* 40*/ u16 next_attr_instance; /* The instance number that will be + assigned to the next attribute added to this + mft record. NOTE: Incremented each time + after it is used. NOTE: Every time the mft + record is reused this number is set to zero. + NOTE: The first instance number is always 0. + */ +/* sizeof() = 42 bytes */ +/* + * When (re)using the mft record, we place the update sequence array at this + * offset, i.e. before we start with the attributes. This also makes sense, + * otherwise we could run into problems with the update sequence array + * containing in itself the last two bytes of a sector which would mean that + * multi sector transfer protection wouldn't work. As you can't protect data + * by overwriting it since you then can't get it back... + * When reading we obviously use the data from the ntfs record header. + */ +} __attribute__((__packed__)) MFT_RECORD_OLD; + +/** + * enum ATTR_TYPES - System defined attributes (32-bit). + * + * Each attribute type has a corresponding attribute name (Unicode string of + * maximum 64 character length) as described by the attribute definitions + * present in the data attribute of the $AttrDef system file. + * + * On NTFS 3.0 volumes the names are just as the types are named in the below + * enum exchanging AT_ for the dollar sign ($). If that isn't a revealing + * choice of symbol... (-; + */ +typedef enum { + AT_UNUSED = const_cpu_to_le32( 0), + AT_STANDARD_INFORMATION = const_cpu_to_le32( 0x10), + AT_ATTRIBUTE_LIST = const_cpu_to_le32( 0x20), + AT_FILE_NAME = const_cpu_to_le32( 0x30), + AT_OBJECT_ID = const_cpu_to_le32( 0x40), + AT_SECURITY_DESCRIPTOR = const_cpu_to_le32( 0x50), + AT_VOLUME_NAME = const_cpu_to_le32( 0x60), + AT_VOLUME_INFORMATION = const_cpu_to_le32( 0x70), + AT_DATA = const_cpu_to_le32( 0x80), + AT_INDEX_ROOT = const_cpu_to_le32( 0x90), + AT_INDEX_ALLOCATION = const_cpu_to_le32( 0xa0), + AT_BITMAP = const_cpu_to_le32( 0xb0), + AT_REPARSE_POINT = const_cpu_to_le32( 0xc0), + AT_EA_INFORMATION = const_cpu_to_le32( 0xd0), + AT_EA = const_cpu_to_le32( 0xe0), + AT_PROPERTY_SET = const_cpu_to_le32( 0xf0), + AT_LOGGED_UTILITY_STREAM = const_cpu_to_le32( 0x100), + AT_FIRST_USER_DEFINED_ATTRIBUTE = const_cpu_to_le32( 0x1000), + AT_END = const_cpu_to_le32(0xffffffff), +} ATTR_TYPES; + +/** + * enum COLLATION_RULES - The collation rules for sorting views/indexes/etc + * (32-bit). + * + * COLLATION_UNICODE_STRING - Collate Unicode strings by comparing their binary + * Unicode values, except that when a character can be uppercased, the + * upper case value collates before the lower case one. + * COLLATION_FILE_NAME - Collate file names as Unicode strings. The collation + * is done very much like COLLATION_UNICODE_STRING. In fact I have no idea + * what the difference is. Perhaps the difference is that file names + * would treat some special characters in an odd way (see + * unistr.c::ntfs_collate_names() and unistr.c::legal_ansi_char_array[] + * for what I mean but COLLATION_UNICODE_STRING would not give any special + * treatment to any characters at all, but this is speculation. + * COLLATION_NTOFS_ULONG - Sorting is done according to ascending u32 key + * values. E.g. used for $SII index in FILE_Secure, which sorts by + * security_id (u32). + * COLLATION_NTOFS_SID - Sorting is done according to ascending SID values. + * E.g. used for $O index in FILE_Extend/$Quota. + * COLLATION_NTOFS_SECURITY_HASH - Sorting is done first by ascending hash + * values and second by ascending security_id values. E.g. used for $SDH + * index in FILE_Secure. + * COLLATION_NTOFS_ULONGS - Sorting is done according to a sequence of ascending + * u32 key values. E.g. used for $O index in FILE_Extend/$ObjId, which + * sorts by object_id (16-byte), by splitting up the object_id in four + * u32 values and using them as individual keys. E.g. take the following + * two security_ids, stored as follows on disk: + * 1st: a1 61 65 b7 65 7b d4 11 9e 3d 00 e0 81 10 42 59 + * 2nd: 38 14 37 d2 d2 f3 d4 11 a5 21 c8 6b 79 b1 97 45 + * To compare them, they are split into four u32 values each, like so: + * 1st: 0xb76561a1 0x11d47b65 0xe0003d9e 0x59421081 + * 2nd: 0xd2371438 0x11d4f3d2 0x6bc821a5 0x4597b179 + * Now, it is apparent why the 2nd object_id collates after the 1st: the + * first u32 value of the 1st object_id is less than the first u32 of + * the 2nd object_id. If the first u32 values of both object_ids were + * equal then the second u32 values would be compared, etc. + */ +typedef enum { + COLLATION_BINARY = const_cpu_to_le32(0), /* Collate by binary + compare where the first byte is most + significant. */ + COLLATION_FILE_NAME = const_cpu_to_le32(1), /* Collate file names + as Unicode strings. */ + COLLATION_UNICODE_STRING = const_cpu_to_le32(2), /* Collate Unicode + strings by comparing their binary + Unicode values, except that when a + character can be uppercased, the upper + case value collates before the lower + case one. */ + COLLATION_NTOFS_ULONG = const_cpu_to_le32(16), + COLLATION_NTOFS_SID = const_cpu_to_le32(17), + COLLATION_NTOFS_SECURITY_HASH = const_cpu_to_le32(18), + COLLATION_NTOFS_ULONGS = const_cpu_to_le32(19), +} COLLATION_RULES; + +/** + * enum ATTR_DEF_FLAGS - + * + * The flags (32-bit) describing attribute properties in the attribute + * definition structure. FIXME: This information is based on Regis's + * information and, according to him, it is not certain and probably + * incomplete. The INDEXABLE flag is fairly certainly correct as only the file + * name attribute has this flag set and this is the only attribute indexed in + * NT4. + */ +typedef enum { + ATTR_DEF_INDEXABLE = const_cpu_to_le32(0x02), /* Attribute can be + indexed. */ + ATTR_DEF_MULTIPLE = const_cpu_to_le32(0x04), /* Attribute type + can be present multiple times in the + mft records of an inode. */ + ATTR_DEF_NOT_ZERO = const_cpu_to_le32(0x08), /* Attribute value + must contain at least one non-zero + byte. */ + ATTR_DEF_INDEXED_UNIQUE = const_cpu_to_le32(0x10), /* Attribute must be + indexed and the attribute value must be + unique for the attribute type in all of + the mft records of an inode. */ + ATTR_DEF_NAMED_UNIQUE = const_cpu_to_le32(0x20), /* Attribute must be + named and the name must be unique for + the attribute type in all of the mft + records of an inode. */ + ATTR_DEF_RESIDENT = const_cpu_to_le32(0x40), /* Attribute must be + resident. */ + ATTR_DEF_ALWAYS_LOG = const_cpu_to_le32(0x80), /* Always log + modifications to this attribute, + regardless of whether it is resident or + non-resident. Without this, only log + modifications if the attribute is + resident. */ +} ATTR_DEF_FLAGS; + +/** + * struct ATTR_DEF - + * + * The data attribute of FILE_AttrDef contains a sequence of attribute + * definitions for the NTFS volume. With this, it is supposed to be safe for an + * older NTFS driver to mount a volume containing a newer NTFS version without + * damaging it (that's the theory. In practice it's: not damaging it too much). + * Entries are sorted by attribute type. The flags describe whether the + * attribute can be resident/non-resident and possibly other things, but the + * actual bits are unknown. + */ +typedef struct { +/*hex ofs*/ +/* 0*/ ntfschar name[0x40]; /* Unicode name of the attribute. Zero + terminated. */ +/* 80*/ ATTR_TYPES type; /* Type of the attribute. */ +/* 84*/ u32 display_rule; /* Default display rule. + FIXME: What does it mean? (AIA) */ +/* 88*/ COLLATION_RULES collation_rule; /* Default collation rule. */ +/* 8c*/ ATTR_DEF_FLAGS flags; /* Flags describing the attribute. */ +/* 90*/ s64 min_size; /* Optional minimum attribute size. */ +/* 98*/ s64 max_size; /* Maximum size of attribute. */ +/* sizeof() = 0xa0 or 160 bytes */ +} __attribute__((__packed__)) ATTR_DEF; + +/** + * enum ATTR_FLAGS - Attribute flags (16-bit). + */ +typedef enum { + ATTR_IS_COMPRESSED = const_cpu_to_le16(0x0001), + ATTR_COMPRESSION_MASK = const_cpu_to_le16(0x00ff), /* Compression + method mask. Also, first + illegal value. */ + ATTR_IS_ENCRYPTED = const_cpu_to_le16(0x4000), + ATTR_IS_SPARSE = const_cpu_to_le16(0x8000), +} __attribute__((__packed__)) ATTR_FLAGS; + +/* + * Attribute compression. + * + * Only the data attribute is ever compressed in the current ntfs driver in + * Windows. Further, compression is only applied when the data attribute is + * non-resident. Finally, to use compression, the maximum allowed cluster size + * on a volume is 4kib. + * + * The compression method is based on independently compressing blocks of X + * clusters, where X is determined from the compression_unit value found in the + * non-resident attribute record header (more precisely: X = 2^compression_unit + * clusters). On Windows NT/2k, X always is 16 clusters (compression_unit = 4). + * + * There are three different cases of how a compression block of X clusters + * can be stored: + * + * 1) The data in the block is all zero (a sparse block): + * This is stored as a sparse block in the runlist, i.e. the runlist + * entry has length = X and lcn = -1. The mapping pairs array actually + * uses a delta_lcn value length of 0, i.e. delta_lcn is not present at + * all, which is then interpreted by the driver as lcn = -1. + * NOTE: Even uncompressed files can be sparse on NTFS 3.0 volumes, then + * the same principles apply as above, except that the length is not + * restricted to being any particular value. + * + * 2) The data in the block is not compressed: + * This happens when compression doesn't reduce the size of the block + * in clusters. I.e. if compression has a small effect so that the + * compressed data still occupies X clusters, then the uncompressed data + * is stored in the block. + * This case is recognised by the fact that the runlist entry has + * length = X and lcn >= 0. The mapping pairs array stores this as + * normal with a run length of X and some specific delta_lcn, i.e. + * delta_lcn has to be present. + * + * 3) The data in the block is compressed: + * The common case. This case is recognised by the fact that the run + * list entry has length L < X and lcn >= 0. The mapping pairs array + * stores this as normal with a run length of X and some specific + * delta_lcn, i.e. delta_lcn has to be present. This runlist entry is + * immediately followed by a sparse entry with length = X - L and + * lcn = -1. The latter entry is to make up the vcn counting to the + * full compression block size X. + * + * In fact, life is more complicated because adjacent entries of the same type + * can be coalesced. This means that one has to keep track of the number of + * clusters handled and work on a basis of X clusters at a time being one + * block. An example: if length L > X this means that this particular runlist + * entry contains a block of length X and part of one or more blocks of length + * L - X. Another example: if length L < X, this does not necessarily mean that + * the block is compressed as it might be that the lcn changes inside the block + * and hence the following runlist entry describes the continuation of the + * potentially compressed block. The block would be compressed if the + * following runlist entry describes at least X - L sparse clusters, thus + * making up the compression block length as described in point 3 above. (Of + * course, there can be several runlist entries with small lengths so that the + * sparse entry does not follow the first data containing entry with + * length < X.) + * + * NOTE: At the end of the compressed attribute value, there most likely is not + * just the right amount of data to make up a compression block, thus this data + * is not even attempted to be compressed. It is just stored as is, unless + * the number of clusters it occupies is reduced when compressed in which case + * it is stored as a compressed compression block, complete with sparse + * clusters at the end. + */ + +/** + * enum RESIDENT_ATTR_FLAGS - Flags of resident attributes (8-bit). + */ +typedef enum { + RESIDENT_ATTR_IS_INDEXED = 0x01, /* Attribute is referenced in an index + (has implications for deleting and + modifying the attribute). */ +} __attribute__((__packed__)) RESIDENT_ATTR_FLAGS; + +/** + * struct ATTR_RECORD - Attribute record header. + * + * Always aligned to 8-byte boundary. + */ +typedef struct { +/*Ofs*/ +/* 0*/ ATTR_TYPES type; /* The (32-bit) type of the attribute. */ +/* 4*/ u32 length; /* Byte size of the resident part of the + attribute (aligned to 8-byte boundary). + Used to get to the next attribute. */ +/* 8*/ u8 non_resident; /* If 0, attribute is resident. + If 1, attribute is non-resident. */ +/* 9*/ u8 name_length; /* Unicode character size of name of attribute. + 0 if unnamed. */ +/* 10*/ u16 name_offset; /* If name_length != 0, the byte offset to the + beginning of the name from the attribute + record. Note that the name is stored as a + Unicode string. When creating, place offset + just at the end of the record header. Then, + follow with attribute value or mapping pairs + array, resident and non-resident attributes + respectively, aligning to an 8-byte + boundary. */ +/* 12*/ ATTR_FLAGS flags; /* Flags describing the attribute. */ +/* 14*/ u16 instance; /* The instance of this attribute record. This + number is unique within this mft record (see + MFT_RECORD/next_attribute_instance notes + above for more details). */ +/* 16*/ union { + /* Resident attributes. */ + struct { +/* 16 */ u32 value_length; /* Byte size of attribute value. */ +/* 20 */ u16 value_offset; /* Byte offset of the attribute + value from the start of the + attribute record. When creating, + align to 8-byte boundary if we + have a name present as this might + not have a length of a multiple + of 8-bytes. */ +/* 22 */ RESIDENT_ATTR_FLAGS resident_flags; /* See above. */ +/* 23 */ s8 reservedR; /* Reserved/alignment to 8-byte + boundary. */ +/* 24 */ void *resident_end[0]; /* Use offsetof(ATTR_RECORD, + resident_end) to get size of + a resident attribute. */ + } __attribute__((__packed__)); + /* Non-resident attributes. */ + struct { +/* 16*/ VCN lowest_vcn; /* Lowest valid virtual cluster number + for this portion of the attribute value or + 0 if this is the only extent (usually the + case). - Only when an attribute list is used + does lowest_vcn != 0 ever occur. */ +/* 24*/ VCN highest_vcn; /* Highest valid vcn of this extent of + the attribute value. - Usually there is only one + portion, so this usually equals the attribute + value size in clusters minus 1. Can be -1 for + zero length files. Can be 0 for "single extent" + attributes. */ +/* 32*/ u16 mapping_pairs_offset; /* Byte offset from the + beginning of the structure to the mapping pairs + array which contains the mappings between the + vcns and the logical cluster numbers (lcns). + When creating, place this at the end of this + record header aligned to 8-byte boundary. */ +/* 34*/ u8 compression_unit; /* The compression unit expressed + as the log to the base 2 of the number of + clusters in a compression unit. 0 means not + compressed. (This effectively limits the + compression unit size to be a power of two + clusters.) WinNT4 only uses a value of 4. */ +/* 35*/ u8 reserved1[5]; /* Align to 8-byte boundary. */ +/* The sizes below are only used when lowest_vcn is zero, as otherwise it would + be difficult to keep them up-to-date.*/ +/* 40*/ s64 allocated_size; /* Byte size of disk space + allocated to hold the attribute value. Always + is a multiple of the cluster size. When a file + is compressed, this field is a multiple of the + compression block size (2^compression_unit) and + it represents the logically allocated space + rather than the actual on disk usage. For this + use the compressed_size (see below). */ +/* 48*/ s64 data_size; /* Byte size of the attribute + value. Can be larger than allocated_size if + attribute value is compressed or sparse. */ +/* 56*/ s64 initialized_size; /* Byte size of initialized + portion of the attribute value. Usually equals + data_size. */ +/* 64 */ void *non_resident_end[0]; /* Use offsetof(ATTR_RECORD, + non_resident_end) to get + size of a non resident + attribute. */ +/* sizeof(uncompressed attr) = 64*/ +/* 64*/ s64 compressed_size; /* Byte size of the attribute + value after compression. Only present when + compressed. Always is a multiple of the + cluster size. Represents the actual amount of + disk space being used on the disk. */ +/* 72 */ void *compressed_end[0]; + /* Use offsetof(ATTR_RECORD, compressed_end) to + get size of a compressed attribute. */ +/* sizeof(compressed attr) = 72*/ + } __attribute__((__packed__)); + } __attribute__((__packed__)); +} __attribute__((__packed__)) ATTR_RECORD; + +typedef ATTR_RECORD ATTR_REC; + +/** + * enum FILE_ATTR_FLAGS - File attribute flags (32-bit). + */ +typedef enum { + /* + * These flags are only present in the STANDARD_INFORMATION attribute + * (in the field file_attributes). + */ + FILE_ATTR_READONLY = const_cpu_to_le32(0x00000001), + FILE_ATTR_HIDDEN = const_cpu_to_le32(0x00000002), + FILE_ATTR_SYSTEM = const_cpu_to_le32(0x00000004), + /* Old DOS volid. Unused in NT. = cpu_to_le32(0x00000008), */ + + FILE_ATTR_DIRECTORY = const_cpu_to_le32(0x00000010), + /* FILE_ATTR_DIRECTORY is not considered valid in NT. It is reserved + for the DOS SUBDIRECTORY flag. */ + FILE_ATTR_ARCHIVE = const_cpu_to_le32(0x00000020), + FILE_ATTR_DEVICE = const_cpu_to_le32(0x00000040), + FILE_ATTR_NORMAL = const_cpu_to_le32(0x00000080), + + FILE_ATTR_TEMPORARY = const_cpu_to_le32(0x00000100), + FILE_ATTR_SPARSE_FILE = const_cpu_to_le32(0x00000200), + FILE_ATTR_REPARSE_POINT = const_cpu_to_le32(0x00000400), + FILE_ATTR_COMPRESSED = const_cpu_to_le32(0x00000800), + + FILE_ATTR_OFFLINE = const_cpu_to_le32(0x00001000), + FILE_ATTR_NOT_CONTENT_INDEXED = const_cpu_to_le32(0x00002000), + FILE_ATTR_ENCRYPTED = const_cpu_to_le32(0x00004000), + + FILE_ATTR_VALID_FLAGS = const_cpu_to_le32(0x00007fb7), + /* FILE_ATTR_VALID_FLAGS masks out the old DOS VolId and the + FILE_ATTR_DEVICE and preserves everything else. This mask + is used to obtain all flags that are valid for reading. */ + FILE_ATTR_VALID_SET_FLAGS = const_cpu_to_le32(0x000031a7), + /* FILE_ATTR_VALID_SET_FLAGS masks out the old DOS VolId, the + FILE_ATTR_DEVICE, FILE_ATTR_DIRECTORY, FILE_ATTR_SPARSE_FILE, + FILE_ATTR_REPARSE_POINT, FILE_ATRE_COMPRESSED and FILE_ATTR_ENCRYPTED + and preserves the rest. This mask is used to to obtain all flags that + are valid for setting. */ + + /** + * FILE_ATTR_I30_INDEX_PRESENT - Is it a directory? + * + * This is a copy of the MFT_RECORD_IS_DIRECTORY bit from the mft + * record, telling us whether this is a directory or not, i.e. whether + * it has an index root attribute named "$I30" or not. + * + * This flag is only present in the FILE_NAME attribute (in the + * file_attributes field). + */ + FILE_ATTR_I30_INDEX_PRESENT = const_cpu_to_le32(0x10000000), + + /** + * FILE_ATTR_VIEW_INDEX_PRESENT - Does have a non-directory index? + * + * This is a copy of the MFT_RECORD_IS_VIEW_INDEX bit from the mft + * record, telling us whether this file has a view index present (eg. + * object id index, quota index, one of the security indexes and the + * reparse points index). + * + * This flag is only present in the $STANDARD_INFORMATION and + * $FILE_NAME attributes. + */ + FILE_ATTR_VIEW_INDEX_PRESENT = const_cpu_to_le32(0x20000000), +} __attribute__((__packed__)) FILE_ATTR_FLAGS; + +/* + * NOTE on times in NTFS: All times are in MS standard time format, i.e. they + * are the number of 100-nanosecond intervals since 1st January 1601, 00:00:00 + * universal coordinated time (UTC). (In Linux time starts 1st January 1970, + * 00:00:00 UTC and is stored as the number of 1-second intervals since then.) + */ + +/** + * struct STANDARD_INFORMATION - Attribute: Standard information (0x10). + * + * NOTE: Always resident. + * NOTE: Present in all base file records on a volume. + * NOTE: There is conflicting information about the meaning of each of the time + * fields but the meaning as defined below has been verified to be + * correct by practical experimentation on Windows NT4 SP6a and is hence + * assumed to be the one and only correct interpretation. + */ +typedef struct { +/*Ofs*/ +/* 0*/ s64 creation_time; /* Time file was created. Updated when + a filename is changed(?). */ +/* 8*/ s64 last_data_change_time; /* Time the data attribute was last + modified. */ +/* 16*/ s64 last_mft_change_time; /* Time this mft record was last + modified. */ +/* 24*/ s64 last_access_time; /* Approximate time when the file was + last accessed (obviously this is not + updated on read-only volumes). In + Windows this is only updated when + accessed if some time delta has + passed since the last update. Also, + last access times updates can be + disabled altogether for speed. */ +/* 32*/ FILE_ATTR_FLAGS file_attributes; /* Flags describing the file. */ +/* 36*/ union { + /* NTFS 1.2 (and previous, presumably) */ + struct { + /* 36 */ u8 reserved12[12]; /* Reserved/alignment to 8-byte + boundary. */ + /* 48 */ void *v1_end[0]; /* Marker for offsetof(). */ + } __attribute__((__packed__)); +/* sizeof() = 48 bytes */ + /* NTFS 3.0 */ + struct { +/* + * If a volume has been upgraded from a previous NTFS version, then these + * fields are present only if the file has been accessed since the upgrade. + * Recognize the difference by comparing the length of the resident attribute + * value. If it is 48, then the following fields are missing. If it is 72 then + * the fields are present. Maybe just check like this: + * if (resident.ValueLength < sizeof(STANDARD_INFORMATION)) { + * Assume NTFS 1.2- format. + * If (volume version is 3.0+) + * Upgrade attribute to NTFS 3.0 format. + * else + * Use NTFS 1.2- format for access. + * } else + * Use NTFS 3.0 format for access. + * Only problem is that it might be legal to set the length of the value to + * arbitrarily large values thus spoiling this check. - But chkdsk probably + * views that as a corruption, assuming that it behaves like this for all + * attributes. + */ + /* 36*/ u32 maximum_versions; /* Maximum allowed versions for + file. Zero if version numbering is disabled. */ + /* 40*/ u32 version_number; /* This file's version (if any). + Set to zero if maximum_versions is zero. */ + /* 44*/ u32 class_id; /* Class id from bidirectional + class id index (?). */ + /* 48*/ u32 owner_id; /* Owner_id of the user owning + the file. Translate via $Q index in FILE_Extend + /$Quota to the quota control entry for the user + owning the file. Zero if quotas are disabled. */ + /* 52*/ u32 security_id; /* Security_id for the file. + Translate via $SII index and $SDS data stream + in FILE_Secure to the security descriptor. */ + /* 56*/ u64 quota_charged; /* Byte size of the charge to + the quota for all streams of the file. Note: Is + zero if quotas are disabled. */ + /* 64*/ u64 usn; /* Last update sequence number + of the file. This is a direct index into the + change (aka usn) journal file. It is zero if + the usn journal is disabled. + NOTE: To disable the journal need to delete + the journal file itself and to then walk the + whole mft and set all Usn entries in all mft + records to zero! (This can take a while!) + The journal is FILE_Extend/$UsnJrnl. Win2k + will recreate the journal and initiate + logging if necessary when mounting the + partition. This, in contrast to disabling the + journal is a very fast process, so the user + won't even notice it. */ + /* 72*/ void *v3_end[0]; /* Marker for offsetof(). */ + } __attribute__((__packed__)); + } __attribute__((__packed__)); +/* sizeof() = 72 bytes (NTFS 3.0) */ +} __attribute__((__packed__)) STANDARD_INFORMATION; + +/** + * struct ATTR_LIST_ENTRY - Attribute: Attribute list (0x20). + * + * - Can be either resident or non-resident. + * - Value consists of a sequence of variable length, 8-byte aligned, + * ATTR_LIST_ENTRY records. + * - The attribute list attribute contains one entry for each attribute of + * the file in which the list is located, except for the list attribute + * itself. The list is sorted: first by attribute type, second by attribute + * name (if present), third by instance number. The extents of one + * non-resident attribute (if present) immediately follow after the initial + * extent. They are ordered by lowest_vcn and have their instance set to zero. + * It is not allowed to have two attributes with all sorting keys equal. + * - Further restrictions: + * - If not resident, the vcn to lcn mapping array has to fit inside the + * base mft record. + * - The attribute list attribute value has a maximum size of 256kb. This + * is imposed by the Windows cache manager. + * - Attribute lists are only used when the attributes of mft record do not + * fit inside the mft record despite all attributes (that can be made + * non-resident) having been made non-resident. This can happen e.g. when: + * - File has a large number of hard links (lots of file name + * attributes present). + * - The mapping pairs array of some non-resident attribute becomes so + * large due to fragmentation that it overflows the mft record. + * - The security descriptor is very complex (not applicable to + * NTFS 3.0 volumes). + * - There are many named streams. + */ +typedef struct { +/*Ofs*/ +/* 0*/ ATTR_TYPES type; /* Type of referenced attribute. */ +/* 4*/ u16 length; /* Byte size of this entry. */ +/* 6*/ u8 name_length; /* Size in Unicode chars of the name of the + attribute or 0 if unnamed. */ +/* 7*/ u8 name_offset; /* Byte offset to beginning of attribute name + (always set this to where the name would + start even if unnamed). */ +/* 8*/ VCN lowest_vcn; /* Lowest virtual cluster number of this portion + of the attribute value. This is usually 0. It + is non-zero for the case where one attribute + does not fit into one mft record and thus + several mft records are allocated to hold + this attribute. In the latter case, each mft + record holds one extent of the attribute and + there is one attribute list entry for each + extent. NOTE: This is DEFINITELY a signed + value! The windows driver uses cmp, followed + by jg when comparing this, thus it treats it + as signed. */ +/* 16*/ MFT_REF mft_reference; /* The reference of the mft record holding + the ATTR_RECORD for this portion of the + attribute value. */ +/* 24*/ u16 instance; /* If lowest_vcn = 0, the instance of the + attribute being referenced; otherwise 0. */ +/* 26*/ ntfschar name[0]; /* Use when creating only. When reading use + name_offset to determine the location of the + name. */ +/* sizeof() = 26 + (attribute_name_length * 2) bytes */ +} __attribute__((__packed__)) ATTR_LIST_ENTRY; + +/* + * The maximum allowed length for a file name. + */ +#define NTFS_MAX_NAME_LEN 255 + +/** + * enum FILE_NAME_TYPE_FLAGS - Possible namespaces for filenames in ntfs. + * (8-bit). + */ +typedef enum { + FILE_NAME_POSIX = 0x00, + /* This is the largest namespace. It is case sensitive and + allows all Unicode characters except for: '\0' and '/'. + Beware that in WinNT/2k files which eg have the same name + except for their case will not be distinguished by the + standard utilities and thus a "del filename" will delete + both "filename" and "fileName" without warning. */ + FILE_NAME_WIN32 = 0x01, + /* The standard WinNT/2k NTFS long filenames. Case insensitive. + All Unicode chars except: '\0', '"', '*', '/', ':', '<', + '>', '?', '\' and '|'. Further, names cannot end with a '.' + or a space. */ + FILE_NAME_DOS = 0x02, + /* The standard DOS filenames (8.3 format). Uppercase only. + All 8-bit characters greater space, except: '"', '*', '+', + ',', '/', ':', ';', '<', '=', '>', '?' and '\'. */ + FILE_NAME_WIN32_AND_DOS = 0x03, + /* 3 means that both the Win32 and the DOS filenames are + identical and hence have been saved in this single filename + record. */ +} __attribute__((__packed__)) FILE_NAME_TYPE_FLAGS; + +/** + * struct FILE_NAME_ATTR - Attribute: Filename (0x30). + * + * NOTE: Always resident. + * NOTE: All fields, except the parent_directory, are only updated when the + * filename is changed. Until then, they just become out of sync with + * reality and the more up to date values are present in the standard + * information attribute. + * NOTE: There is conflicting information about the meaning of each of the time + * fields but the meaning as defined below has been verified to be + * correct by practical experimentation on Windows NT4 SP6a and is hence + * assumed to be the one and only correct interpretation. + */ +typedef struct { +/*hex ofs*/ +/* 0*/ MFT_REF parent_directory; /* Directory this filename is + referenced from. */ +/* 8*/ s64 creation_time; /* Time file was created. */ +/* 10*/ s64 last_data_change_time; /* Time the data attribute was last + modified. */ +/* 18*/ s64 last_mft_change_time; /* Time this mft record was last + modified. */ +/* 20*/ s64 last_access_time; /* Last time this mft record was + accessed. */ +/* 28*/ s64 allocated_size; /* Byte size of on-disk allocated space + for the data attribute. So for + normal $DATA, this is the + allocated_size from the unnamed + $DATA attribute and for compressed + and/or sparse $DATA, this is the + compressed_size from the unnamed + $DATA attribute. NOTE: This is a + multiple of the cluster size. */ +/* 30*/ s64 data_size; /* Byte size of actual data in data + attribute. */ +/* 38*/ FILE_ATTR_FLAGS file_attributes; /* Flags describing the file. */ +/* 3c*/ union { + /* 3c*/ struct { + /* 3c*/ u16 packed_ea_size; /* Size of the buffer needed to + pack the extended attributes + (EAs), if such are present.*/ + /* 3e*/ u16 reserved; /* Reserved for alignment. */ + } __attribute__((__packed__)); + /* 3c*/ u32 reparse_point_tag; /* Type of reparse point, + present only in reparse + points and only if there are + no EAs. */ + } __attribute__((__packed__)); +/* 40*/ u8 file_name_length; /* Length of file name in + (Unicode) characters. */ +/* 41*/ FILE_NAME_TYPE_FLAGS file_name_type; /* Namespace of the file name.*/ +/* 42*/ ntfschar file_name[0]; /* File name in Unicode. */ +} __attribute__((__packed__)) FILE_NAME_ATTR; + +/** + * struct GUID - GUID structures store globally unique identifiers (GUID). + * + * A GUID is a 128-bit value consisting of one group of eight hexadecimal + * digits, followed by three groups of four hexadecimal digits each, followed + * by one group of twelve hexadecimal digits. GUIDs are Microsoft's + * implementation of the distributed computing environment (DCE) universally + * unique identifier (UUID). + * + * Example of a GUID: + * 1F010768-5A73-BC91-0010-A52216A7227B + */ +typedef struct { + u32 data1; /* The first eight hexadecimal digits of the GUID. */ + u16 data2; /* The first group of four hexadecimal digits. */ + u16 data3; /* The second group of four hexadecimal digits. */ + u8 data4[8]; /* The first two bytes are the third group of four + hexadecimal digits. The remaining six bytes are the + final 12 hexadecimal digits. */ +} __attribute__((__packed__)) GUID; + +/** + * struct OBJ_ID_INDEX_DATA - FILE_Extend/$ObjId contains an index named $O. + * + * This index contains all object_ids present on the volume as the index keys + * and the corresponding mft_record numbers as the index entry data parts. + * + * The data part (defined below) also contains three other object_ids: + * birth_volume_id - object_id of FILE_Volume on which the file was first + * created. Optional (i.e. can be zero). + * birth_object_id - object_id of file when it was first created. Usually + * equals the object_id. Optional (i.e. can be zero). + * domain_id - Reserved (always zero). + */ +typedef struct { + MFT_REF mft_reference; /* Mft record containing the object_id in + the index entry key. */ + union { + struct { + GUID birth_volume_id; + GUID birth_object_id; + GUID domain_id; + } __attribute__((__packed__)); + u8 extended_info[48]; + } __attribute__((__packed__)); +} __attribute__((__packed__)) OBJ_ID_INDEX_DATA; + +/** + * struct OBJECT_ID_ATTR - Attribute: Object id (NTFS 3.0+) (0x40). + * + * NOTE: Always resident. + */ +typedef struct { + GUID object_id; /* Unique id assigned to the + file.*/ + /* The following fields are optional. The attribute value size is 16 + bytes, i.e. sizeof(GUID), if these are not present at all. Note, + the entries can be present but one or more (or all) can be zero + meaning that that particular value(s) is(are) not defined. Note, + when the fields are missing here, it is well possible that they are + to be found within the $Extend/$ObjId system file indexed under the + above object_id. */ + union { + struct { + GUID birth_volume_id; /* Unique id of volume on which + the file was first created.*/ + GUID birth_object_id; /* Unique id of file when it was + first created. */ + GUID domain_id; /* Reserved, zero. */ + } __attribute__((__packed__)); + u8 extended_info[48]; + } __attribute__((__packed__)); +} __attribute__((__packed__)) OBJECT_ID_ATTR; + +#if 0 +/** + * enum IDENTIFIER_AUTHORITIES - + * + * The pre-defined IDENTIFIER_AUTHORITIES used as SID_IDENTIFIER_AUTHORITY in + * the SID structure (see below). + */ +typedef enum { /* SID string prefix. */ + SECURITY_NULL_SID_AUTHORITY = {0, 0, 0, 0, 0, 0}, /* S-1-0 */ + SECURITY_WORLD_SID_AUTHORITY = {0, 0, 0, 0, 0, 1}, /* S-1-1 */ + SECURITY_LOCAL_SID_AUTHORITY = {0, 0, 0, 0, 0, 2}, /* S-1-2 */ + SECURITY_CREATOR_SID_AUTHORITY = {0, 0, 0, 0, 0, 3}, /* S-1-3 */ + SECURITY_NON_UNIQUE_AUTHORITY = {0, 0, 0, 0, 0, 4}, /* S-1-4 */ + SECURITY_NT_SID_AUTHORITY = {0, 0, 0, 0, 0, 5}, /* S-1-5 */ +} IDENTIFIER_AUTHORITIES; +#endif + +/** + * enum RELATIVE_IDENTIFIERS - + * + * These relative identifiers (RIDs) are used with the above identifier + * authorities to make up universal well-known SIDs. + * + * Note: The relative identifier (RID) refers to the portion of a SID, which + * identifies a user or group in relation to the authority that issued the SID. + * For example, the universal well-known SID Creator Owner ID (S-1-3-0) is + * made up of the identifier authority SECURITY_CREATOR_SID_AUTHORITY (3) and + * the relative identifier SECURITY_CREATOR_OWNER_RID (0). + */ +typedef enum { /* Identifier authority. */ + SECURITY_NULL_RID = 0, /* S-1-0 */ + SECURITY_WORLD_RID = 0, /* S-1-1 */ + SECURITY_LOCAL_RID = 0, /* S-1-2 */ + + SECURITY_CREATOR_OWNER_RID = 0, /* S-1-3 */ + SECURITY_CREATOR_GROUP_RID = 1, /* S-1-3 */ + + SECURITY_CREATOR_OWNER_SERVER_RID = 2, /* S-1-3 */ + SECURITY_CREATOR_GROUP_SERVER_RID = 3, /* S-1-3 */ + + SECURITY_DIALUP_RID = 1, + SECURITY_NETWORK_RID = 2, + SECURITY_BATCH_RID = 3, + SECURITY_INTERACTIVE_RID = 4, + SECURITY_SERVICE_RID = 6, + SECURITY_ANONYMOUS_LOGON_RID = 7, + SECURITY_PROXY_RID = 8, + SECURITY_ENTERPRISE_CONTROLLERS_RID=9, + SECURITY_SERVER_LOGON_RID = 9, + SECURITY_PRINCIPAL_SELF_RID = 0xa, + SECURITY_AUTHENTICATED_USER_RID = 0xb, + SECURITY_RESTRICTED_CODE_RID = 0xc, + SECURITY_TERMINAL_SERVER_RID = 0xd, + + SECURITY_LOGON_IDS_RID = 5, + SECURITY_LOGON_IDS_RID_COUNT = 3, + + SECURITY_LOCAL_SYSTEM_RID = 0x12, + + SECURITY_NT_NON_UNIQUE = 0x15, + + SECURITY_BUILTIN_DOMAIN_RID = 0x20, + + /* + * Well-known domain relative sub-authority values (RIDs). + */ + + /* Users. */ + DOMAIN_USER_RID_ADMIN = 0x1f4, + DOMAIN_USER_RID_GUEST = 0x1f5, + DOMAIN_USER_RID_KRBTGT = 0x1f6, + + /* Groups. */ + DOMAIN_GROUP_RID_ADMINS = 0x200, + DOMAIN_GROUP_RID_USERS = 0x201, + DOMAIN_GROUP_RID_GUESTS = 0x202, + DOMAIN_GROUP_RID_COMPUTERS = 0x203, + DOMAIN_GROUP_RID_CONTROLLERS = 0x204, + DOMAIN_GROUP_RID_CERT_ADMINS = 0x205, + DOMAIN_GROUP_RID_SCHEMA_ADMINS = 0x206, + DOMAIN_GROUP_RID_ENTERPRISE_ADMINS= 0x207, + DOMAIN_GROUP_RID_POLICY_ADMINS = 0x208, + + /* Aliases. */ + DOMAIN_ALIAS_RID_ADMINS = 0x220, + DOMAIN_ALIAS_RID_USERS = 0x221, + DOMAIN_ALIAS_RID_GUESTS = 0x222, + DOMAIN_ALIAS_RID_POWER_USERS = 0x223, + + DOMAIN_ALIAS_RID_ACCOUNT_OPS = 0x224, + DOMAIN_ALIAS_RID_SYSTEM_OPS = 0x225, + DOMAIN_ALIAS_RID_PRINT_OPS = 0x226, + DOMAIN_ALIAS_RID_BACKUP_OPS = 0x227, + + DOMAIN_ALIAS_RID_REPLICATOR = 0x228, + DOMAIN_ALIAS_RID_RAS_SERVERS = 0x229, + DOMAIN_ALIAS_RID_PREW2KCOMPACCESS = 0x22a, +} RELATIVE_IDENTIFIERS; + +/* + * The universal well-known SIDs: + * + * NULL_SID S-1-0-0 + * WORLD_SID S-1-1-0 + * LOCAL_SID S-1-2-0 + * CREATOR_OWNER_SID S-1-3-0 + * CREATOR_GROUP_SID S-1-3-1 + * CREATOR_OWNER_SERVER_SID S-1-3-2 + * CREATOR_GROUP_SERVER_SID S-1-3-3 + * + * (Non-unique IDs) S-1-4 + * + * NT well-known SIDs: + * + * NT_AUTHORITY_SID S-1-5 + * DIALUP_SID S-1-5-1 + * + * NETWORD_SID S-1-5-2 + * BATCH_SID S-1-5-3 + * INTERACTIVE_SID S-1-5-4 + * SERVICE_SID S-1-5-6 + * ANONYMOUS_LOGON_SID S-1-5-7 (aka null logon session) + * PROXY_SID S-1-5-8 + * SERVER_LOGON_SID S-1-5-9 (aka domain controller account) + * SELF_SID S-1-5-10 (self RID) + * AUTHENTICATED_USER_SID S-1-5-11 + * RESTRICTED_CODE_SID S-1-5-12 (running restricted code) + * TERMINAL_SERVER_SID S-1-5-13 (running on terminal server) + * + * (Logon IDs) S-1-5-5-X-Y + * + * (NT non-unique IDs) S-1-5-0x15-... + * + * (Built-in domain) S-1-5-0x20 + */ + +/** + * union SID_IDENTIFIER_AUTHORITY - A 48-bit value used in the SID structure + * + * NOTE: This is stored as a big endian number. + */ +typedef union { + struct { + u16 high_part; /* High 16-bits. */ + u32 low_part; /* Low 32-bits. */ + } __attribute__((__packed__)); + u8 value[6]; /* Value as individual bytes. */ +} __attribute__((__packed__)) SID_IDENTIFIER_AUTHORITY; + +/** + * struct SID - + * + * The SID structure is a variable-length structure used to uniquely identify + * users or groups. SID stands for security identifier. + * + * The standard textual representation of the SID is of the form: + * S-R-I-S-S... + * Where: + * - The first "S" is the literal character 'S' identifying the following + * digits as a SID. + * - R is the revision level of the SID expressed as a sequence of digits + * in decimal. + * - I is the 48-bit identifier_authority, expressed as digits in decimal, + * if I < 2^32, or hexadecimal prefixed by "0x", if I >= 2^32. + * - S... is one or more sub_authority values, expressed as digits in + * decimal. + * + * Example SID; the domain-relative SID of the local Administrators group on + * Windows NT/2k: + * S-1-5-32-544 + * This translates to a SID with: + * revision = 1, + * sub_authority_count = 2, + * identifier_authority = {0,0,0,0,0,5}, // SECURITY_NT_AUTHORITY + * sub_authority[0] = 32, // SECURITY_BUILTIN_DOMAIN_RID + * sub_authority[1] = 544 // DOMAIN_ALIAS_RID_ADMINS + */ +typedef struct { + u8 revision; + u8 sub_authority_count; + SID_IDENTIFIER_AUTHORITY identifier_authority; + u32 sub_authority[1]; /* At least one sub_authority. */ +} __attribute__((__packed__)) SID; + +/** + * enum SID_CONSTANTS - Current constants for SIDs. + */ +typedef enum { + SID_REVISION = 1, /* Current revision level. */ + SID_MAX_SUB_AUTHORITIES = 15, /* Maximum number of those. */ + SID_RECOMMENDED_SUB_AUTHORITIES = 1, /* Will change to around 6 in + a future revision. */ +} SID_CONSTANTS; + +/** + * enum ACE_TYPES - The predefined ACE types (8-bit, see below). + */ +typedef enum { + ACCESS_MIN_MS_ACE_TYPE = 0, + ACCESS_ALLOWED_ACE_TYPE = 0, + ACCESS_DENIED_ACE_TYPE = 1, + SYSTEM_AUDIT_ACE_TYPE = 2, + SYSTEM_ALARM_ACE_TYPE = 3, /* Not implemented as of Win2k. */ + ACCESS_MAX_MS_V2_ACE_TYPE = 3, + + ACCESS_ALLOWED_COMPOUND_ACE_TYPE= 4, + ACCESS_MAX_MS_V3_ACE_TYPE = 4, + + /* The following are Win2k only. */ + ACCESS_MIN_MS_OBJECT_ACE_TYPE = 5, + ACCESS_ALLOWED_OBJECT_ACE_TYPE = 5, + ACCESS_DENIED_OBJECT_ACE_TYPE = 6, + SYSTEM_AUDIT_OBJECT_ACE_TYPE = 7, + SYSTEM_ALARM_OBJECT_ACE_TYPE = 8, + ACCESS_MAX_MS_OBJECT_ACE_TYPE = 8, + + ACCESS_MAX_MS_V4_ACE_TYPE = 8, + + /* This one is for WinNT&2k. */ + ACCESS_MAX_MS_ACE_TYPE = 8, +} __attribute__((__packed__)) ACE_TYPES; + +/** + * enum ACE_FLAGS - The ACE flags (8-bit) for audit and inheritance. + * + * SUCCESSFUL_ACCESS_ACE_FLAG is only used with system audit and alarm ACE + * types to indicate that a message is generated (in Windows!) for successful + * accesses. + * + * FAILED_ACCESS_ACE_FLAG is only used with system audit and alarm ACE types + * to indicate that a message is generated (in Windows!) for failed accesses. + */ +typedef enum { + /* The inheritance flags. */ + OBJECT_INHERIT_ACE = 0x01, + CONTAINER_INHERIT_ACE = 0x02, + NO_PROPAGATE_INHERIT_ACE = 0x04, + INHERIT_ONLY_ACE = 0x08, + INHERITED_ACE = 0x10, /* Win2k only. */ + VALID_INHERIT_FLAGS = 0x1f, + + /* The audit flags. */ + SUCCESSFUL_ACCESS_ACE_FLAG = 0x40, + FAILED_ACCESS_ACE_FLAG = 0x80, +} __attribute__((__packed__)) ACE_FLAGS; + +/** + * struct ACE_HEADER - + * + * An ACE is an access-control entry in an access-control list (ACL). + * An ACE defines access to an object for a specific user or group or defines + * the types of access that generate system-administration messages or alarms + * for a specific user or group. The user or group is identified by a security + * identifier (SID). + * + * Each ACE starts with an ACE_HEADER structure (aligned on 4-byte boundary), + * which specifies the type and size of the ACE. The format of the subsequent + * data depends on the ACE type. + */ +typedef struct { + ACE_TYPES type; /* Type of the ACE. */ + ACE_FLAGS flags; /* Flags describing the ACE. */ + u16 size; /* Size in bytes of the ACE. */ +} __attribute__((__packed__)) ACE_HEADER; + +/** + * enum ACCESS_MASK - The access mask (32-bit). + * + * Defines the access rights. + */ +typedef enum { + /* + * The specific rights (bits 0 to 15). Depend on the type of the + * object being secured by the ACE. + */ + + /* Specific rights for files and directories are as follows: */ + + /* Right to read data from the file. (FILE) */ + FILE_READ_DATA = const_cpu_to_le32(0x00000001), + /* Right to list contents of a directory. (DIRECTORY) */ + FILE_LIST_DIRECTORY = const_cpu_to_le32(0x00000001), + + /* Right to write data to the file. (FILE) */ + FILE_WRITE_DATA = const_cpu_to_le32(0x00000002), + /* Right to create a file in the directory. (DIRECTORY) */ + FILE_ADD_FILE = const_cpu_to_le32(0x00000002), + + /* Right to append data to the file. (FILE) */ + FILE_APPEND_DATA = const_cpu_to_le32(0x00000004), + /* Right to create a subdirectory. (DIRECTORY) */ + FILE_ADD_SUBDIRECTORY = const_cpu_to_le32(0x00000004), + + /* Right to read extended attributes. (FILE/DIRECTORY) */ + FILE_READ_EA = const_cpu_to_le32(0x00000008), + + /* Right to write extended attributes. (FILE/DIRECTORY) */ + FILE_WRITE_EA = const_cpu_to_le32(0x00000010), + + /* Right to execute a file. (FILE) */ + FILE_EXECUTE = const_cpu_to_le32(0x00000020), + /* Right to traverse the directory. (DIRECTORY) */ + FILE_TRAVERSE = const_cpu_to_le32(0x00000020), + + /* + * Right to delete a directory and all the files it contains (its + * children), even if the files are read-only. (DIRECTORY) + */ + FILE_DELETE_CHILD = const_cpu_to_le32(0x00000040), + + /* Right to read file attributes. (FILE/DIRECTORY) */ + FILE_READ_ATTRIBUTES = const_cpu_to_le32(0x00000080), + + /* Right to change file attributes. (FILE/DIRECTORY) */ + FILE_WRITE_ATTRIBUTES = const_cpu_to_le32(0x00000100), + + /* + * The standard rights (bits 16 to 23). Are independent of the type of + * object being secured. + */ + + /* Right to delete the object. */ + DELETE = const_cpu_to_le32(0x00010000), + + /* + * Right to read the information in the object's security descriptor, + * not including the information in the SACL. I.e. right to read the + * security descriptor and owner. + */ + READ_CONTROL = const_cpu_to_le32(0x00020000), + + /* Right to modify the DACL in the object's security descriptor. */ + WRITE_DAC = const_cpu_to_le32(0x00040000), + + /* Right to change the owner in the object's security descriptor. */ + WRITE_OWNER = const_cpu_to_le32(0x00080000), + + /* + * Right to use the object for synchronization. Enables a process to + * wait until the object is in the signalled state. Some object types + * do not support this access right. + */ + SYNCHRONIZE = const_cpu_to_le32(0x00100000), + + /* + * The following STANDARD_RIGHTS_* are combinations of the above for + * convenience and are defined by the Win32 API. + */ + + /* These are currently defined to READ_CONTROL. */ + STANDARD_RIGHTS_READ = const_cpu_to_le32(0x00020000), + STANDARD_RIGHTS_WRITE = const_cpu_to_le32(0x00020000), + STANDARD_RIGHTS_EXECUTE = const_cpu_to_le32(0x00020000), + + /* Combines DELETE, READ_CONTROL, WRITE_DAC, and WRITE_OWNER access. */ + STANDARD_RIGHTS_REQUIRED = const_cpu_to_le32(0x000f0000), + + /* + * Combines DELETE, READ_CONTROL, WRITE_DAC, WRITE_OWNER, and + * SYNCHRONIZE access. + */ + STANDARD_RIGHTS_ALL = const_cpu_to_le32(0x001f0000), + + /* + * The access system ACL and maximum allowed access types (bits 24 to + * 25, bits 26 to 27 are reserved). + */ + ACCESS_SYSTEM_SECURITY = const_cpu_to_le32(0x01000000), + MAXIMUM_ALLOWED = const_cpu_to_le32(0x02000000), + + /* + * The generic rights (bits 28 to 31). These map onto the standard and + * specific rights. + */ + + /* Read, write, and execute access. */ + GENERIC_ALL = const_cpu_to_le32(0x10000000), + + /* Execute access. */ + GENERIC_EXECUTE = const_cpu_to_le32(0x20000000), + + /* + * Write access. For files, this maps onto: + * FILE_APPEND_DATA | FILE_WRITE_ATTRIBUTES | FILE_WRITE_DATA | + * FILE_WRITE_EA | STANDARD_RIGHTS_WRITE | SYNCHRONIZE + * For directories, the mapping has the same numerical value. See + * above for the descriptions of the rights granted. + */ + GENERIC_WRITE = const_cpu_to_le32(0x40000000), + + /* + * Read access. For files, this maps onto: + * FILE_READ_ATTRIBUTES | FILE_READ_DATA | FILE_READ_EA | + * STANDARD_RIGHTS_READ | SYNCHRONIZE + * For directories, the mapping has the same numerical value. See + * above for the descriptions of the rights granted. + */ + GENERIC_READ = const_cpu_to_le32(0x80000000), +} ACCESS_MASK; + +/** + * struct GENERIC_MAPPING - + * + * The generic mapping array. Used to denote the mapping of each generic + * access right to a specific access mask. + * + * FIXME: What exactly is this and what is it for? (AIA) + */ +typedef struct { + ACCESS_MASK generic_read; + ACCESS_MASK generic_write; + ACCESS_MASK generic_execute; + ACCESS_MASK generic_all; +} __attribute__((__packed__)) GENERIC_MAPPING; + +/* + * The predefined ACE type structures are as defined below. + */ + +/** + * struct ACCESS_DENIED_ACE - + * + * ACCESS_ALLOWED_ACE, ACCESS_DENIED_ACE, SYSTEM_AUDIT_ACE, SYSTEM_ALARM_ACE + */ +typedef struct { +/* 0 ACE_HEADER; -- Unfolded here as gcc doesn't like unnamed structs. */ + ACE_TYPES type; /* Type of the ACE. */ + ACE_FLAGS flags; /* Flags describing the ACE. */ + u16 size; /* Size in bytes of the ACE. */ + +/* 4*/ ACCESS_MASK mask; /* Access mask associated with the ACE. */ +/* 8*/ SID sid; /* The SID associated with the ACE. */ +} __attribute__((__packed__)) ACCESS_ALLOWED_ACE, ACCESS_DENIED_ACE, + SYSTEM_AUDIT_ACE, SYSTEM_ALARM_ACE; + +/** + * enum OBJECT_ACE_FLAGS - The object ACE flags (32-bit). + */ +typedef enum { + ACE_OBJECT_TYPE_PRESENT = const_cpu_to_le32(1), + ACE_INHERITED_OBJECT_TYPE_PRESENT = const_cpu_to_le32(2), +} OBJECT_ACE_FLAGS; + +/** + * struct ACCESS_ALLOWED_OBJECT_ACE - + */ +typedef struct { +/* 0 ACE_HEADER; -- Unfolded here as gcc doesn't like unnamed structs. */ + ACE_TYPES type; /* Type of the ACE. */ + ACE_FLAGS flags; /* Flags describing the ACE. */ + u16 size; /* Size in bytes of the ACE. */ + +/* 4*/ ACCESS_MASK mask; /* Access mask associated with the ACE. */ +/* 8*/ OBJECT_ACE_FLAGS object_flags; /* Flags describing the object ACE. */ +/* 12*/ GUID object_type; +/* 28*/ GUID inherited_object_type; +/* 44*/ SID sid; /* The SID associated with the ACE. */ +} __attribute__((__packed__)) ACCESS_ALLOWED_OBJECT_ACE, + ACCESS_DENIED_OBJECT_ACE, + SYSTEM_AUDIT_OBJECT_ACE, + SYSTEM_ALARM_OBJECT_ACE; + +/** + * struct ACL - An ACL is an access-control list (ACL). + * + * An ACL starts with an ACL header structure, which specifies the size of + * the ACL and the number of ACEs it contains. The ACL header is followed by + * zero or more access control entries (ACEs). The ACL as well as each ACE + * are aligned on 4-byte boundaries. + */ +typedef struct { + u8 revision; /* Revision of this ACL. */ + u8 alignment1; + u16 size; /* Allocated space in bytes for ACL. Includes this + header, the ACEs and the remaining free space. */ + u16 ace_count; /* Number of ACEs in the ACL. */ + u16 alignment2; +/* sizeof() = 8 bytes */ +} __attribute__((__packed__)) ACL; + +/** + * enum ACL_CONSTANTS - Current constants for ACLs. + */ +typedef enum { + /* Current revision. */ + ACL_REVISION = 2, + ACL_REVISION_DS = 4, + + /* History of revisions. */ + ACL_REVISION1 = 1, + MIN_ACL_REVISION = 2, + ACL_REVISION2 = 2, + ACL_REVISION3 = 3, + ACL_REVISION4 = 4, + MAX_ACL_REVISION = 4, +} ACL_CONSTANTS; + +/** + * enum SECURITY_DESCRIPTOR_CONTROL - + * + * The security descriptor control flags (16-bit). + * + * SE_OWNER_DEFAULTED - This boolean flag, when set, indicates that the + * SID pointed to by the Owner field was provided by a + * defaulting mechanism rather than explicitly provided by the + * original provider of the security descriptor. This may + * affect the treatment of the SID with respect to inheritance + * of an owner. + * + * SE_GROUP_DEFAULTED - This boolean flag, when set, indicates that the + * SID in the Group field was provided by a defaulting mechanism + * rather than explicitly provided by the original provider of + * the security descriptor. This may affect the treatment of + * the SID with respect to inheritance of a primary group. + * + * SE_DACL_PRESENT - This boolean flag, when set, indicates that the + * security descriptor contains a discretionary ACL. If this + * flag is set and the Dacl field of the SECURITY_DESCRIPTOR is + * null, then a null ACL is explicitly being specified. + * + * SE_DACL_DEFAULTED - This boolean flag, when set, indicates that the + * ACL pointed to by the Dacl field was provided by a defaulting + * mechanism rather than explicitly provided by the original + * provider of the security descriptor. This may affect the + * treatment of the ACL with respect to inheritance of an ACL. + * This flag is ignored if the DaclPresent flag is not set. + * + * SE_SACL_PRESENT - This boolean flag, when set, indicates that the + * security descriptor contains a system ACL pointed to by the + * Sacl field. If this flag is set and the Sacl field of the + * SECURITY_DESCRIPTOR is null, then an empty (but present) + * ACL is being specified. + * + * SE_SACL_DEFAULTED - This boolean flag, when set, indicates that the + * ACL pointed to by the Sacl field was provided by a defaulting + * mechanism rather than explicitly provided by the original + * provider of the security descriptor. This may affect the + * treatment of the ACL with respect to inheritance of an ACL. + * This flag is ignored if the SaclPresent flag is not set. + * + * SE_SELF_RELATIVE - This boolean flag, when set, indicates that the + * security descriptor is in self-relative form. In this form, + * all fields of the security descriptor are contiguous in memory + * and all pointer fields are expressed as offsets from the + * beginning of the security descriptor. + */ +typedef enum { + SE_OWNER_DEFAULTED = const_cpu_to_le16(0x0001), + SE_GROUP_DEFAULTED = const_cpu_to_le16(0x0002), + SE_DACL_PRESENT = const_cpu_to_le16(0x0004), + SE_DACL_DEFAULTED = const_cpu_to_le16(0x0008), + SE_SACL_PRESENT = const_cpu_to_le16(0x0010), + SE_SACL_DEFAULTED = const_cpu_to_le16(0x0020), + SE_DACL_AUTO_INHERIT_REQ = const_cpu_to_le16(0x0100), + SE_SACL_AUTO_INHERIT_REQ = const_cpu_to_le16(0x0200), + SE_DACL_AUTO_INHERITED = const_cpu_to_le16(0x0400), + SE_SACL_AUTO_INHERITED = const_cpu_to_le16(0x0800), + SE_DACL_PROTECTED = const_cpu_to_le16(0x1000), + SE_SACL_PROTECTED = const_cpu_to_le16(0x2000), + SE_RM_CONTROL_VALID = const_cpu_to_le16(0x4000), + SE_SELF_RELATIVE = const_cpu_to_le16(0x8000), +} __attribute__((__packed__)) SECURITY_DESCRIPTOR_CONTROL; + +/** + * struct SECURITY_DESCRIPTOR_RELATIVE - + * + * Self-relative security descriptor. Contains the owner and group SIDs as well + * as the sacl and dacl ACLs inside the security descriptor itself. + */ +typedef struct { + u8 revision; /* Revision level of the security descriptor. */ + u8 alignment; + SECURITY_DESCRIPTOR_CONTROL control; /* Flags qualifying the type of + the descriptor as well as the following fields. */ + u32 owner; /* Byte offset to a SID representing an object's + owner. If this is NULL, no owner SID is present in + the descriptor. */ + u32 group; /* Byte offset to a SID representing an object's + primary group. If this is NULL, no primary group + SID is present in the descriptor. */ + u32 sacl; /* Byte offset to a system ACL. Only valid, if + SE_SACL_PRESENT is set in the control field. If + SE_SACL_PRESENT is set but sacl is NULL, a NULL ACL + is specified. */ + u32 dacl; /* Byte offset to a discretionary ACL. Only valid, if + SE_DACL_PRESENT is set in the control field. If + SE_DACL_PRESENT is set but dacl is NULL, a NULL ACL + (unconditionally granting access) is specified. */ +/* sizeof() = 0x14 bytes */ +} __attribute__((__packed__)) SECURITY_DESCRIPTOR_RELATIVE; + +/** + * struct SECURITY_DESCRIPTOR - Absolute security descriptor. + * + * Does not contain the owner and group SIDs, nor the sacl and dacl ACLs inside + * the security descriptor. Instead, it contains pointers to these structures + * in memory. Obviously, absolute security descriptors are only useful for in + * memory representations of security descriptors. + * + * On disk, a self-relative security descriptor is used. + */ +typedef struct { + u8 revision; /* Revision level of the security descriptor. */ + u8 alignment; + SECURITY_DESCRIPTOR_CONTROL control; /* Flags qualifying the type of + the descriptor as well as the following fields. */ + SID *owner; /* Points to a SID representing an object's owner. If + this is NULL, no owner SID is present in the + descriptor. */ + SID *group; /* Points to a SID representing an object's primary + group. If this is NULL, no primary group SID is + present in the descriptor. */ + ACL *sacl; /* Points to a system ACL. Only valid, if + SE_SACL_PRESENT is set in the control field. If + SE_SACL_PRESENT is set but sacl is NULL, a NULL ACL + is specified. */ + ACL *dacl; /* Points to a discretionary ACL. Only valid, if + SE_DACL_PRESENT is set in the control field. If + SE_DACL_PRESENT is set but dacl is NULL, a NULL ACL + (unconditionally granting access) is specified. */ +} __attribute__((__packed__)) SECURITY_DESCRIPTOR; + +/** + * enum SECURITY_DESCRIPTOR_CONSTANTS - + * + * Current constants for security descriptors. + */ +typedef enum { + /* Current revision. */ + SECURITY_DESCRIPTOR_REVISION = 1, + SECURITY_DESCRIPTOR_REVISION1 = 1, + + /* The sizes of both the absolute and relative security descriptors is + the same as pointers, at least on ia32 architecture are 32-bit. */ + SECURITY_DESCRIPTOR_MIN_LENGTH = sizeof(SECURITY_DESCRIPTOR), +} SECURITY_DESCRIPTOR_CONSTANTS; + +/* + * Attribute: Security descriptor (0x50). + * + * A standard self-relative security descriptor. + * + * NOTE: Can be resident or non-resident. + * NOTE: Not used in NTFS 3.0+, as security descriptors are stored centrally + * in FILE_Secure and the correct descriptor is found using the security_id + * from the standard information attribute. + */ +typedef SECURITY_DESCRIPTOR_RELATIVE SECURITY_DESCRIPTOR_ATTR; + +/* + * On NTFS 3.0+, all security descriptors are stored in FILE_Secure. Only one + * referenced instance of each unique security descriptor is stored. + * + * FILE_Secure contains no unnamed data attribute, i.e. it has zero length. It + * does, however, contain two indexes ($SDH and $SII) as well as a named data + * stream ($SDS). + * + * Every unique security descriptor is assigned a unique security identifier + * (security_id, not to be confused with a SID). The security_id is unique for + * the NTFS volume and is used as an index into the $SII index, which maps + * security_ids to the security descriptor's storage location within the $SDS + * data attribute. The $SII index is sorted by ascending security_id. + * + * A simple hash is computed from each security descriptor. This hash is used + * as an index into the $SDH index, which maps security descriptor hashes to + * the security descriptor's storage location within the $SDS data attribute. + * The $SDH index is sorted by security descriptor hash and is stored in a B+ + * tree. When searching $SDH (with the intent of determining whether or not a + * new security descriptor is already present in the $SDS data stream), if a + * matching hash is found, but the security descriptors do not match, the + * search in the $SDH index is continued, searching for a next matching hash. + * + * When a precise match is found, the security_id corresponding to the security + * descriptor in the $SDS attribute is read from the found $SDH index entry and + * is stored in the $STANDARD_INFORMATION attribute of the file/directory to + * which the security descriptor is being applied. The $STANDARD_INFORMATION + * attribute is present in all base mft records (i.e. in all files and + * directories). + * + * If a match is not found, the security descriptor is assigned a new unique + * security_id and is added to the $SDS data attribute. Then, entries + * referencing the this security descriptor in the $SDS data attribute are + * added to the $SDH and $SII indexes. + * + * Note: Entries are never deleted from FILE_Secure, even if nothing + * references an entry any more. + */ + +/** + * struct SECURITY_DESCRIPTOR_HEADER - + * + * This header precedes each security descriptor in the $SDS data stream. + * This is also the index entry data part of both the $SII and $SDH indexes. + */ +typedef struct { + u32 hash; /* Hash of the security descriptor. */ + u32 security_id; /* The security_id assigned to the descriptor. */ + u64 offset; /* Byte offset of this entry in the $SDS stream. */ + u32 length; /* Size in bytes of this entry in $SDS stream. */ +} __attribute__((__packed__)) SECURITY_DESCRIPTOR_HEADER; + +/** + * struct SDH_INDEX_DATA - + */ +typedef struct { + u32 hash; /* Hash of the security descriptor. */ + u32 security_id; /* The security_id assigned to the descriptor. */ + u64 offset; /* Byte offset of this entry in the $SDS stream. */ + u32 length; /* Size in bytes of this entry in $SDS stream. */ + u32 reserved_II; /* Padding - always unicode "II" or zero. This field + isn't counted in INDEX_ENTRY's data_length. */ +} __attribute__((__packed__)) SDH_INDEX_DATA; + +/** + * struct SII_INDEX_DATA - + */ +typedef SECURITY_DESCRIPTOR_HEADER SII_INDEX_DATA; + +/** + * struct SDS_ENTRY - + * + * The $SDS data stream contains the security descriptors, aligned on 16-byte + * boundaries, sorted by security_id in a B+ tree. Security descriptors cannot + * cross 256kib boundaries (this restriction is imposed by the Windows cache + * manager). Each security descriptor is contained in a SDS_ENTRY structure. + * Also, each security descriptor is stored twice in the $SDS stream with a + * fixed offset of 0x40000 bytes (256kib, the Windows cache manager's max size) + * between them; i.e. if a SDS_ENTRY specifies an offset of 0x51d0, then the + * the first copy of the security descriptor will be at offset 0x51d0 in the + * $SDS data stream and the second copy will be at offset 0x451d0. + */ +typedef struct { +/* 0 SECURITY_DESCRIPTOR_HEADER; -- Unfolded here as gcc doesn't like + unnamed structs. */ + u32 hash; /* Hash of the security descriptor. */ + u32 security_id; /* The security_id assigned to the descriptor. */ + u64 offset; /* Byte offset of this entry in the $SDS stream. */ + u32 length; /* Size in bytes of this entry in $SDS stream. */ +/* 20*/ SECURITY_DESCRIPTOR_RELATIVE sid; /* The self-relative security + descriptor. */ +} __attribute__((__packed__)) SDS_ENTRY; + +/** + * struct SII_INDEX_KEY - The index entry key used in the $SII index. + * + * The collation type is COLLATION_NTOFS_ULONG. + */ +typedef struct { + u32 security_id; /* The security_id assigned to the descriptor. */ +} __attribute__((__packed__)) SII_INDEX_KEY; + +/** + * struct SDH_INDEX_KEY - The index entry key used in the $SDH index. + * + * The keys are sorted first by hash and then by security_id. + * The collation rule is COLLATION_NTOFS_SECURITY_HASH. + */ +typedef struct { + u32 hash; /* Hash of the security descriptor. */ + u32 security_id; /* The security_id assigned to the descriptor. */ +} __attribute__((__packed__)) SDH_INDEX_KEY; + +/** + * struct VOLUME_NAME - Attribute: Volume name (0x60). + * + * NOTE: Always resident. + * NOTE: Present only in FILE_Volume. + */ +typedef struct { + ntfschar name[0]; /* The name of the volume in Unicode. */ +} __attribute__((__packed__)) VOLUME_NAME; + +/** + * enum VOLUME_FLAGS - Possible flags for the volume (16-bit). + */ +typedef enum { + VOLUME_IS_DIRTY = const_cpu_to_le16(0x0001), + VOLUME_RESIZE_LOG_FILE = const_cpu_to_le16(0x0002), + VOLUME_UPGRADE_ON_MOUNT = const_cpu_to_le16(0x0004), + VOLUME_MOUNTED_ON_NT4 = const_cpu_to_le16(0x0008), + VOLUME_DELETE_USN_UNDERWAY = const_cpu_to_le16(0x0010), + VOLUME_REPAIR_OBJECT_ID = const_cpu_to_le16(0x0020), + VOLUME_CHKDSK_UNDERWAY = const_cpu_to_le16(0x4000), + VOLUME_MODIFIED_BY_CHKDSK = const_cpu_to_le16(0x8000), + VOLUME_FLAGS_MASK = const_cpu_to_le16(0xc03f), +} __attribute__((__packed__)) VOLUME_FLAGS; + +/** + * struct VOLUME_INFORMATION - Attribute: Volume information (0x70). + * + * NOTE: Always resident. + * NOTE: Present only in FILE_Volume. + * NOTE: Windows 2000 uses NTFS 3.0 while Windows NT4 service pack 6a uses + * NTFS 1.2. I haven't personally seen other values yet. + */ +typedef struct { + u64 reserved; /* Not used (yet?). */ + u8 major_ver; /* Major version of the ntfs format. */ + u8 minor_ver; /* Minor version of the ntfs format. */ + VOLUME_FLAGS flags; /* Bit array of VOLUME_* flags. */ +} __attribute__((__packed__)) VOLUME_INFORMATION; + +/** + * struct DATA_ATTR - Attribute: Data attribute (0x80). + * + * NOTE: Can be resident or non-resident. + * + * Data contents of a file (i.e. the unnamed stream) or of a named stream. + */ +typedef struct { + u8 data[0]; /* The file's data contents. */ +} __attribute__((__packed__)) DATA_ATTR; + +/** + * enum INDEX_HEADER_FLAGS - Index header flags (8-bit). + */ +typedef enum { + /* When index header is in an index root attribute: */ + SMALL_INDEX = 0, /* The index is small enough to fit inside the + index root attribute and there is no index + allocation attribute present. */ + LARGE_INDEX = 1, /* The index is too large to fit in the index + root attribute and/or an index allocation + attribute is present. */ + /* + * When index header is in an index block, i.e. is part of index + * allocation attribute: + */ + LEAF_NODE = 0, /* This is a leaf node, i.e. there are no more + nodes branching off it. */ + INDEX_NODE = 1, /* This node indexes other nodes, i.e. is not a + leaf node. */ + NODE_MASK = 1, /* Mask for accessing the *_NODE bits. */ +} __attribute__((__packed__)) INDEX_HEADER_FLAGS; + +/** + * struct INDEX_HEADER - + * + * This is the header for indexes, describing the INDEX_ENTRY records, which + * follow the INDEX_HEADER. Together the index header and the index entries + * make up a complete index. + * + * IMPORTANT NOTE: The offset, length and size structure members are counted + * relative to the start of the index header structure and not relative to the + * start of the index root or index allocation structures themselves. + */ +typedef struct { +/* 0*/ u32 entries_offset; /* Byte offset from the INDEX_HEADER to first + INDEX_ENTRY, aligned to 8-byte boundary. */ +/* 4*/ u32 index_length; /* Data size in byte of the INDEX_ENTRY's, + including the INDEX_HEADER, aligned to 8. */ +/* 8*/ u32 allocated_size; /* Allocated byte size of this index (block), + multiple of 8 bytes. See more below. */ + /* + For the index root attribute, the above two numbers are always + equal, as the attribute is resident and it is resized as needed. + + For the index allocation attribute, the attribute is not resident + and the allocated_size is equal to the index_block_size specified + by the corresponding INDEX_ROOT attribute minus the INDEX_BLOCK + size not counting the INDEX_HEADER part (i.e. minus -24). + */ +/* 12*/ INDEX_HEADER_FLAGS ih_flags; /* Bit field of INDEX_HEADER_FLAGS. */ +/* 13*/ u8 reserved[3]; /* Reserved/align to 8-byte boundary.*/ +/* sizeof() == 16 */ +} __attribute__((__packed__)) INDEX_HEADER; + +/** + * struct INDEX_ROOT - Attribute: Index root (0x90). + * + * NOTE: Always resident. + * + * This is followed by a sequence of index entries (INDEX_ENTRY structures) + * as described by the index header. + * + * When a directory is small enough to fit inside the index root then this + * is the only attribute describing the directory. When the directory is too + * large to fit in the index root, on the other hand, two additional attributes + * are present: an index allocation attribute, containing sub-nodes of the B+ + * directory tree (see below), and a bitmap attribute, describing which virtual + * cluster numbers (vcns) in the index allocation attribute are in use by an + * index block. + * + * NOTE: The root directory (FILE_root) contains an entry for itself. Other + * directories do not contain entries for themselves, though. + */ +typedef struct { +/* 0*/ ATTR_TYPES type; /* Type of the indexed attribute. Is + $FILE_NAME for directories, zero + for view indexes. No other values + allowed. */ +/* 4*/ COLLATION_RULES collation_rule; /* Collation rule used to sort the + index entries. If type is $FILE_NAME, + this must be COLLATION_FILE_NAME. */ +/* 8*/ u32 index_block_size; /* Size of each index block in bytes (in + the index allocation attribute). */ +/* 12*/ s8 clusters_per_index_block; /* Cluster size of each index block (in + the index allocation attribute), when + an index block is >= than a cluster, + otherwise this will be the -log of + the size (like how the encoding of + the mft record size and the index + record size found in the boot sector + work). Has to be a power of 2. */ +/* 13*/ u8 reserved[3]; /* Reserved/align to 8-byte boundary. */ +/* 16*/ INDEX_HEADER index; /* Index header describing the + following index entries. */ +/* sizeof()= 32 bytes */ +} __attribute__((__packed__)) INDEX_ROOT; + +/** + * struct INDEX_BLOCK - Attribute: Index allocation (0xa0). + * + * NOTE: Always non-resident (doesn't make sense to be resident anyway!). + * + * This is an array of index blocks. Each index block starts with an + * INDEX_BLOCK structure containing an index header, followed by a sequence of + * index entries (INDEX_ENTRY structures), as described by the INDEX_HEADER. + */ +typedef struct { +/* 0 NTFS_RECORD; -- Unfolded here as gcc doesn't like unnamed structs. */ + NTFS_RECORD_TYPES magic;/* Magic is "INDX". */ + u16 usa_ofs; /* See NTFS_RECORD definition. */ + u16 usa_count; /* See NTFS_RECORD definition. */ + +/* 8*/ LSN lsn; /* $LogFile sequence number of the last + modification of this index block. */ +/* 16*/ VCN index_block_vcn; /* Virtual cluster number of the index block. */ +/* 24*/ INDEX_HEADER index; /* Describes the following index entries. */ +/* sizeof()= 40 (0x28) bytes */ +/* + * When creating the index block, we place the update sequence array at this + * offset, i.e. before we start with the index entries. This also makes sense, + * otherwise we could run into problems with the update sequence array + * containing in itself the last two bytes of a sector which would mean that + * multi sector transfer protection wouldn't work. As you can't protect data + * by overwriting it since you then can't get it back... + * When reading use the data from the ntfs record header. + */ +} __attribute__((__packed__)) INDEX_BLOCK; + +typedef INDEX_BLOCK INDEX_ALLOCATION; + +/** + * struct REPARSE_INDEX_KEY - + * + * The system file FILE_Extend/$Reparse contains an index named $R listing + * all reparse points on the volume. The index entry keys are as defined + * below. Note, that there is no index data associated with the index entries. + * + * The index entries are sorted by the index key file_id. The collation rule is + * COLLATION_NTOFS_ULONGS. FIXME: Verify whether the reparse_tag is not the + * primary key / is not a key at all. (AIA) + */ +typedef struct { + u32 reparse_tag; /* Reparse point type (inc. flags). */ + MFT_REF file_id; /* Mft record of the file containing the + reparse point attribute. */ +} __attribute__((__packed__)) REPARSE_INDEX_KEY; + +/** + * enum QUOTA_FLAGS - Quota flags (32-bit). + */ +typedef enum { + /* The user quota flags. Names explain meaning. */ + QUOTA_FLAG_DEFAULT_LIMITS = const_cpu_to_le32(0x00000001), + QUOTA_FLAG_LIMIT_REACHED = const_cpu_to_le32(0x00000002), + QUOTA_FLAG_ID_DELETED = const_cpu_to_le32(0x00000004), + + QUOTA_FLAG_USER_MASK = const_cpu_to_le32(0x00000007), + /* Bit mask for user quota flags. */ + + /* These flags are only present in the quota defaults index entry, + i.e. in the entry where owner_id = QUOTA_DEFAULTS_ID. */ + QUOTA_FLAG_TRACKING_ENABLED = const_cpu_to_le32(0x00000010), + QUOTA_FLAG_ENFORCEMENT_ENABLED = const_cpu_to_le32(0x00000020), + QUOTA_FLAG_TRACKING_REQUESTED = const_cpu_to_le32(0x00000040), + QUOTA_FLAG_LOG_THRESHOLD = const_cpu_to_le32(0x00000080), + QUOTA_FLAG_LOG_LIMIT = const_cpu_to_le32(0x00000100), + QUOTA_FLAG_OUT_OF_DATE = const_cpu_to_le32(0x00000200), + QUOTA_FLAG_CORRUPT = const_cpu_to_le32(0x00000400), + QUOTA_FLAG_PENDING_DELETES = const_cpu_to_le32(0x00000800), +} QUOTA_FLAGS; + +/** + * struct QUOTA_CONTROL_ENTRY - + * + * The system file FILE_Extend/$Quota contains two indexes $O and $Q. Quotas + * are on a per volume and per user basis. + * + * The $Q index contains one entry for each existing user_id on the volume. The + * index key is the user_id of the user/group owning this quota control entry, + * i.e. the key is the owner_id. The user_id of the owner of a file, i.e. the + * owner_id, is found in the standard information attribute. The collation rule + * for $Q is COLLATION_NTOFS_ULONG. + * + * The $O index contains one entry for each user/group who has been assigned + * a quota on that volume. The index key holds the SID of the user_id the + * entry belongs to, i.e. the owner_id. The collation rule for $O is + * COLLATION_NTOFS_SID. + * + * The $O index entry data is the user_id of the user corresponding to the SID. + * This user_id is used as an index into $Q to find the quota control entry + * associated with the SID. + * + * The $Q index entry data is the quota control entry and is defined below. + */ +typedef struct { + u32 version; /* Currently equals 2. */ + QUOTA_FLAGS flags; /* Flags describing this quota entry. */ + u64 bytes_used; /* How many bytes of the quota are in use. */ + s64 change_time; /* Last time this quota entry was changed. */ + s64 threshold; /* Soft quota (-1 if not limited). */ + s64 limit; /* Hard quota (-1 if not limited). */ + s64 exceeded_time; /* How long the soft quota has been exceeded. */ +/* The below field is NOT present for the quota defaults entry. */ + SID sid; /* The SID of the user/object associated with + this quota entry. If this field is missing + then the INDEX_ENTRY is padded with zeros + to multiply of 8 which are not counted in + the data_length field. If the sid is present + then this structure is padded with zeros to + multiply of 8 and the padding is counted in + the INDEX_ENTRY's data_length. */ +} __attribute__((__packed__)) QUOTA_CONTROL_ENTRY; + +/** + * struct QUOTA_O_INDEX_DATA - + */ +typedef struct { + u32 owner_id; + u32 unknown; /* Always 32. Seems to be padding and it's not + counted in the INDEX_ENTRY's data_length. + This field shouldn't be really here. */ +} __attribute__((__packed__)) QUOTA_O_INDEX_DATA; + +/** + * enum PREDEFINED_OWNER_IDS - Predefined owner_id values (32-bit). + */ +typedef enum { + QUOTA_INVALID_ID = const_cpu_to_le32(0x00000000), + QUOTA_DEFAULTS_ID = const_cpu_to_le32(0x00000001), + QUOTA_FIRST_USER_ID = const_cpu_to_le32(0x00000100), +} PREDEFINED_OWNER_IDS; + +/** + * enum INDEX_ENTRY_FLAGS - Index entry flags (16-bit). + */ +typedef enum { + INDEX_ENTRY_NODE = const_cpu_to_le16(1), /* This entry contains a + sub-node, i.e. a reference to an index + block in form of a virtual cluster + number (see below). */ + INDEX_ENTRY_END = const_cpu_to_le16(2), /* This signifies the last + entry in an index block. The index + entry does not represent a file but it + can point to a sub-node. */ + INDEX_ENTRY_SPACE_FILLER = 0xffff, /* Just to force 16-bit width. */ +} __attribute__((__packed__)) INDEX_ENTRY_FLAGS; + +/** + * struct INDEX_ENTRY_HEADER - This the index entry header (see below). + * + * ========================================================== + * !!!!! SEE DESCRIPTION OF THE FIELDS AT INDEX_ENTRY !!!!! + * ========================================================== + */ +typedef struct { +/* 0*/ union { + MFT_REF indexed_file; + struct { + u16 data_offset; + u16 data_length; + u32 reservedV; + } __attribute__((__packed__)); + } __attribute__((__packed__)); +/* 8*/ u16 length; +/* 10*/ u16 key_length; +/* 12*/ INDEX_ENTRY_FLAGS flags; +/* 14*/ u16 reserved; +/* sizeof() = 16 bytes */ +} __attribute__((__packed__)) INDEX_ENTRY_HEADER; + +/** + * struct INDEX_ENTRY - This is an index entry. + * + * A sequence of such entries follows each INDEX_HEADER structure. Together + * they make up a complete index. The index follows either an index root + * attribute or an index allocation attribute. + * + * NOTE: Before NTFS 3.0 only filename attributes were indexed. + */ +typedef struct { +/* 0 INDEX_ENTRY_HEADER; -- Unfolded here as gcc dislikes unnamed structs. */ + union { /* Only valid when INDEX_ENTRY_END is not set. */ + MFT_REF indexed_file; /* The mft reference of the file + described by this index + entry. Used for directory + indexes. */ + struct { /* Used for views/indexes to find the entry's data. */ + u16 data_offset; /* Data byte offset from this + INDEX_ENTRY. Follows the + index key. */ + u16 data_length; /* Data length in bytes. */ + u32 reservedV; /* Reserved (zero). */ + } __attribute__((__packed__)); + } __attribute__((__packed__)); +/* 8*/ u16 length; /* Byte size of this index entry, multiple of + 8-bytes. Size includes INDEX_ENTRY_HEADER + and the optional subnode VCN. See below. */ +/* 10*/ u16 key_length; /* Byte size of the key value, which is in the + index entry. It follows field reserved. Not + multiple of 8-bytes. */ +/* 12*/ INDEX_ENTRY_FLAGS ie_flags; /* Bit field of INDEX_ENTRY_* flags. */ +/* 14*/ u16 reserved; /* Reserved/align to 8-byte boundary. */ +/* End of INDEX_ENTRY_HEADER */ +/* 16*/ union { /* The key of the indexed attribute. NOTE: Only present + if INDEX_ENTRY_END bit in flags is not set. NOTE: On + NTFS versions before 3.0 the only valid key is the + FILE_NAME_ATTR. On NTFS 3.0+ the following + additional index keys are defined: */ + FILE_NAME_ATTR file_name;/* $I30 index in directories. */ + SII_INDEX_KEY sii; /* $SII index in $Secure. */ + SDH_INDEX_KEY sdh; /* $SDH index in $Secure. */ + GUID object_id; /* $O index in FILE_Extend/$ObjId: The + object_id of the mft record found in + the data part of the index. */ + REPARSE_INDEX_KEY reparse; /* $R index in + FILE_Extend/$Reparse. */ + SID sid; /* $O index in FILE_Extend/$Quota: + SID of the owner of the user_id. */ + u32 owner_id; /* $Q index in FILE_Extend/$Quota: + user_id of the owner of the quota + control entry in the data part of + the index. */ + } __attribute__((__packed__)) key; + /* The (optional) index data is inserted here when creating. */ + // VCN vcn; If INDEX_ENTRY_NODE bit in ie_flags is set, the last + // eight bytes of this index entry contain the virtual + // cluster number of the index block that holds the + // entries immediately preceding the current entry. + // + // If the key_length is zero, then the vcn immediately + // follows the INDEX_ENTRY_HEADER. + // + // The address of the vcn of "ie" INDEX_ENTRY is given by + // (char*)ie + le16_to_cpu(ie->length) - sizeof(VCN) +} __attribute__((__packed__)) INDEX_ENTRY; + +/** + * struct BITMAP_ATTR - Attribute: Bitmap (0xb0). + * + * Contains an array of bits (aka a bitfield). + * + * When used in conjunction with the index allocation attribute, each bit + * corresponds to one index block within the index allocation attribute. Thus + * the number of bits in the bitmap * index block size / cluster size is the + * number of clusters in the index allocation attribute. + */ +typedef struct { + u8 bitmap[0]; /* Array of bits. */ +} __attribute__((__packed__)) BITMAP_ATTR; + +/** + * enum PREDEFINED_REPARSE_TAGS - + * + * The reparse point tag defines the type of the reparse point. It also + * includes several flags, which further describe the reparse point. + * + * The reparse point tag is an unsigned 32-bit value divided in three parts: + * + * 1. The least significant 16 bits (i.e. bits 0 to 15) specify the type of + * the reparse point. + * 2. The 13 bits after this (i.e. bits 16 to 28) are reserved for future use. + * 3. The most significant three bits are flags describing the reparse point. + * They are defined as follows: + * bit 29: Name surrogate bit. If set, the filename is an alias for + * another object in the system. + * bit 30: High-latency bit. If set, accessing the first byte of data will + * be slow. (E.g. the data is stored on a tape drive.) + * bit 31: Microsoft bit. If set, the tag is owned by Microsoft. User + * defined tags have to use zero here. + */ +typedef enum { + IO_REPARSE_TAG_IS_ALIAS = const_cpu_to_le32(0x20000000), + IO_REPARSE_TAG_IS_HIGH_LATENCY = const_cpu_to_le32(0x40000000), + IO_REPARSE_TAG_IS_MICROSOFT = const_cpu_to_le32(0x80000000), + + IO_REPARSE_TAG_RESERVED_ZERO = const_cpu_to_le32(0x00000000), + IO_REPARSE_TAG_RESERVED_ONE = const_cpu_to_le32(0x00000001), + IO_REPARSE_TAG_RESERVED_RANGE = const_cpu_to_le32(0x00000001), + + IO_REPARSE_TAG_NSS = const_cpu_to_le32(0x68000005), + IO_REPARSE_TAG_NSS_RECOVER = const_cpu_to_le32(0x68000006), + IO_REPARSE_TAG_SIS = const_cpu_to_le32(0x68000007), + IO_REPARSE_TAG_DFS = const_cpu_to_le32(0x68000008), + + IO_REPARSE_TAG_MOUNT_POINT = const_cpu_to_le32(0x88000003), + + IO_REPARSE_TAG_HSM = const_cpu_to_le32(0xa8000004), + + IO_REPARSE_TAG_SYMBOLIC_LINK = const_cpu_to_le32(0xe8000000), + + IO_REPARSE_TAG_VALID_VALUES = const_cpu_to_le32(0xe000ffff), +} PREDEFINED_REPARSE_TAGS; + +/** + * struct REPARSE_POINT - Attribute: Reparse point (0xc0). + * + * NOTE: Can be resident or non-resident. + */ +typedef struct { + u32 reparse_tag; /* Reparse point type (inc. flags). */ + u16 reparse_data_length; /* Byte size of reparse data. */ + u16 reserved; /* Align to 8-byte boundary. */ + u8 reparse_data[0]; /* Meaning depends on reparse_tag. */ +} __attribute__((__packed__)) REPARSE_POINT; + +/** + * struct EA_INFORMATION - Attribute: Extended attribute information (0xd0). + * + * NOTE: Always resident. + */ +typedef struct { + u16 ea_length; /* Byte size of the packed extended + attributes. */ + u16 need_ea_count; /* The number of extended attributes which have + the NEED_EA bit set. */ + u32 ea_query_length; /* Byte size of the buffer required to query + the extended attributes when calling + ZwQueryEaFile() in Windows NT/2k. I.e. the + byte size of the unpacked extended + attributes. */ +} __attribute__((__packed__)) EA_INFORMATION; + +/** + * enum EA_FLAGS - Extended attribute flags (8-bit). + */ +typedef enum { + NEED_EA = 0x80, /* Indicate that the file to which the EA + belongs cannot be interpreted without + understanding the associated extended + attributes. */ +} __attribute__((__packed__)) EA_FLAGS; + +/** + * struct EA_ATTR - Attribute: Extended attribute (EA) (0xe0). + * + * Like the attribute list and the index buffer list, the EA attribute value is + * a sequence of EA_ATTR variable length records. + * + * FIXME: It appears weird that the EA name is not Unicode. Is it true? + * FIXME: It seems that name is always uppercased. Is it true? + */ +typedef struct { + u32 next_entry_offset; /* Offset to the next EA_ATTR. */ + EA_FLAGS flags; /* Flags describing the EA. */ + u8 name_length; /* Length of the name of the extended + attribute in bytes. */ + u16 value_length; /* Byte size of the EA's value. */ + u8 name[0]; /* Name of the EA. */ + u8 value[0]; /* The value of the EA. Immediately + follows the name. */ +} __attribute__((__packed__)) EA_ATTR; + +/** + * struct PROPERTY_SET - Attribute: Property set (0xf0). + * + * Intended to support Native Structure Storage (NSS) - a feature removed from + * NTFS 3.0 during beta testing. + */ +typedef struct { + /* Irrelevant as feature unused. */ +} __attribute__((__packed__)) PROPERTY_SET; + +/** + * struct LOGGED_UTILITY_STREAM - Attribute: Logged utility stream (0x100). + * + * NOTE: Can be resident or non-resident. + * + * Operations on this attribute are logged to the journal ($LogFile) like + * normal metadata changes. + * + * Used by the Encrypting File System (EFS). All encrypted files have this + * attribute with the name $EFS. See below for the relevant structures. + */ +typedef struct { + /* Can be anything the creator chooses. */ +} __attribute__((__packed__)) LOGGED_UTILITY_STREAM; + +/* + * $EFS Data Structure: + * + * The following information is about the data structures that are contained + * inside a logged utility stream (0x100) with a name of "$EFS". + * + * The stream starts with an instance of EFS_ATTR_HEADER. + * + * Next, at offsets offset_to_ddf_array and offset_to_drf_array (unless any of + * them is 0) there is a EFS_DF_ARRAY_HEADER immediately followed by a sequence + * of multiple data decryption/recovery fields. + * + * Each data decryption/recovery field starts with a EFS_DF_HEADER and the next + * one (if it exists) can be found by adding EFS_DF_HEADER->df_length bytes to + * the offset of the beginning of the current EFS_DF_HEADER. + * + * The data decryption/recovery field contains an EFS_DF_CERTIFICATE_HEADER, a + * SID, an optional GUID, an optional container name, a non-optional user name, + * and the encrypted FEK. + * + * Note all the below are best guesses so may have mistakes/inaccuracies. + * Corrections/clarifications/additions are always welcome! + * + * Ntfs.sys takes an EFS value length of <= 0x54 or > 0x40000 to BSOD, i.e. it + * is invalid. + */ + +/** + * struct EFS_ATTR_HEADER - "$EFS" header. + * + * The header of the Logged utility stream (0x100) attribute named "$EFS". + */ +typedef struct { +/* 0*/ u32 length; /* Length of EFS attribute in bytes. */ + u32 state; /* Always 0? */ + u32 version; /* Efs version. Always 2? */ + u32 crypto_api_version; /* Always 0? */ +/* 16*/ u8 unknown4[16]; /* MD5 hash of decrypted FEK? This field is + created with a call to UuidCreate() so is + unlikely to be an MD5 hash and is more + likely to be GUID of this encrytped file + or something like that. */ +/* 32*/ u8 unknown5[16]; /* MD5 hash of DDFs? */ +/* 48*/ u8 unknown6[16]; /* MD5 hash of DRFs? */ +/* 64*/ u32 offset_to_ddf_array;/* Offset in bytes to the array of data + decryption fields (DDF), see below. Zero if + no DDFs are present. */ + u32 offset_to_drf_array;/* Offset in bytes to the array of data + recovery fields (DRF), see below. Zero if + no DRFs are present. */ + u32 reserved; /* Reserved. */ +} __attribute__((__packed__)) EFS_ATTR_HEADER; + +/** + * struct EFS_DF_ARRAY_HEADER - + */ +typedef struct { + u32 df_count; /* Number of data decryption/recovery fields in + the array. */ +} __attribute__((__packed__)) EFS_DF_ARRAY_HEADER; + +/** + * struct EFS_DF_HEADER - + */ +typedef struct { +/* 0*/ u32 df_length; /* Length of this data decryption/recovery + field in bytes. */ + u32 cred_header_offset; /* Offset in bytes to the credential header. */ + u32 fek_size; /* Size in bytes of the encrypted file + encryption key (FEK). */ + u32 fek_offset; /* Offset in bytes to the FEK from the start of + the data decryption/recovery field. */ +/* 16*/ u32 unknown1; /* always 0? Might be just padding. */ +} __attribute__((__packed__)) EFS_DF_HEADER; + +/** + * struct EFS_DF_CREDENTIAL_HEADER - + */ +typedef struct { +/* 0*/ u32 cred_length; /* Length of this credential in bytes. */ + u32 sid_offset; /* Offset in bytes to the user's sid from start + of this structure. Zero if no sid is + present. */ +/* 8*/ u32 type; /* Type of this credential: + 1 = CryptoAPI container. + 2 = Unexpected type. + 3 = Certificate thumbprint. + other = Unknown type. */ + union { + /* CryptoAPI container. */ + struct { +/* 12*/ u32 container_name_offset; /* Offset in bytes to + the name of the container from start of this + structure (may not be zero). */ +/* 16*/ u32 provider_name_offset; /* Offset in bytes to + the name of the provider from start of this + structure (may not be zero). */ + u32 public_key_blob_offset; /* Offset in bytes to + the public key blob from start of this + structure. */ +/* 24*/ u32 public_key_blob_size; /* Size in bytes of + public key blob. */ + } __attribute__((__packed__)); + /* Certificate thumbprint. */ + struct { +/* 12*/ u32 cert_thumbprint_header_size; /* Size in + bytes of the header of the certificate + thumbprint. */ +/* 16*/ u32 cert_thumbprint_header_offset; /* Offset in + bytes to the header of the certificate + thumbprint from start of this structure. */ + u32 unknown1; /* Always 0? Might be padding... */ + u32 unknown2; /* Always 0? Might be padding... */ + } __attribute__((__packed__)); + } __attribute__((__packed__)); +} __attribute__((__packed__)) EFS_DF_CREDENTIAL_HEADER; + +typedef EFS_DF_CREDENTIAL_HEADER EFS_DF_CRED_HEADER; + +/** + * struct EFS_DF_CERTIFICATE_THUMBPRINT_HEADER - + */ +typedef struct { +/* 0*/ u32 thumbprint_offset; /* Offset in bytes to the thumbprint. */ + u32 thumbprint_size; /* Size of thumbprint in bytes. */ +/* 8*/ u32 container_name_offset; /* Offset in bytes to the name of the + container from start of this + structure or 0 if no name present. */ + u32 provider_name_offset; /* Offset in bytes to the name of the + cryptographic provider from start of + this structure or 0 if no name + present. */ +/* 16*/ u32 user_name_offset; /* Offset in bytes to the user name + from start of this structure or 0 if + no user name present. (This is also + known as lpDisplayInformation.) */ +} __attribute__((__packed__)) EFS_DF_CERTIFICATE_THUMBPRINT_HEADER; + +typedef EFS_DF_CERTIFICATE_THUMBPRINT_HEADER EFS_DF_CERT_THUMBPRINT_HEADER; + +typedef enum { + INTX_SYMBOLIC_LINK = + const_cpu_to_le64(0x014B4E4C78746E49ULL), /* "IntxLNK\1" */ + INTX_CHARACTER_DEVICE = + const_cpu_to_le64(0x0052484378746E49ULL), /* "IntxCHR\0" */ + INTX_BLOCK_DEVICE = + const_cpu_to_le64(0x004B4C4278746E49ULL), /* "IntxBLK\0" */ +} INTX_FILE_TYPES; + +typedef struct { + INTX_FILE_TYPES magic; /* Intx file magic. */ + union { + /* For character and block devices. */ + struct { + u64 major; /* Major device number. */ + u64 minor; /* Minor device number. */ + void *device_end[0]; /* Marker for offsetof(). */ + } __attribute__((__packed__)); + /* For symbolic links. */ + ntfschar target[0]; + } __attribute__((__packed__)); +} __attribute__((__packed__)) INTX_FILE; + +#endif /* defined _NTFS_LAYOUT_H */ diff --git a/include/ntfs-3g/lcnalloc.h b/include/ntfs-3g/lcnalloc.h new file mode 100644 index 00000000..e87ca43a --- /dev/null +++ b/include/ntfs-3g/lcnalloc.h @@ -0,0 +1,50 @@ +/* + * lcnalloc.h - Exports for cluster (de)allocation. Originated from the Linux-NTFS + * project. + * + * Copyright (c) 2002 Anton Altaparmakov + * Copyright (c) 2004 Yura Pakhuchiy + * + * 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 + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program/include file 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 + */ + +#ifndef _NTFS_LCNALLOC_H +#define _NTFS_LCNALLOC_H + +#include "types.h" +#include "runlist.h" +#include "volume.h" + +/** + * enum NTFS_CLUSTER_ALLOCATION_ZONES - + */ +typedef enum { + FIRST_ZONE = 0, /* For sanity checking. */ + MFT_ZONE = 0, /* Allocate from $MFT zone. */ + DATA_ZONE = 1, /* Allocate from $DATA zone. */ + LAST_ZONE = 1, /* For sanity checking. */ +} NTFS_CLUSTER_ALLOCATION_ZONES; + +extern runlist *ntfs_cluster_alloc(ntfs_volume *vol, VCN start_vcn, s64 count, + LCN start_lcn, const NTFS_CLUSTER_ALLOCATION_ZONES zone); + +extern int ntfs_cluster_free_from_rl(ntfs_volume *vol, runlist *rl); + +extern int ntfs_cluster_free(ntfs_volume *vol, ntfs_attr *na, VCN start_vcn, + s64 count); + +#endif /* defined _NTFS_LCNALLOC_H */ + diff --git a/include/ntfs-3g/list.h b/include/ntfs-3g/list.h new file mode 100644 index 00000000..415e03c0 --- /dev/null +++ b/include/ntfs-3g/list.h @@ -0,0 +1,192 @@ +/* + * list.h - Linked list implementation. Originated from the Linux-NTFS project. + * + * Copyright (c) 2000-2002 Anton Altaparmakov and others + * + * 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 + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program/include file 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 + */ + +#ifndef _NTFS_LIST_H +#define _NTFS_LIST_H + +/** + * struct list_head - Simple doubly linked list implementation. + * + * Copied from Linux kernel 2.4.2-ac18 into Linux-NTFS (with minor + * modifications). - AIA + * + * Some of the internal functions ("__xxx") are useful when + * manipulating whole lists rather than single entries, as + * sometimes we already know the next/prev entries and we can + * generate better code by using them directly rather than + * using the generic single-entry routines. + */ +struct list_head { + struct list_head *next, *prev; +}; + +#define LIST_HEAD_INIT(name) { &(name), &(name) } + +#define LIST_HEAD(name) \ + struct list_head name = LIST_HEAD_INIT(name) + +#define INIT_LIST_HEAD(ptr) do { \ + (ptr)->next = (ptr); (ptr)->prev = (ptr); \ +} while (0) + +/** + * __list_add - Insert a new entry between two known consecutive entries. + * @new: + * @prev: + * @next: + * + * This is only for internal list manipulation where we know the prev/next + * entries already! + */ +static void __list_add(struct list_head * new, + struct list_head * prev, struct list_head * next) +{ + next->prev = new; + new->next = next; + new->prev = prev; + prev->next = new; +} + +/** + * list_add - add a new entry + * @new: new entry to be added + * @head: list head to add it after + * + * Insert a new entry after the specified head. + * This is good for implementing stacks. + */ +static __inline__ void list_add(struct list_head *new, struct list_head *head) +{ + __list_add(new, head, head->next); +} + +/** + * list_add_tail - add a new entry + * @new: new entry to be added + * @head: list head to add it before + * + * Insert a new entry before the specified head. + * This is useful for implementing queues. + */ +static __inline__ void list_add_tail(struct list_head *new, struct list_head *head) +{ + __list_add(new, head->prev, head); +} + +/** + * __list_del - + * @prev: + * @next: + * + * Delete a list entry by making the prev/next entries point to each other. + * + * This is only for internal list manipulation where we know the prev/next + * entries already! + */ +static __inline__ void __list_del(struct list_head * prev, + struct list_head * next) +{ + next->prev = prev; + prev->next = next; +} + +/** + * list_del - deletes entry from list. + * @entry: the element to delete from the list. + * + * Note: list_empty on entry does not return true after this, the entry is in + * an undefined state. + */ +static __inline__ void list_del(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); +} + +/** + * list_del_init - deletes entry from list and reinitialize it. + * @entry: the element to delete from the list. + */ +static __inline__ void list_del_init(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); + INIT_LIST_HEAD(entry); +} + +/** + * list_empty - tests whether a list is empty + * @head: the list to test. + */ +static __inline__ int list_empty(struct list_head *head) +{ + return head->next == head; +} + +/** + * list_splice - join two lists + * @list: the new list to add. + * @head: the place to add it in the first list. + */ +static void list_splice(struct list_head *list, + struct list_head *head) +{ + struct list_head *first = list->next; + + if (first != list) { + struct list_head *last = list->prev; + struct list_head *at = head->next; + + first->prev = head; + head->next = first; + + last->next = at; + at->prev = last; + } +} + +/** + * list_entry - get the struct for this entry + * @ptr: the &struct list_head pointer. + * @type: the type of the struct this is embedded in. + * @member: the name of the list_struct within the struct. + */ +#define list_entry(ptr, type, member) \ + ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member))) + +/** + * list_for_each - iterate over a list + * @pos: the &struct list_head to use as a loop counter. + * @head: the head for your list. + */ +#define list_for_each(pos, head) \ + for (pos = (head)->next; pos != (head); pos = pos->next) + +/** + * list_for_each_safe - iterate over a list safe against removal of list entry + * @pos: the &struct list_head to use as a loop counter. + * @n: another &struct list_head to use as temporary storage + * @head: the head for your list. + */ +#define list_for_each_safe(pos, n, head) \ + for (pos = (head)->next, n = pos->next; pos != (head); \ + pos = n, n = pos->next) + +#endif /* defined _NTFS_LIST_H */ + diff --git a/include/ntfs-3g/logfile.h b/include/ntfs-3g/logfile.h new file mode 100644 index 00000000..798d562d --- /dev/null +++ b/include/ntfs-3g/logfile.h @@ -0,0 +1,394 @@ +/* + * logfile.h - Exports for $LogFile handling. Originated from the Linux-NTFS project. + * + * Copyright (c) 2000-2005 Anton Altaparmakov + * + * 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 + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program/include file 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 + */ + +#ifndef _NTFS_LOGFILE_H +#define _NTFS_LOGFILE_H + +#include "types.h" +#include "endians.h" +#include "layout.h" + +/* + * Journal ($LogFile) organization: + * + * Two restart areas present in the first two pages (restart pages, one restart + * area in each page). When the volume is dismounted they should be identical, + * except for the update sequence array which usually has a different update + * sequence number. + * + * These are followed by log records organized in pages headed by a log record + * header going up to log file size. Not all pages contain log records when a + * volume is first formatted, but as the volume ages, all records will be used. + * When the log file fills up, the records at the beginning are purged (by + * modifying the oldest_lsn to a higher value presumably) and writing begins + * at the beginning of the file. Effectively, the log file is viewed as a + * circular entity. + * + * NOTE: Windows NT, 2000, and XP all use log file version 1.1 but they accept + * versions <= 1.x, including 0.-1. (Yes, that is a minus one in there!) We + * probably only want to support 1.1 as this seems to be the current version + * and we don't know how that differs from the older versions. The only + * exception is if the journal is clean as marked by the two restart pages + * then it doesn't matter whether we are on an earlier version. We can just + * reinitialize the logfile and start again with version 1.1. + */ + +/* Some $LogFile related constants. */ +#define MaxLogFileSize 0x100000000ULL +#define DefaultLogPageSize 4096 +#define MinLogRecordPages 48 + +/** + * struct RESTART_PAGE_HEADER - Log file restart page header. + * + * Begins the restart area. + */ +typedef struct { +/*Ofs*/ +/* 0 NTFS_RECORD; -- Unfolded here as gcc doesn't like unnamed structs. */ +/* 0*/ NTFS_RECORD_TYPES magic;/* The magic is "RSTR". */ +/* 4*/ le16 usa_ofs; /* See NTFS_RECORD definition in layout.h. + When creating, set this to be immediately + after this header structure (without any + alignment). */ +/* 6*/ le16 usa_count; /* See NTFS_RECORD definition in layout.h. */ + +/* 8*/ leLSN chkdsk_lsn; /* The last log file sequence number found by + chkdsk. Only used when the magic is changed + to "CHKD". Otherwise this is zero. */ +/* 16*/ le32 system_page_size; /* Byte size of system pages when the log file + was created, has to be >= 512 and a power of + 2. Use this to calculate the required size + of the usa (usa_count) and add it to usa_ofs. + Then verify that the result is less than the + value of the restart_area_offset. */ +/* 20*/ le32 log_page_size; /* Byte size of log file pages, has to be >= + 512 and a power of 2. The default is 4096 + and is used when the system page size is + between 4096 and 8192. Otherwise this is + set to the system page size instead. */ +/* 24*/ le16 restart_area_offset;/* Byte offset from the start of this header to + the RESTART_AREA. Value has to be aligned + to 8-byte boundary. When creating, set this + to be after the usa. */ +/* 26*/ sle16 minor_ver; /* Log file minor version. Only check if major + version is 1. */ +/* 28*/ sle16 major_ver; /* Log file major version. We only support + version 1.1. */ +/* sizeof() = 30 (0x1e) bytes */ +} __attribute__((__packed__)) RESTART_PAGE_HEADER; + +/* + * Constant for the log client indices meaning that there are no client records + * in this particular client array. Also inside the client records themselves, + * this means that there are no client records preceding or following this one. + */ +#define LOGFILE_NO_CLIENT const_cpu_to_le16(0xffff) +#define LOGFILE_NO_CLIENT_CPU 0xffff + +/* + * These are the so far known RESTART_AREA_* flags (16-bit) which contain + * information about the log file in which they are present. + */ +enum { + RESTART_VOLUME_IS_CLEAN = const_cpu_to_le16(0x0002), + RESTART_SPACE_FILLER = 0xffff, /* gcc: Force enum bit width to 16. */ +} __attribute__((__packed__)); + +typedef le16 RESTART_AREA_FLAGS; + +/** + * struct RESTART_AREA - Log file restart area record. + * + * The offset of this record is found by adding the offset of the + * RESTART_PAGE_HEADER to the restart_area_offset value found in it. + * See notes at restart_area_offset above. + */ +typedef struct { +/*Ofs*/ +/* 0*/ leLSN current_lsn; /* The current, i.e. last LSN inside the log + when the restart area was last written. + This happens often but what is the interval? + Is it just fixed time or is it every time a + check point is written or something else? + On create set to 0. */ +/* 8*/ le16 log_clients; /* Number of log client records in the array of + log client records which follows this + restart area. Must be 1. */ +/* 10*/ le16 client_free_list; /* The index of the first free log client record + in the array of log client records. + LOGFILE_NO_CLIENT means that there are no + free log client records in the array. + If != LOGFILE_NO_CLIENT, check that + log_clients > client_free_list. On Win2k + and presumably earlier, on a clean volume + this is != LOGFILE_NO_CLIENT, and it should + be 0, i.e. the first (and only) client + record is free and thus the logfile is + closed and hence clean. A dirty volume + would have left the logfile open and hence + this would be LOGFILE_NO_CLIENT. On WinXP + and presumably later, the logfile is always + open, even on clean shutdown so this should + always be LOGFILE_NO_CLIENT. */ +/* 12*/ le16 client_in_use_list;/* The index of the first in-use log client + record in the array of log client records. + LOGFILE_NO_CLIENT means that there are no + in-use log client records in the array. If + != LOGFILE_NO_CLIENT check that log_clients + > client_in_use_list. On Win2k and + presumably earlier, on a clean volume this + is LOGFILE_NO_CLIENT, i.e. there are no + client records in use and thus the logfile + is closed and hence clean. A dirty volume + would have left the logfile open and hence + this would be != LOGFILE_NO_CLIENT, and it + should be 0, i.e. the first (and only) + client record is in use. On WinXP and + presumably later, the logfile is always + open, even on clean shutdown so this should + always be 0. */ +/* 14*/ RESTART_AREA_FLAGS flags;/* Flags modifying LFS behaviour. On Win2k + and presumably earlier this is always 0. On + WinXP and presumably later, if the logfile + was shutdown cleanly, the second bit, + RESTART_VOLUME_IS_CLEAN, is set. This bit + is cleared when the volume is mounted by + WinXP and set when the volume is dismounted, + thus if the logfile is dirty, this bit is + clear. Thus we don't need to check the + Windows version to determine if the logfile + is clean. Instead if the logfile is closed, + we know it must be clean. If it is open and + this bit is set, we also know it must be + clean. If on the other hand the logfile is + open and this bit is clear, we can be almost + certain that the logfile is dirty. */ +/* 16*/ le32 seq_number_bits; /* How many bits to use for the sequence + number. This is calculated as 67 - the + number of bits required to store the logfile + size in bytes and this can be used in with + the specified file_size as a consistency + check. */ +/* 20*/ le16 restart_area_length;/* Length of the restart area including the + client array. Following checks required if + version matches. Otherwise, skip them. + restart_area_offset + restart_area_length + has to be <= system_page_size. Also, + restart_area_length has to be >= + client_array_offset + (log_clients * + sizeof(log client record)). */ +/* 22*/ le16 client_array_offset;/* Offset from the start of this record to + the first log client record if versions are + matched. When creating, set this to be + after this restart area structure, aligned + to 8-bytes boundary. If the versions do not + match, this is ignored and the offset is + assumed to be (sizeof(RESTART_AREA) + 7) & + ~7, i.e. rounded up to first 8-byte + boundary. Either way, client_array_offset + has to be aligned to an 8-byte boundary. + Also, restart_area_offset + + client_array_offset has to be <= 510. + Finally, client_array_offset + (log_clients + * sizeof(log client record)) has to be <= + system_page_size. On Win2k and presumably + earlier, this is 0x30, i.e. immediately + following this record. On WinXP and + presumably later, this is 0x40, i.e. there + are 16 extra bytes between this record and + the client array. This probably means that + the RESTART_AREA record is actually bigger + in WinXP and later. */ +/* 24*/ sle64 file_size; /* Usable byte size of the log file. If the + restart_area_offset + the offset of the + file_size are > 510 then corruption has + occurred. This is the very first check when + starting with the restart_area as if it + fails it means that some of the above values + will be corrupted by the multi sector + transfer protection. The file_size has to + be rounded down to be a multiple of the + log_page_size in the RESTART_PAGE_HEADER and + then it has to be at least big enough to + store the two restart pages and 48 (0x30) + log record pages. */ +/* 32*/ le32 last_lsn_data_length;/* Length of data of last LSN, not including + the log record header. On create set to + 0. */ +/* 36*/ le16 log_record_header_length;/* Byte size of the log record header. + If the version matches then check that the + value of log_record_header_length is a + multiple of 8, i.e. + (log_record_header_length + 7) & ~7 == + log_record_header_length. When creating set + it to sizeof(LOG_RECORD_HEADER), aligned to + 8 bytes. */ +/* 38*/ le16 log_page_data_offset;/* Offset to the start of data in a log record + page. Must be a multiple of 8. On create + set it to immediately after the update + sequence array of the log record page. */ +/* 40*/ le32 restart_log_open_count;/* A counter that gets incremented every + time the logfile is restarted which happens + at mount time when the logfile is opened. + When creating set to a random value. Win2k + sets it to the low 32 bits of the current + system time in NTFS format (see time.h). */ +/* 44*/ le32 reserved; /* Reserved/alignment to 8-byte boundary. */ +/* sizeof() = 48 (0x30) bytes */ +} __attribute__((__packed__)) RESTART_AREA; + +/** + * struct LOG_CLIENT_RECORD - Log client record. + * + * The offset of this record is found by adding the offset of the + * RESTART_AREA to the client_array_offset value found in it. + */ +typedef struct { +/*Ofs*/ +/* 0*/ leLSN oldest_lsn; /* Oldest LSN needed by this client. On create + set to 0. */ +/* 8*/ leLSN client_restart_lsn;/* LSN at which this client needs to restart + the volume, i.e. the current position within + the log file. At present, if clean this + should = current_lsn in restart area but it + probably also = current_lsn when dirty most + of the time. At create set to 0. */ +/* 16*/ le16 prev_client; /* The offset to the previous log client record + in the array of log client records. + LOGFILE_NO_CLIENT means there is no previous + client record, i.e. this is the first one. + This is always LOGFILE_NO_CLIENT. */ +/* 18*/ le16 next_client; /* The offset to the next log client record in + the array of log client records. + LOGFILE_NO_CLIENT means there are no next + client records, i.e. this is the last one. + This is always LOGFILE_NO_CLIENT. */ +/* 20*/ le16 seq_number; /* On Win2k and presumably earlier, this is set + to zero every time the logfile is restarted + and it is incremented when the logfile is + closed at dismount time. Thus it is 0 when + dirty and 1 when clean. On WinXP and + presumably later, this is always 0. */ +/* 22*/ u8 reserved[6]; /* Reserved/alignment. */ +/* 28*/ le32 client_name_length;/* Length of client name in bytes. Should + always be 8. */ +/* 32*/ ntfschar client_name[64];/* Name of the client in Unicode. Should + always be "NTFS" with the remaining bytes + set to 0. */ +/* sizeof() = 160 (0xa0) bytes */ +} __attribute__((__packed__)) LOG_CLIENT_RECORD; + +/** + * struct RECORD_PAGE_HEADER - Log page record page header. + * + * Each log page begins with this header and is followed by several LOG_RECORD + * structures, starting at offset 0x40 (the size of this structure and the + * following update sequence array and then aligned to 8 byte boundary, but is + * this specified anywhere?). + */ +typedef struct { +/* 0 NTFS_RECORD; -- Unfolded here as gcc doesn't like unnamed structs. */ + NTFS_RECORD_TYPES magic;/* Usually the magic is "RCRD". */ + u16 usa_ofs; /* See NTFS_RECORD definition in layout.h. + When creating, set this to be immediately + after this header structure (without any + alignment). */ + u16 usa_count; /* See NTFS_RECORD definition in layout.h. */ + + union { + LSN last_lsn; + s64 file_offset; + } __attribute__((__packed__)) copy; + u32 flags; + u16 page_count; + u16 page_position; + union { + struct { + u16 next_record_offset; + u8 reserved[6]; + LSN last_end_lsn; + } __attribute__((__packed__)) packed; + } __attribute__((__packed__)) header; +} __attribute__((__packed__)) RECORD_PAGE_HEADER; + +/** + * enum LOG_RECORD_FLAGS - Possible 16-bit flags for log records. + * + * (Or is it log record pages?) + */ +typedef enum { + LOG_RECORD_MULTI_PAGE = const_cpu_to_le16(0x0001), /* ??? */ + LOG_RECORD_SIZE_PLACE_HOLDER = 0xffff, + /* This has nothing to do with the log record. It is only so + gcc knows to make the flags 16-bit. */ +} __attribute__((__packed__)) LOG_RECORD_FLAGS; + +/** + * struct LOG_CLIENT_ID - The log client id structure identifying a log client. + */ +typedef struct { + u16 seq_number; + u16 client_index; +} __attribute__((__packed__)) LOG_CLIENT_ID; + +/** + * struct LOG_RECORD - Log record header. + * + * Each log record seems to have a constant size of 0x70 bytes. + */ +typedef struct { + LSN this_lsn; + LSN client_previous_lsn; + LSN client_undo_next_lsn; + u32 client_data_length; + LOG_CLIENT_ID client_id; + u32 record_type; + u32 transaction_id; + u16 flags; + u16 reserved_or_alignment[3]; +/* Now are at ofs 0x30 into struct. */ + u16 redo_operation; + u16 undo_operation; + u16 redo_offset; + u16 redo_length; + u16 undo_offset; + u16 undo_length; + u16 target_attribute; + u16 lcns_to_follow; /* Number of lcn_list entries + following this entry. */ +/* Now at ofs 0x40. */ + u16 record_offset; + u16 attribute_offset; + u32 alignment_or_reserved; + VCN target_vcn; +/* Now at ofs 0x50. */ + struct { /* Only present if lcns_to_follow + is not 0. */ + LCN lcn; + } __attribute__((__packed__)) lcn_list[0]; +} __attribute__((__packed__)) LOG_RECORD; + +extern BOOL ntfs_check_logfile(ntfs_attr *log_na, RESTART_PAGE_HEADER **rp); +extern BOOL ntfs_is_logfile_clean(ntfs_attr *log_na, RESTART_PAGE_HEADER *rp); +extern int ntfs_empty_logfile(ntfs_attr *na); + +#endif /* defined _NTFS_LOGFILE_H */ diff --git a/include/ntfs-3g/logging.h b/include/ntfs-3g/logging.h new file mode 100644 index 00000000..bc84734f --- /dev/null +++ b/include/ntfs-3g/logging.h @@ -0,0 +1,111 @@ +/* + * logging.h - Centralised logging. Originated from the Linux-NTFS project. + * + * Copyright (c) 2005 Richard Russon + * + * 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 + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program/include file 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 + */ + +#ifndef _LOGGING_H_ +#define _LOGGING_H_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_STDARG_H +#include +#endif + +#include "types.h" + +/* Function prototype for the logging handlers */ +typedef int (ntfs_log_handler)(const char *function, const char *file, int line, + u32 level, void *data, const char *format, va_list args); + +/* Set the logging handler from one of the functions, below. */ +void ntfs_log_set_handler(ntfs_log_handler *handler); + +/* Logging handlers */ +ntfs_log_handler ntfs_log_handler_syslog __attribute__((format(printf, 6, 0))); +ntfs_log_handler ntfs_log_handler_fprintf __attribute__((format(printf, 6, 0))); +ntfs_log_handler ntfs_log_handler_null __attribute__((format(printf, 6, 0))); +ntfs_log_handler ntfs_log_handler_stdout __attribute__((format(printf, 6, 0))); +ntfs_log_handler ntfs_log_handler_outerr __attribute__((format(printf, 6, 0))); +ntfs_log_handler ntfs_log_handler_stderr __attribute__((format(printf, 6, 0))); + +/* Enable/disable certain log levels */ +u32 ntfs_log_set_levels(u32 levels); +u32 ntfs_log_clear_levels(u32 levels); +u32 ntfs_log_get_levels(void); + +/* Enable/disable certain log flags */ +u32 ntfs_log_set_flags(u32 flags); +u32 ntfs_log_clear_flags(u32 flags); +u32 ntfs_log_get_flags(void); + +/* Turn command-line options into logging flags */ +BOOL ntfs_log_parse_option(const char *option); + +int ntfs_log_redirect(const char *function, const char *file, int line, + u32 level, void *data, const char *format, ...) + __attribute__((format(printf, 6, 7))); + +/* Logging levels - Determine what gets logged */ +#define NTFS_LOG_LEVEL_DEBUG (1 << 0) /* x = 42 */ +#define NTFS_LOG_LEVEL_TRACE (1 << 1) /* Entering function x() */ +#define NTFS_LOG_LEVEL_QUIET (1 << 2) /* Quietable output */ +#define NTFS_LOG_LEVEL_INFO (1 << 3) /* Volume needs defragmenting */ +#define NTFS_LOG_LEVEL_VERBOSE (1 << 4) /* Forced to continue */ +#define NTFS_LOG_LEVEL_PROGRESS (1 << 5) /* 54% complete */ +#define NTFS_LOG_LEVEL_WARNING (1 << 6) /* You should backup before starting */ +#define NTFS_LOG_LEVEL_ERROR (1 << 7) /* Operation failed, no damage done */ +#define NTFS_LOG_LEVEL_PERROR (1 << 8) /* Message : standard error description */ +#define NTFS_LOG_LEVEL_CRITICAL (1 << 9) /* Operation failed,damage may have occurred */ + +/* Logging style flags - Manage the style of the output */ +#define NTFS_LOG_FLAG_PREFIX (1 << 0) /* Prefix messages with "ERROR: ", etc */ +#define NTFS_LOG_FLAG_FILENAME (1 << 1) /* Show the file origin of the message */ +#define NTFS_LOG_FLAG_LINE (1 << 2) /* Show the line number of the message */ +#define NTFS_LOG_FLAG_FUNCTION (1 << 3) /* Show the function name containing the message */ +#define NTFS_LOG_FLAG_ONLYNAME (1 << 4) /* Only display the filename, not the pathname */ +#define NTFS_LOG_FLAG_COLOUR (1 << 5) /* Colour highlight some messages */ + +/* Macros to simplify logging. One for each level defined above. + * Note, ntfs_log_debug/trace have effect only if DEBUG is defined. + */ +#define ntfs_log_critical(FORMAT, ARGS...) ntfs_log_redirect(__FUNCTION__,__FILE__,__LINE__,NTFS_LOG_LEVEL_CRITICAL,NULL,FORMAT,##ARGS) +#define ntfs_log_error(FORMAT, ARGS...) ntfs_log_redirect(__FUNCTION__,__FILE__,__LINE__,NTFS_LOG_LEVEL_ERROR,NULL,FORMAT,##ARGS) +#define ntfs_log_info(FORMAT, ARGS...) ntfs_log_redirect(__FUNCTION__,__FILE__,__LINE__,NTFS_LOG_LEVEL_INFO,NULL,FORMAT,##ARGS) +#define ntfs_log_perror(FORMAT, ARGS...) ntfs_log_redirect(__FUNCTION__,__FILE__,__LINE__,NTFS_LOG_LEVEL_PERROR,NULL,FORMAT,##ARGS) +#define ntfs_log_progress(FORMAT, ARGS...) ntfs_log_redirect(__FUNCTION__,__FILE__,__LINE__,NTFS_LOG_LEVEL_PROGRESS,NULL,FORMAT,##ARGS) +#define ntfs_log_quiet(FORMAT, ARGS...) ntfs_log_redirect(__FUNCTION__,__FILE__,__LINE__,NTFS_LOG_LEVEL_QUIET,NULL,FORMAT,##ARGS) +#define ntfs_log_verbose(FORMAT, ARGS...) ntfs_log_redirect(__FUNCTION__,__FILE__,__LINE__,NTFS_LOG_LEVEL_VERBOSE,NULL,FORMAT,##ARGS) +#define ntfs_log_warning(FORMAT, ARGS...) ntfs_log_redirect(__FUNCTION__,__FILE__,__LINE__,NTFS_LOG_LEVEL_WARNING,NULL,FORMAT,##ARGS) + +/* By default debug and trace messages are compiled into the program, + * but not displayed. + */ +#ifdef DEBUG +#define ntfs_log_debug(FORMAT, ARGS...) ntfs_log_redirect(__FUNCTION__,__FILE__,__LINE__,NTFS_LOG_LEVEL_DEBUG,NULL,FORMAT,##ARGS) +#define ntfs_log_trace(FORMAT, ARGS...) ntfs_log_redirect(__FUNCTION__,__FILE__,__LINE__,NTFS_LOG_LEVEL_TRACE,NULL,FORMAT,##ARGS) +#else +#define ntfs_log_debug(FORMAT, ARGS...)do {} while (0) +#define ntfs_log_trace(FORMAT, ARGS...)do {} while (0) +#endif /* DEBUG */ + +#endif /* _LOGGING_H_ */ + diff --git a/include/ntfs-3g/mft.h b/include/ntfs-3g/mft.h new file mode 100644 index 00000000..b6bf1875 --- /dev/null +++ b/include/ntfs-3g/mft.h @@ -0,0 +1,118 @@ +/* + * mft.h - Exports for MFT record handling. Originated from the Linux-NTFS project. + * + * Copyright (c) 2000-2002 Anton Altaparmakov + * Copyright (c) 2004-2005 Richard Russon + * Copyright (c) 2006 Szabolcs Szakacsits + * + * 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 + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program/include file 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 + */ + +#ifndef _NTFS_MFT_H +#define _NTFS_MFT_H + +#include "volume.h" +#include "inode.h" +#include "layout.h" + +extern int ntfs_mft_records_read(const ntfs_volume *vol, const MFT_REF mref, + const s64 count, MFT_RECORD *b); + +/** + * ntfs_mft_record_read - read a record from the mft + * @vol: volume to read from + * @mref: mft record number to read + * @b: output data buffer + * + * Read the mft record specified by @mref from volume @vol into buffer @b. + * Return 0 on success or -1 on error, with errno set to the error code. + * + * The read mft record is mst deprotected and is hence ready to use. The caller + * should check the record with is_baad_record() in case mst deprotection + * failed. + * + * NOTE: @b has to be at least of size vol->mft_record_size. + */ +static __inline__ int ntfs_mft_record_read(const ntfs_volume *vol, + const MFT_REF mref, MFT_RECORD *b) +{ + return ntfs_mft_records_read(vol, mref, 1, b); +} + +extern int ntfs_file_record_read(const ntfs_volume *vol, const MFT_REF mref, + MFT_RECORD **mrec, ATTR_RECORD **attr); + +extern int ntfs_mft_records_write(const ntfs_volume *vol, const MFT_REF mref, + const s64 count, MFT_RECORD *b); + +/** + * ntfs_mft_record_write - write an mft record to disk + * @vol: volume to write to + * @mref: mft record number to write + * @b: data buffer containing the mft record to write + * + * Write the mft record specified by @mref from buffer @b to volume @vol. + * Return 0 on success or -1 on error, with errno set to the error code. + * + * Before the mft record is written, it is mst protected. After the write, it + * is deprotected again, thus resulting in an increase in the update sequence + * number inside the buffer @b. + * + * NOTE: @b has to be at least of size vol->mft_record_size. + */ +static __inline__ int ntfs_mft_record_write(const ntfs_volume *vol, + const MFT_REF mref, MFT_RECORD *b) +{ + return ntfs_mft_records_write(vol, mref, 1, b); +} + +/** + * ntfs_mft_record_get_data_size - return number of bytes used in mft record @b + * @m: mft record to get the data size of + * + * Takes the mft record @m and returns the number of bytes used in the record + * or 0 on error (i.e. @m is not a valid mft record). Zero is not a valid size + * for an mft record as it at least has to have the MFT_RECORD itself and a + * zero length attribute of type AT_END, thus making the minimum size 56 bytes. + * + * Aside: The size is independent of NTFS versions 1.x/3.x because the 8-byte + * alignment of the first attribute mask the difference in MFT_RECORD size + * between NTFS 1.x and 3.x. Also, you would expect every mft record to + * contain an update sequence array as well but that could in theory be + * non-existent (don't know if Windows' NTFS driver/chkdsk wouldn't view this + * as corruption in itself though). + */ +static __inline__ u32 ntfs_mft_record_get_data_size(const MFT_RECORD *m) +{ + if (!m || !ntfs_is_mft_record(m->magic)) + return 0; + /* Get the number of used bytes and return it. */ + return le32_to_cpu(m->bytes_in_use); +} + +extern int ntfs_mft_record_layout(const ntfs_volume *vol, const MFT_REF mref, + MFT_RECORD *mrec); + +extern int ntfs_mft_record_format(const ntfs_volume *vol, const MFT_REF mref); + +extern ntfs_inode *ntfs_mft_record_alloc(ntfs_volume *vol, ntfs_inode *base_ni); + +extern int ntfs_mft_record_free(ntfs_volume *vol, ntfs_inode *ni); + +extern int ntfs_mft_usn_dec(MFT_RECORD *mrec); + +#endif /* defined _NTFS_MFT_H */ + diff --git a/include/ntfs-3g/misc.h b/include/ntfs-3g/misc.h new file mode 100644 index 00000000..67cc1eb6 --- /dev/null +++ b/include/ntfs-3g/misc.h @@ -0,0 +1,8 @@ +#ifndef _NTFS_MISC_H_ +#define _NTFS_MISC_H_ + +void *ntfs_calloc(size_t size); +void *ntfs_malloc(size_t size); + +#endif /* _NTFS_MISC_H_ */ + diff --git a/include/ntfs-3g/mst.h b/include/ntfs-3g/mst.h new file mode 100644 index 00000000..ca813821 --- /dev/null +++ b/include/ntfs-3g/mst.h @@ -0,0 +1,34 @@ +/* + * mst.h - Exports for multi sector transfer fixup functions. + * Originated from the Linux-NTFS project. + * + * Copyright (c) 2000-2002 Anton Altaparmakov + * + * 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 + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program/include file 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 + */ + +#ifndef _NTFS_MST_H +#define _NTFS_MST_H + +#include "types.h" +#include "layout.h" + +extern int ntfs_mst_post_read_fixup(NTFS_RECORD *b, const u32 size); +extern int ntfs_mst_pre_write_fixup(NTFS_RECORD *b, const u32 size); +extern void ntfs_mst_post_write_fixup(NTFS_RECORD *b); + +#endif /* defined _NTFS_MST_H */ + diff --git a/include/ntfs-3g/ntfstime.h b/include/ntfs-3g/ntfstime.h new file mode 100644 index 00000000..e933b53c --- /dev/null +++ b/include/ntfs-3g/ntfstime.h @@ -0,0 +1,69 @@ +/* + * ntfstime.h - NTFS time related functions. Originated from the Linux-NTFS project. + * + * Copyright (c) 2005 Anton Altaparmakov + * Copyright (c) 2005 Yura Pakhuchiy + * + * 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 + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program/include file 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 + */ + +#ifndef _NTFS_NTFSTIME_H +#define _NTFS_NTFSTIME_H + +#ifdef HAVE_TIME_H +#include +#endif + +#include "types.h" + +#define NTFS_TIME_OFFSET ((s64)(369 * 365 + 89) * 24 * 3600 * 10000000) + +/** + * ntfs2utc - Convert an NTFS time to Unix time + * @ntfs_time: An NTFS time in 100ns units since 1601 + * + * NTFS stores times as the number of 100ns intervals since January 1st 1601 at + * 00:00 UTC. This system will not suffer from Y2K problems until ~57000AD. + * + * Return: n A Unix time (number of seconds since 1970) + */ +static __inline__ time_t ntfs2utc(s64 ntfs_time) +{ + return (sle64_to_cpu(ntfs_time) - (NTFS_TIME_OFFSET)) / 10000000; +} + +/** + * utc2ntfs - Convert Linux time to NTFS time + * @utc_time: Linux time to convert to NTFS + * + * Convert the Linux time @utc_time to its corresponding NTFS time. + * + * Linux stores time in a long at present and measures it as the number of + * 1-second intervals since 1st January 1970, 00:00:00 UTC. + * + * NTFS uses Microsoft's standard time format which is stored in a s64 and is + * measured as the number of 100 nano-second intervals since 1st January 1601, + * 00:00:00 UTC. + * + * Return: n An NTFS time (100ns units since Jan 1601) + */ +static __inline__ s64 utc2ntfs(time_t utc_time) +{ + /* Convert to 100ns intervals and then add the NTFS time offset. */ + return cpu_to_sle64((s64)utc_time * 10000000 + NTFS_TIME_OFFSET); +} + +#endif /* _NTFS_NTFSTIME_H */ diff --git a/include/ntfs-3g/runlist.h b/include/ntfs-3g/runlist.h new file mode 100644 index 00000000..11950e19 --- /dev/null +++ b/include/ntfs-3g/runlist.h @@ -0,0 +1,87 @@ +/* + * runlist.h - Exports for runlist handling. Originated from the Linux-NTFS project. + * + * Copyright (c) 2002 Anton Altaparmakov + * Copyright (c) 2002 Richard Russon + * + * 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 + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program/include file 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 + */ + +#ifndef _NTFS_RUNLIST_H +#define _NTFS_RUNLIST_H + +#include "types.h" + +/* Forward declarations */ +typedef struct _runlist_element runlist_element; +typedef runlist_element runlist; + +#include "attrib.h" +#include "volume.h" + +/** + * struct _runlist_element - in memory vcn to lcn mapping array element. + * @vcn: starting vcn of the current array element + * @lcn: starting lcn of the current array element + * @length: length in clusters of the current array element + * + * The last vcn (in fact the last vcn + 1) is reached when length == 0. + * + * When lcn == -1 this means that the count vcns starting at vcn are not + * physically allocated (i.e. this is a hole / data is sparse). + */ +struct _runlist_element {/* In memory vcn to lcn mapping structure element. */ + VCN vcn; /* vcn = Starting virtual cluster number. */ + LCN lcn; /* lcn = Starting logical cluster number. */ + s64 length; /* Run length in clusters. */ +}; + +extern LCN ntfs_rl_vcn_to_lcn(const runlist_element *rl, const VCN vcn); + +extern s64 ntfs_rl_pread(const ntfs_volume *vol, const runlist_element *rl, + const s64 pos, s64 count, void *b); +extern s64 ntfs_rl_pwrite(const ntfs_volume *vol, const runlist_element *rl, + const s64 pos, s64 count, void *b); + +extern runlist_element *ntfs_runlists_merge(runlist_element *drl, + runlist_element *srl); + +extern runlist_element *ntfs_mapping_pairs_decompress(const ntfs_volume *vol, + const ATTR_RECORD *attr, runlist_element *old_rl); + +extern int ntfs_get_nr_significant_bytes(const s64 n); + +extern int ntfs_get_size_for_mapping_pairs(const ntfs_volume *vol, + const runlist_element *rl, const VCN start_vcn); + +extern int ntfs_write_significant_bytes(u8 *dst, const u8 *dst_max, + const s64 n); + +extern int ntfs_mapping_pairs_build(const ntfs_volume *vol, u8 *dst, + const int dst_len, const runlist_element *rl, + const VCN start_vcn, VCN *const stop_vcn); + +extern int ntfs_rl_truncate(runlist **arl, const VCN start_vcn); + +extern int ntfs_rl_sparse(runlist *rl); +extern s64 ntfs_rl_get_compressed_size(ntfs_volume *vol, runlist *rl); + +#ifdef NTFS_TEST +int test_rl_main(int argc, char *argv[]); +#endif + +#endif /* defined _NTFS_RUNLIST_H */ + diff --git a/include/ntfs-3g/security.h b/include/ntfs-3g/security.h new file mode 100644 index 00000000..e903f672 --- /dev/null +++ b/include/ntfs-3g/security.h @@ -0,0 +1,58 @@ +/* + * security.h - Exports for handling security/ACLs in NTFS. + * Originated from the Linux-NTFS project. + * + * Copyright (c) 2004 Anton Altaparmakov + * Copyright (c) 2005-2006 Szabolcs Szakacsits + * + * 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 + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program/include file 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 + */ + +#ifndef _NTFS_SECURITY_H +#define _NTFS_SECURITY_H + +#include "types.h" +#include "layout.h" +#include "inode.h" + +extern const GUID *const zero_guid; + +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); +extern void ntfs_generate_guid(GUID *guid); +extern int ntfs_sd_add_everyone(ntfs_inode *ni); + +#endif /* defined _NTFS_SECURITY_H */ diff --git a/include/ntfs-3g/support.h b/include/ntfs-3g/support.h new file mode 100644 index 00000000..6af4761e --- /dev/null +++ b/include/ntfs-3g/support.h @@ -0,0 +1,85 @@ +/* + * support.h - Useful definitions and macros. Originated from the Linux-NTFS project. + * + * Copyright (c) 2000-2004 Anton Altaparmakov + * + * 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 + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program/include file 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 + */ + +#ifndef _NTFS_SUPPORT_H +#define _NTFS_SUPPORT_H + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_STDDEF_H +#include +#endif + +/* + * Our mailing list. Use this define to prevent typos in email address. + */ +#define NTFS_DEV_LIST "ntfs-3g-devel@lists.sf.net" + +/* + * Generic macro to convert pointers to values for comparison purposes. + */ +#ifndef p2n +#define p2n(p) ((ptrdiff_t)((ptrdiff_t*)(p))) +#endif + +/* + * The classic min and max macros. + */ +#ifndef min +#define min(a,b) ((a) <= (b) ? (a) : (b)) +#endif + +#ifndef max +#define max(a,b) ((a) >= (b) ? (a) : (b)) +#endif + +/* + * Useful macro for determining the offset of a struct member. + */ +#ifndef offsetof +#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) +#endif + +/* + * Simple bit operation macros. NOTE: These are NOT atomic. + */ +#define test_bit(bit, var) ((var) & (1 << (bit))) +#define set_bit(bit, var) (var) |= 1 << (bit) +#define clear_bit(bit, var) (var) &= ~(1 << (bit)) + +#define test_and_set_bit(bit, var) \ +({ \ + const BOOL old_state = test_bit(bit, var); \ + set_bit(bit, var); \ + old_state; \ +}) + +#define test_and_clear_bit(bit, var) \ +({ \ + const BOOL old_state = test_bit(bit, var); \ + clear_bit(bit, var); \ + old_state; \ +}) + +#endif /* defined _NTFS_SUPPORT_H */ + diff --git a/include/ntfs-3g/types.h b/include/ntfs-3g/types.h new file mode 100644 index 00000000..8b7f3f52 --- /dev/null +++ b/include/ntfs-3g/types.h @@ -0,0 +1,124 @@ +/* + * types.h - Misc type definitions not related to on-disk structure. + * Originated from the Linux-NTFS project. + * + * Copyright (c) 2000-2004 Anton Altaparmakov + * + * 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 + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program/include file 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 + */ + +#ifndef _NTFS_TYPES_H +#define _NTFS_TYPES_H + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#if HAVE_STDINT_H || !HAVE_CONFIG_H +#include +#endif +#ifdef HAVE_SYS_TYPES_H +#include +#endif + +typedef uint8_t u8; /* Unsigned types of an exact size */ +typedef uint16_t u16; +typedef uint32_t u32; +typedef uint64_t u64; + +typedef int8_t s8; /* Signed types of an exact size */ +typedef int16_t s16; +typedef int32_t s32; +typedef int64_t s64; + +typedef u16 le16; +typedef u32 le32; +typedef u64 le64; + +/* + * Declare sle{16,32,64} to be unsigned because we do not want sign extension + * on BE architectures. + */ +typedef u16 sle16; +typedef u32 sle32; +typedef u64 sle64; + +typedef u16 ntfschar; /* 2-byte Unicode character type. */ +#define UCHAR_T_SIZE_BITS 1 + +/* + * Clusters are signed 64-bit values on NTFS volumes. We define two types, LCN + * and VCN, to allow for type checking and better code readability. + */ +typedef s64 VCN; +typedef sle64 leVCN; +typedef s64 LCN; +typedef sle64 leLCN; + +/* + * The NTFS journal $LogFile uses log sequence numbers which are signed 64-bit + * values. We define our own type LSN, to allow for type checking and better + * code readability. + */ +typedef s64 LSN; +typedef sle64 leLSN; + +/* + * Cygwin has a collision between our BOOL and 's + * As long as this file will be included after were fine. + */ +#ifndef _WINDEF_H +/** + * enum BOOL - These are just to make the code more readable... + */ +typedef enum { +#ifndef FALSE + FALSE = 0, +#endif +#ifndef NO + NO = 0, +#endif +#ifndef ZERO + ZERO = 0, +#endif +#ifndef TRUE + TRUE = 1, +#endif +#ifndef YES + YES = 1, +#endif +#ifndef ONE + ONE = 1, +#endif +} BOOL; +#endif /* defined _WINDEF_H */ + +/** + * enum IGNORE_CASE_BOOL - + */ +typedef enum { + CASE_SENSITIVE = 0, + IGNORE_CASE = 1, +} IGNORE_CASE_BOOL; + +#define STATUS_OK (0) +#define STATUS_ERROR (-1) +#define STATUS_RESIDENT_ATTRIBUTE_FILLED_MFT (-2) +#define STATUS_KEEP_SEARCHING (-3) +#define STATUS_NOT_FOUND (-4) + +#endif /* defined _NTFS_TYPES_H */ + diff --git a/include/ntfs-3g/unistr.h b/include/ntfs-3g/unistr.h new file mode 100644 index 00000000..b45101e9 --- /dev/null +++ b/include/ntfs-3g/unistr.h @@ -0,0 +1,69 @@ +/* + * unistr.h - Exports for Unicode string handling. Originated from the Linux-NTFS + * project. + * + * Copyright (c) 2000-2004 Anton Altaparmakov + * + * 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 + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program/include file 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 + */ + +#ifndef _NTFS_UNISTR_H +#define _NTFS_UNISTR_H + +#include "types.h" +#include "layout.h" + +extern BOOL ntfs_names_are_equal(const ntfschar *s1, size_t s1_len, + const ntfschar *s2, size_t s2_len, const IGNORE_CASE_BOOL ic, + const ntfschar *upcase, const u32 upcase_size); + +extern int ntfs_names_collate(const ntfschar *name1, const u32 name1_len, + const ntfschar *name2, const u32 name2_len, + const int err_val, const IGNORE_CASE_BOOL ic, + const ntfschar *upcase, const u32 upcase_len); + +extern int ntfs_ucsncmp(const ntfschar *s1, const ntfschar *s2, size_t n); + +extern int ntfs_ucsncasecmp(const ntfschar *s1, const ntfschar *s2, size_t n, + const ntfschar *upcase, const u32 upcase_size); + +extern u32 ntfs_ucsnlen(const ntfschar *s, u32 maxlen); + +extern ntfschar *ntfs_ucsndup(const ntfschar *s, u32 maxlen); + +extern void ntfs_name_upcase(ntfschar *name, u32 name_len, + const ntfschar *upcase, const u32 upcase_len); + +extern void ntfs_file_value_upcase(FILE_NAME_ATTR *file_name_attr, + const ntfschar *upcase, const u32 upcase_len); + +extern int ntfs_file_values_compare(const FILE_NAME_ATTR *file_name_attr1, + const FILE_NAME_ATTR *file_name_attr2, + const int err_val, const IGNORE_CASE_BOOL ic, + const ntfschar *upcase, const u32 upcase_len); + +extern int ntfs_ucstombs(const ntfschar *ins, const int ins_len, char **outs, + int outs_len); +extern int ntfs_mbstoucs(const char *ins, ntfschar **outs, int outs_len); + +extern void ntfs_upcase_table_build(ntfschar *uc, u32 uc_len); + +extern ntfschar *ntfs_str2ucs(const char *s, int *len); + +extern void ntfs_ucsfree(ntfschar *ucs); + +#endif /* defined _NTFS_UNISTR_H */ + diff --git a/include/ntfs-3g/version.h b/include/ntfs-3g/version.h new file mode 100644 index 00000000..5f94d11a --- /dev/null +++ b/include/ntfs-3g/version.h @@ -0,0 +1,29 @@ +/* + * version.h - Info about the NTFS library. Originated from the Linux-NTFS project. + * + * Copyright (c) 2005 Anton Altaparmakov + * Copyright (c) 2005 Richard Russon + * + * 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 + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program/include file 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 + */ + +#ifndef _NTFS_VERSION_H_ +#define _NTFS_VERSION_H_ + +extern const char *ntfs_libntfs_version(void); + +#endif /* _NTFS_VERSION_H_ */ + diff --git a/include/ntfs-3g/volume.h b/include/ntfs-3g/volume.h new file mode 100644 index 00000000..7f14ab99 --- /dev/null +++ b/include/ntfs-3g/volume.h @@ -0,0 +1,233 @@ +/* + * volume.h - Exports for NTFS volume handling. Originated from the Linux-NTFS project. + * + * Copyright (c) 2000-2004 Anton Altaparmakov + * Copyright (c) 2004-2005 Richard Russon + * Copyright (c) 2005-2006 Yura Pakhuchiy + * Copyright (c) 2005-2006 Szabolcs Szakacsits + * + * 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 + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program/include file 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 + */ + +#ifndef _NTFS_VOLUME_H +#define _NTFS_VOLUME_H + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_STDIO_H +#include +#endif +#ifdef HAVE_SYS_PARAM_H +#include +#endif +#ifdef HAVE_SYS_MOUNT_H +#include +#endif +#ifdef HAVE_MNTENT_H +#include +#endif + +/* + * Under Cygwin, DJGPP and FreeBSD we do not have MS_RDONLY and MS_NOATIME, + * so we define them ourselves. + */ +#ifndef MS_RDONLY +#define MS_RDONLY 1 +#endif +/* + * Solaris defines MS_RDONLY but not MS_NOATIME thus we need to carefully + * define MS_NOATIME. + */ +#ifndef MS_NOATIME +#if (MS_RDONLY != 1) +# define MS_NOATIME 1 +#else +# define MS_NOATIME 2 +#endif +#endif + +/* Forward declaration */ +typedef struct _ntfs_volume ntfs_volume; + +#include "types.h" +#include "support.h" +#include "device.h" +#include "inode.h" +#include "attrib.h" + +/** + * enum ntfs_mount_flags - + * + * Flags returned by the ntfs_check_if_mounted() function. + */ +typedef enum { + NTFS_MF_MOUNTED = 1, /* Device is mounted. */ + NTFS_MF_ISROOT = 2, /* Device is mounted as system root. */ + NTFS_MF_READONLY = 4, /* Device is mounted read-only. */ +} ntfs_mount_flags; + +extern int ntfs_check_if_mounted(const char *file, unsigned long *mnt_flags); + +/** + * enum ntfs_volume_state_bits - + * + * Defined bits for the state field in the ntfs_volume structure. + */ +typedef enum { + NV_ReadOnly, /* 1: Volume is read-only. */ + NV_CaseSensitive, /* 1: Volume is mounted case-sensitive. */ + NV_LogFileEmpty, /* 1: $logFile journal is empty. */ + NV_NoATime, /* 1: Do not update access time. */ +} ntfs_volume_state_bits; + +#define test_nvol_flag(nv, flag) test_bit(NV_##flag, (nv)->state) +#define set_nvol_flag(nv, flag) set_bit(NV_##flag, (nv)->state) +#define clear_nvol_flag(nv, flag) clear_bit(NV_##flag, (nv)->state) + +#define NVolReadOnly(nv) test_nvol_flag(nv, ReadOnly) +#define NVolSetReadOnly(nv) set_nvol_flag(nv, ReadOnly) +#define NVolClearReadOnly(nv) clear_nvol_flag(nv, ReadOnly) + +#define NVolCaseSensitive(nv) test_nvol_flag(nv, CaseSensitive) +#define NVolSetCaseSensitive(nv) set_nvol_flag(nv, CaseSensitive) +#define NVolClearCaseSensitive(nv) clear_nvol_flag(nv, CaseSensitive) + +#define NVolLogFileEmpty(nv) test_nvol_flag(nv, LogFileEmpty) +#define NVolSetLogFileEmpty(nv) set_nvol_flag(nv, LogFileEmpty) +#define NVolClearLogFileEmpty(nv) clear_nvol_flag(nv, LogFileEmpty) + +#define NVolNoATime(nv) test_nvol_flag(nv, NoATime) +#define NVolSetNoATime(nv) set_nvol_flag(nv, NoATime) +#define NVolClearNoATime(nv) clear_nvol_flag(nv, NoATime) + +/* + * NTFS version 1.1 and 1.2 are used by Windows NT4. + * NTFS version 2.x is used by Windows 2000 Beta + * NTFS version 3.0 is used by Windows 2000. + * NTFS version 3.1 is used by Windows XP, 2003 and Vista. + */ + +#define NTFS_V1_1(major, minor) ((major) == 1 && (minor) == 1) +#define NTFS_V1_2(major, minor) ((major) == 1 && (minor) == 2) +#define NTFS_V2_X(major, minor) ((major) == 2) +#define NTFS_V3_0(major, minor) ((major) == 3 && (minor) == 0) +#define NTFS_V3_1(major, minor) ((major) == 3 && (minor) == 1) + +#define NTFS_BUF_SIZE 8192 + +/** + * struct _ntfs_volume - structure describing an open volume in memory. + */ +struct _ntfs_volume { + union { + struct ntfs_device *dev; /* NTFS device associated with + the volume. */ + void *sb; /* For kernel porting compatibility. */ + }; + char *vol_name; /* Name of the volume. */ + unsigned long state; /* NTFS specific flags describing this volume. + See ntfs_volume_state_bits above. */ + + ntfs_inode *vol_ni; /* ntfs_inode structure for FILE_Volume. */ + u8 major_ver; /* Ntfs major version of volume. */ + u8 minor_ver; /* Ntfs minor version of volume. */ + u16 flags; /* Bit array of VOLUME_* flags. */ + + u16 sector_size; /* Byte size of a sector. */ + u8 sector_size_bits; /* Log(2) of the byte size of a sector. */ + u32 cluster_size; /* Byte size of a cluster. */ + u32 mft_record_size; /* Byte size of a mft record. */ + u32 indx_record_size; /* Byte size of a INDX record. */ + u8 cluster_size_bits; /* Log(2) of the byte size of a cluster. */ + u8 mft_record_size_bits;/* Log(2) of the byte size of a mft record. */ + u8 indx_record_size_bits;/* Log(2) of the byte size of a INDX record. */ + + /* Variables used by the cluster and mft allocators. */ + u8 mft_zone_multiplier; /* Initial mft zone multiplier. */ + s64 mft_data_pos; /* Mft record number at which to allocate the + next mft record. */ + LCN mft_zone_start; /* First cluster of the mft zone. */ + LCN mft_zone_end; /* First cluster beyond the mft zone. */ + LCN mft_zone_pos; /* Current position in the mft zone. */ + LCN data1_zone_pos; /* Current position in the first data zone. */ + LCN data2_zone_pos; /* Current position in the second data zone. */ + + s64 nr_clusters; /* Volume size in clusters, hence also the + number of bits in lcn_bitmap. */ + ntfs_inode *lcnbmp_ni; /* ntfs_inode structure for FILE_Bitmap. */ + ntfs_attr *lcnbmp_na; /* ntfs_attr structure for the data attribute + of FILE_Bitmap. Each bit represents a + cluster on the volume, bit 0 representing + lcn 0 and so on. A set bit means that the + cluster and vice versa. */ + + LCN mft_lcn; /* Logical cluster number of the data attribute + for FILE_MFT. */ + ntfs_inode *mft_ni; /* ntfs_inode structure for FILE_MFT. */ + ntfs_attr *mft_na; /* ntfs_attr structure for the data attribute + of FILE_MFT. */ + ntfs_attr *mftbmp_na; /* ntfs_attr structure for the bitmap attribute + of FILE_MFT. Each bit represents an mft + record in the $DATA attribute, bit 0 + representing mft record 0 and so on. A set + bit means that the mft record is in use and + vice versa. */ + + int mftmirr_size; /* Size of the FILE_MFTMirr in mft records. */ + LCN mftmirr_lcn; /* Logical cluster number of the data attribute + for FILE_MFTMirr. */ + ntfs_inode *mftmirr_ni; /* ntfs_inode structure for FILE_MFTMirr. */ + ntfs_attr *mftmirr_na; /* ntfs_attr structure for the data attribute + of FILE_MFTMirr. */ + + ntfschar *upcase; /* Upper case equivalents of all 65536 2-byte + Unicode characters. Obtained from + FILE_UpCase. */ + u32 upcase_len; /* Length in Unicode characters of the upcase + table. */ + + ATTR_DEF *attrdef; /* Attribute definitions. Obtained from + FILE_AttrDef. */ + s32 attrdef_len; /* Size of the attribute definition table in + bytes. */ + + /* Temp: for directory handling */ + void *private_data; /* ntfs_dir for . */ + void *private_bmp1; /* ntfs_bmp for $MFT/$BITMAP */ + void *private_bmp2; /* ntfs_bmp for $Bitmap */ +}; + +extern ntfs_volume *ntfs_volume_alloc(void); + +extern ntfs_volume *ntfs_volume_startup(struct ntfs_device *dev, + unsigned long flags); + +extern ntfs_volume *ntfs_device_mount(struct ntfs_device *dev, + unsigned long flags); +extern int ntfs_device_umount(ntfs_volume *vol, const BOOL force); + +extern ntfs_volume *ntfs_mount(const char *name, unsigned long flags); +extern int ntfs_umount(ntfs_volume *vol, const BOOL force); + +extern int ntfs_version_is_supported(ntfs_volume *vol); +extern int ntfs_logfile_reset(ntfs_volume *vol); + +extern int ntfs_volume_write_flags(ntfs_volume *vol, const u16 flags); + +#endif /* defined _NTFS_VOLUME_H */ + diff --git a/libntfs-3g/Makefile.am b/libntfs-3g/Makefile.am new file mode 100644 index 00000000..78b64961 --- /dev/null +++ b/libntfs-3g/Makefile.am @@ -0,0 +1,64 @@ +# +# Before making a release, the LTVERSION string should be modified. +# The string is of the form CURRENT:REVISION:AGE. +# +# CURRENT (C) +# The most recent interface number that this library implements. +# +# REVISION (R) +# The implementation number that this library implements. +# +# AGE (A) +# The difference between the newest and oldest interfaces that this +# library implements. In other works, the library implements all the +# interface numbers in the range from number 'CURRENT - AGE' to +# 'CURRENT'. +# +# This means that: +# +# - If interfaces have been changed or added, but binary compatibility has +# been preserved, change to C+1:0:A+1 +# +# - If binary compatibility has been broken (eg removed or changed +# interfaces) change to C+1:0:0 +# +# - If the interface is the same as the previous version, change to C:R+1:A +# + +linux_ntfsincludedir = -I$(top_srcdir)/include/ntfs-3g + +lib_LTLIBRARIES = libntfs-3g.la +libntfs_3g_la_CFLAGS = $(LIBNTFS_3G_CFLAGS) +libntfs_3g_la_SOURCES = \ + attrib.c \ + attrlist.c \ + bitmap.c \ + bootsect.c \ + collate.c \ + compat.c \ + compress.c \ + debug.c \ + device.c \ + device_io.c \ + dir.c \ + index.c \ + inode.c \ + lcnalloc.c \ + logfile.c \ + logging.c \ + mft.c \ + misc.c \ + mst.c \ + runlist.c \ + security.c \ + unistr.c \ + version.c \ + volume.c + +AM_CPPFLAGS = $(linux_ntfsincludedir) $(all_includes) + +EXTRA_DIST = unix_io.c + +MAINTAINERCLEANFILES = Makefile.in + +libs: $(lib_LTLIBRARIES) diff --git a/libntfs-3g/attrib.c b/libntfs-3g/attrib.c new file mode 100644 index 00000000..e35f220d --- /dev/null +++ b/libntfs-3g/attrib.c @@ -0,0 +1,4928 @@ +/** + * attrib.c - Attribute handling code. Originated from the Linux-NTFS project. + * + * Copyright (c) 2000-2005 Anton Altaparmakov + * Copyright (c) 2002-2005 Richard Russon + * Copyright (c) 2002-2006 Szabolcs Szakacsits + * Copyright (c) 2004-2006 Yura Pakhuchiy + * + * 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 + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program/include file 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 + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_STDIO_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_ERRNO_H +#include +#endif + +#include "compat.h" +#include "attrib.h" +#include "attrlist.h" +#include "device.h" +#include "mft.h" +#include "debug.h" +#include "mst.h" +#include "volume.h" +#include "types.h" +#include "layout.h" +#include "inode.h" +#include "runlist.h" +#include "lcnalloc.h" +#include "dir.h" +#include "compress.h" +#include "bitmap.h" +#include "logging.h" +#include "misc.h" + +ntfschar AT_UNNAMED[] = { const_cpu_to_le16('\0') }; + +/** + * ntfs_get_attribute_value_length - Find the length of an attribute + * @a: + * + * Description... + * + * Returns: + */ +s64 ntfs_get_attribute_value_length(const ATTR_RECORD *a) +{ + if (!a) { + errno = EINVAL; + return 0; + } + errno = 0; + if (a->non_resident) + return sle64_to_cpu(a->data_size); + + return (s64)le32_to_cpu(a->value_length); +} + +/** + * ntfs_get_attribute_value - Get a copy of an attribute + * @vol: + * @a: + * @b: + * + * Description... + * + * Returns: + */ +s64 ntfs_get_attribute_value(const ntfs_volume *vol, + const ATTR_RECORD *a, u8 *b) +{ + runlist *rl; + s64 total, r; + int i; + + /* Sanity checks. */ + if (!vol || !a || !b) { + errno = EINVAL; + return 0; + } + /* Complex attribute? */ + /* + * Ignore the flags in case they are not zero for an attribute list + * attribute. Windows does not complain about invalid flags and chkdsk + * does not detect or fix them so we need to cope with it, too. + */ + if (a->type != AT_ATTRIBUTE_LIST && a->flags) { + ntfs_log_error("Non-zero (%04x) attribute flags. Cannot handle " + "this yet.\n", le16_to_cpu(a->flags)); + errno = EOPNOTSUPP; + return 0; + } + if (!a->non_resident) { + /* Attribute is resident. */ + + /* Sanity check. */ + if (le32_to_cpu(a->value_length) + le16_to_cpu(a->value_offset) + > le32_to_cpu(a->length)) { + return 0; + } + + memcpy(b, (const char*)a + le16_to_cpu(a->value_offset), + le32_to_cpu(a->value_length)); + errno = 0; + return (s64)le32_to_cpu(a->value_length); + } + + /* Attribute is not resident. */ + + /* If no data, return 0. */ + if (!(a->data_size)) { + errno = 0; + return 0; + } + /* + * FIXME: What about attribute lists?!? (AIA) + */ + /* Decompress the mapping pairs array into a runlist. */ + rl = ntfs_mapping_pairs_decompress(vol, a, NULL); + if (!rl) { + errno = EINVAL; + return 0; + } + /* + * FIXED: We were overflowing here in a nasty fashion when we + * reach the last cluster in the runlist as the buffer will + * only be big enough to hold data_size bytes while we are + * reading in allocated_size bytes which is usually larger + * than data_size, since the actual data is unlikely to have a + * size equal to a multiple of the cluster size! + * FIXED2: We were also overflowing here in the same fashion + * when the data_size was more than one run smaller than the + * allocated size which happens with Windows XP sometimes. + */ + /* Now load all clusters in the runlist into b. */ + for (i = 0, total = 0; rl[i].length; i++) { + if (total + (rl[i].length << vol->cluster_size_bits) >= + sle64_to_cpu(a->data_size)) { + unsigned char *intbuf = NULL; + /* + * We have reached the last run so we were going to + * overflow when executing the ntfs_pread() which is + * BAAAAAAAD! + * Temporary fix: + * Allocate a new buffer with size: + * rl[i].length << vol->cluster_size_bits, do the + * read into our buffer, then memcpy the correct + * amount of data into the caller supplied buffer, + * free our buffer, and continue. + * We have reached the end of data size so we were + * going to overflow in the same fashion. + * Temporary fix: same as above. + */ + intbuf = ntfs_malloc(rl[i].length << vol->cluster_size_bits); + if (!intbuf) { + free(rl); + return 0; + } + /* + * FIXME: If compressed file: Only read if lcn != -1. + * Otherwise, we are dealing with a sparse run and we + * just memset the user buffer to 0 for the length of + * the run, which should be 16 (= compression unit + * size). + * FIXME: Really only when file is compressed, or can + * we have sparse runs in uncompressed files as well? + * - Yes we can, in sparse files! But not necessarily + * size of 16, just run length. + */ + r = ntfs_pread(vol->dev, rl[i].lcn << + vol->cluster_size_bits, rl[i].length << + vol->cluster_size_bits, intbuf); + if (r != rl[i].length << vol->cluster_size_bits) { +#define ESTR "Error reading attribute value" + if (r == -1) + ntfs_log_perror(ESTR); + else if (r < rl[i].length << + vol->cluster_size_bits) { + ntfs_log_debug(ESTR ": Ran out of input data.\n"); + errno = EIO; + } else { + ntfs_log_debug(ESTR ": unknown error\n"); + errno = EIO; + } +#undef ESTR + free(rl); + free(intbuf); + return 0; + } + memcpy(b + total, intbuf, sle64_to_cpu(a->data_size) - + total); + free(intbuf); + total = sle64_to_cpu(a->data_size); + break; + } + /* + * FIXME: If compressed file: Only read if lcn != -1. + * Otherwise, we are dealing with a sparse run and we just + * memset the user buffer to 0 for the length of the run, which + * should be 16 (= compression unit size). + * FIXME: Really only when file is compressed, or can + * we have sparse runs in uncompressed files as well? + * - Yes we can, in sparse files! But not necessarily size of + * 16, just run length. + */ + r = ntfs_pread(vol->dev, rl[i].lcn << vol->cluster_size_bits, + rl[i].length << vol->cluster_size_bits, + b + total); + if (r != rl[i].length << vol->cluster_size_bits) { +#define ESTR "Error reading attribute value" + if (r == -1) + ntfs_log_perror(ESTR); + else if (r < rl[i].length << vol->cluster_size_bits) { + ntfs_log_debug(ESTR ": Ran out of input data.\n"); + errno = EIO; + } else { + ntfs_log_debug(ESTR ": unknown error\n"); + errno = EIO; + } +#undef ESTR + free(rl); + return 0; + } + total += r; + } + free(rl); + return total; +} + +/* Already cleaned up code below, but still look for FIXME:... */ + +/** + * __ntfs_attr_init - primary initialization of an ntfs attribute structure + * @na: ntfs attribute to initialize + * @ni: ntfs inode with which to initialize the ntfs attribute + * @type: attribute type + * @name: attribute name in little endian Unicode or NULL + * @name_len: length of attribute @name in Unicode characters (if @name given) + * + * Initialize the ntfs attribute @na with @ni, @type, @name, and @name_len. + */ +static void __ntfs_attr_init(ntfs_attr *na, ntfs_inode *ni, + const ATTR_TYPES type, ntfschar *name, const u32 name_len) +{ + na->rl = NULL; + na->ni = ni; + na->type = type; + na->name = name; + if (name) + na->name_len = name_len; + else + na->name_len = 0; +} + +/** + * ntfs_attr_init - initialize an ntfs_attr with data sizes and status + * @na: + * @non_resident: + * @compressed: + * @encrypted: + * @sparse: + * @allocated_size: + * @data_size: + * @initialized_size: + * @compressed_size: + * @compression_unit: + * + * Final initialization for an ntfs attribute. + */ +void ntfs_attr_init(ntfs_attr *na, const BOOL non_resident, + const BOOL compressed, const BOOL encrypted, const BOOL sparse, + const s64 allocated_size, const s64 data_size, + const s64 initialized_size, const s64 compressed_size, + const u8 compression_unit) +{ + if (!NAttrInitialized(na)) { + if (non_resident) + NAttrSetNonResident(na); + if (compressed) + NAttrSetCompressed(na); + if (encrypted) + NAttrSetEncrypted(na); + if (sparse) + NAttrSetSparse(na); + na->allocated_size = allocated_size; + na->data_size = data_size; + na->initialized_size = initialized_size; + if (compressed || sparse) { + ntfs_volume *vol = na->ni->vol; + + na->compressed_size = compressed_size; + na->compression_block_clusters = 1 << compression_unit; + na->compression_block_size = 1 << (compression_unit + + vol->cluster_size_bits); + na->compression_block_size_bits = ffs( + na->compression_block_size) - 1; + } + NAttrSetInitialized(na); + } +} + +/** + * ntfs_attr_open - open an ntfs attribute for access + * @ni: open ntfs inode in which the ntfs attribute resides + * @type: attribute type + * @name: attribute name in little endian Unicode or AT_UNNAMED or NULL + * @name_len: length of attribute @name in Unicode characters (if @name given) + * + * Allocate a new ntfs attribute structure, initialize it with @ni, @type, + * @name, and @name_len, then return it. Return NULL on error with + * errno set to the error code. + * + * If @name is AT_UNNAMED look specifically for an unnamed attribute. If you + * do not care whether the attribute is named or not set @name to NULL. In + * both those cases @name_len is not used at all. + */ +ntfs_attr *ntfs_attr_open(ntfs_inode *ni, const ATTR_TYPES type, + ntfschar *name, u32 name_len) +{ + ntfs_attr_search_ctx *ctx; + ntfs_attr *na; + ATTR_RECORD *a; + int err; + BOOL cs; + + ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x.\n", + (unsigned long long)ni->mft_no, type); + if (!ni || !ni->vol || !ni->mrec) { + errno = EINVAL; + return NULL; + } + na = calloc(sizeof(ntfs_attr), 1); + if (!na) + return NULL; + if (name && name != AT_UNNAMED && name != NTFS_INDEX_I30) { + name = ntfs_ucsndup(name, name_len); + if (!name) { + free(na); + return NULL; + } + } + + ctx = ntfs_attr_get_search_ctx(ni, NULL); + if (!ctx) { + err = errno; + goto err_out; + } + if (ntfs_attr_lookup(type, name, name_len, 0, 0, NULL, 0, ctx)) { + err = errno; + goto put_err_out; + } + + a = ctx->attr; + /* + * Wipe the flags in case they are not zero for an attribute list + * attribute. Windows does not complain about invalid flags and chkdsk + * does not detect or fix them so we need to cope with it, too. + */ + if (type == AT_ATTRIBUTE_LIST) + a->flags = 0; + cs = a->flags & (ATTR_IS_COMPRESSED | ATTR_IS_SPARSE); + if (!name) { + if (a->name_length) { + name = ntfs_ucsndup((ntfschar*)((u8*)a + le16_to_cpu( + a->name_offset)), a->name_length); + if (!name) { + err = errno; + goto put_err_out; + } + name_len = a->name_length; + } else { + name = AT_UNNAMED; + name_len = 0; + } + } + __ntfs_attr_init(na, ni, type, name, name_len); + if (a->non_resident) { + ntfs_attr_init(na, TRUE, a->flags & ATTR_IS_COMPRESSED, + a->flags & ATTR_IS_ENCRYPTED, + a->flags & ATTR_IS_SPARSE, + sle64_to_cpu(a->allocated_size), + sle64_to_cpu(a->data_size), + sle64_to_cpu(a->initialized_size), + cs ? sle64_to_cpu(a->compressed_size) : 0, + cs ? a->compression_unit : 0); + } else { + s64 l = le32_to_cpu(a->value_length); + ntfs_attr_init(na, FALSE, a->flags & ATTR_IS_COMPRESSED, + a->flags & ATTR_IS_ENCRYPTED, + a->flags & ATTR_IS_SPARSE, (l + 7) & ~7, l, l, + cs ? (l + 7) & ~7 : 0, 0); + } + ntfs_attr_put_search_ctx(ctx); + return na; +put_err_out: + ntfs_attr_put_search_ctx(ctx); +err_out: + free(na); + errno = err; + return NULL; +} + +/** + * ntfs_attr_close - free an ntfs attribute structure + * @na: ntfs attribute structure to free + * + * Release all memory associated with the ntfs attribute @na and then release + * @na itself. + */ +void ntfs_attr_close(ntfs_attr *na) +{ + if (!na) + return; + if (NAttrNonResident(na) && na->rl) + free(na->rl); + /* Don't release if using an internal constant. */ + if (na->name != AT_UNNAMED && na->name != NTFS_INDEX_I30) + free(na->name); + free(na); +} + +/** + * ntfs_attr_map_runlist - map (a part of) a runlist of an ntfs attribute + * @na: ntfs attribute for which to map (part of) a runlist + * @vcn: map runlist part containing this vcn + * + * Map the part of a runlist containing the @vcn of an the ntfs attribute @na. + * + * Return 0 on success and -1 on error with errno set to the error code. + */ +int ntfs_attr_map_runlist(ntfs_attr *na, VCN vcn) +{ + LCN lcn; + ntfs_attr_search_ctx *ctx; + + ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x, vcn 0x%llx.\n", + (unsigned long long)na->ni->mft_no, na->type, (long long)vcn); + + lcn = ntfs_rl_vcn_to_lcn(na->rl, vcn); + if (lcn >= 0 || lcn == LCN_HOLE || lcn == LCN_ENOENT) + return 0; + + ctx = ntfs_attr_get_search_ctx(na->ni, NULL); + if (!ctx) + return -1; + + /* Find the attribute in the mft record. */ + if (!ntfs_attr_lookup(na->type, na->name, na->name_len, CASE_SENSITIVE, + vcn, NULL, 0, ctx)) { + runlist_element *rl; + + /* Decode the runlist. */ + rl = ntfs_mapping_pairs_decompress(na->ni->vol, ctx->attr, + na->rl); + if (rl) { + na->rl = rl; + ntfs_attr_put_search_ctx(ctx); + return 0; + } + } + + ntfs_attr_put_search_ctx(ctx); + return -1; +} + +/** + * ntfs_attr_map_whole_runlist - map the whole runlist of an ntfs attribute + * @na: ntfs attribute for which to map the runlist + * + * Map the whole runlist of an the ntfs attribute @na. For an attribute made + * up of only one attribute extent this is the same as calling + * ntfs_attr_map_runlist(na, 0) but for an attribute with multiple extents this + * will map the runlist fragments from each of the extents thus giving access + * to the entirety of the disk allocation of an attribute. + * + * Return 0 on success and -1 on error with errno set to the error code. + */ +int ntfs_attr_map_whole_runlist(ntfs_attr *na) +{ + VCN next_vcn, last_vcn, highest_vcn; + ntfs_attr_search_ctx *ctx; + ntfs_volume *vol = na->ni->vol; + ATTR_RECORD *a; + int err; + + ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x.\n", + (unsigned long long)na->ni->mft_no, na->type); + + ctx = ntfs_attr_get_search_ctx(na->ni, NULL); + if (!ctx) + return -1; + + /* Map all attribute extents one by one. */ + next_vcn = last_vcn = highest_vcn = 0; + a = NULL; + while (1) { + runlist_element *rl; + + int not_mapped = 0; + if (ntfs_rl_vcn_to_lcn(na->rl, next_vcn) == LCN_RL_NOT_MAPPED) + not_mapped = 1; + + if (ntfs_attr_lookup(na->type, na->name, na->name_len, + CASE_SENSITIVE, next_vcn, NULL, 0, ctx)) + break; + + a = ctx->attr; + + if (not_mapped) { + /* Decode the runlist. */ + rl = ntfs_mapping_pairs_decompress(na->ni->vol, + a, na->rl); + if (!rl) + goto err_out; + na->rl = rl; + } + + /* Are we in the first extent? */ + if (!next_vcn) { + if (a->lowest_vcn) { + errno = EIO; + ntfs_log_perror("Attribute first extent has " + "non-zero lowest_vcn"); + goto err_out; + } + /* Get the last vcn in the attribute. */ + last_vcn = sle64_to_cpu(a->allocated_size) >> + vol->cluster_size_bits; + } + + /* Get the lowest vcn for the next extent. */ + highest_vcn = sle64_to_cpu(a->highest_vcn); + next_vcn = highest_vcn + 1; + + /* Only one extent or error, which we catch below. */ + if (next_vcn <= 0) { + errno = ENOENT; + break; + } + + /* Avoid endless loops due to corruption. */ + if (next_vcn < sle64_to_cpu(a->lowest_vcn)) { + errno = EIO; + ntfs_log_perror("Inode has corrupt attribute list"); + goto err_out; + } + } + if (!a) { + ntfs_log_perror("Couldn't find attribute for runlist mapping"); + goto err_out; + } + if (highest_vcn && highest_vcn != last_vcn - 1) { + errno = EIO; + ntfs_log_perror("Couldn't load full runlist: " + "highest_vcn = 0x%llx, last_vcn = 0x%llx", + (long long)highest_vcn, (long long)last_vcn); + goto err_out; + } + err = errno; + ntfs_attr_put_search_ctx(ctx); + if (err == ENOENT) + return 0; +out_now: + errno = err; + return -1; +err_out: + err = errno; + ntfs_attr_put_search_ctx(ctx); + goto out_now; +} + +/** + * ntfs_attr_vcn_to_lcn - convert a vcn into a lcn given an ntfs attribute + * @na: ntfs attribute whose runlist to use for conversion + * @vcn: vcn to convert + * + * Convert the virtual cluster number @vcn of an attribute into a logical + * cluster number (lcn) of a device using the runlist @na->rl to map vcns to + * their corresponding lcns. + * + * If the @vcn is not mapped yet, attempt to map the attribute extent + * containing the @vcn and retry the vcn to lcn conversion. + * + * Since lcns must be >= 0, we use negative return values with special meaning: + * + * Return value Meaning / Description + * ========================================== + * -1 = LCN_HOLE Hole / not allocated on disk. + * -3 = LCN_ENOENT There is no such vcn in the attribute. + * -4 = LCN_EINVAL Input parameter error. + * -5 = LCN_EIO Corrupt fs, disk i/o error, or not enough memory. + */ +LCN ntfs_attr_vcn_to_lcn(ntfs_attr *na, const VCN vcn) +{ + LCN lcn; + BOOL is_retry = FALSE; + + if (!na || !NAttrNonResident(na) || vcn < 0) + return (LCN)LCN_EINVAL; + + ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x.\n", (unsigned long + long)na->ni->mft_no, na->type); +retry: + /* Convert vcn to lcn. If that fails map the runlist and retry once. */ + lcn = ntfs_rl_vcn_to_lcn(na->rl, vcn); + if (lcn >= 0) + return lcn; + if (!is_retry && !ntfs_attr_map_runlist(na, vcn)) { + is_retry = TRUE; + goto retry; + } + /* + * If the attempt to map the runlist failed, or we are getting + * LCN_RL_NOT_MAPPED despite having mapped the attribute extent + * successfully, something is really badly wrong... + */ + if (!is_retry || lcn == (LCN)LCN_RL_NOT_MAPPED) + return (LCN)LCN_EIO; + /* lcn contains the appropriate error code. */ + return lcn; +} + +/** + * ntfs_attr_find_vcn - find a vcn in the runlist of an ntfs attribute + * @na: ntfs attribute whose runlist to search + * @vcn: vcn to find + * + * Find the virtual cluster number @vcn in the runlist of the ntfs attribute + * @na and return the the address of the runlist element containing the @vcn. + * + * Note you need to distinguish between the lcn of the returned runlist + * element being >= 0 and LCN_HOLE. In the later case you have to return zeroes + * on read and allocate clusters on write. You need to update the runlist, the + * attribute itself as well as write the modified mft record to disk. + * + * If there is an error return NULL with errno set to the error code. The + * following error codes are defined: + * EINVAL Input parameter error. + * ENOENT There is no such vcn in the runlist. + * ENOMEM Not enough memory. + * EIO I/O error or corrupt metadata. + */ +runlist_element *ntfs_attr_find_vcn(ntfs_attr *na, const VCN vcn) +{ + runlist_element *rl; + BOOL is_retry = FALSE; + + if (!na || !NAttrNonResident(na) || vcn < 0) { + errno = EINVAL; + return NULL; + } + + ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x, vcn %llx\n", + (unsigned long long)na->ni->mft_no, na->type, + (long long)vcn); +retry: + rl = na->rl; + if (!rl) + goto map_rl; + if (vcn < rl[0].vcn) + goto map_rl; + while (rl->length) { + if (vcn < rl[1].vcn) { + if (rl->lcn >= (LCN)LCN_HOLE) + return rl; + break; + } + rl++; + } + switch (rl->lcn) { + case (LCN)LCN_RL_NOT_MAPPED: + goto map_rl; + case (LCN)LCN_ENOENT: + errno = ENOENT; + break; + case (LCN)LCN_EINVAL: + errno = EINVAL; + break; + default: + errno = EIO; + break; + } + return NULL; +map_rl: + /* The @vcn is in an unmapped region, map the runlist and retry. */ + if (!is_retry && !ntfs_attr_map_runlist(na, vcn)) { + is_retry = TRUE; + goto retry; + } + /* + * If we already retried or the mapping attempt failed something has + * gone badly wrong. EINVAL and ENOENT coming from a failed mapping + * attempt are equivalent to errors for us as they should not happen + * in our code paths. + */ + if (is_retry || errno == EINVAL || errno == ENOENT) + errno = EIO; + return NULL; +} + +/** + * ntfs_attr_pread - read from an attribute specified by an ntfs_attr structure + * @na: ntfs attribute to read from + * @pos: byte position in the attribute to begin reading from + * @count: number of bytes to read + * @b: output data buffer + * + * This function will read @count bytes starting at offset @pos from the ntfs + * attribute @na into the data buffer @b. + * + * On success, return the number of successfully read bytes. If this number is + * lower than @count this means that the read reached end of file or that an + * error was encountered during the read so that the read is partial. 0 means + * end of file or nothing was read (also return 0 when @count is 0). + * + * On error and nothing has been read, return -1 with errno set appropriately + * to the return code of ntfs_pread(), or to EINVAL in case of invalid + * arguments. + */ +s64 ntfs_attr_pread(ntfs_attr *na, const s64 pos, s64 count, void *b) +{ + s64 br, to_read, ofs, total, total2; + ntfs_volume *vol; + runlist_element *rl; + + ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x, pos 0x%llx, count " + "0x%llx.\n", (unsigned long long)na->ni->mft_no, + na->type, (long long)pos, (long long)count); + if (!na || !na->ni || !na->ni->vol || !b || pos < 0 || count < 0) { + errno = EINVAL; + return -1; + } + /* + * If this is a compressed attribute it needs special treatment, but + * only if it is non-resident. + */ + if (NAttrCompressed(na) && NAttrNonResident(na)) + return ntfs_compressed_attr_pread(na, pos, count, b); + /* + * Encrypted non-resident attributes are not supported. We return + * access denied, which is what Windows NT4 does, too. + */ + if (NAttrEncrypted(na) && NAttrNonResident(na)) { + errno = EACCES; + return -1; + } + vol = na->ni->vol; + /* Update access time if needed. */ + if (na->type == AT_DATA || na->type == AT_INDEX_ROOT || + na->type == AT_INDEX_ALLOCATION) + ntfs_inode_update_atime(na->ni); + if (!count) + return 0; + /* Truncate reads beyond end of attribute. */ + if (pos + count > na->data_size) { + if (pos >= na->data_size) + return 0; + count = na->data_size - pos; + } + /* If it is a resident attribute, get the value from the mft record. */ + if (!NAttrNonResident(na)) { + ntfs_attr_search_ctx *ctx; + char *val; + + ctx = ntfs_attr_get_search_ctx(na->ni, NULL); + if (!ctx) + return -1; + if (ntfs_attr_lookup(na->type, na->name, na->name_len, 0, + 0, NULL, 0, ctx)) { +res_err_out: + ntfs_attr_put_search_ctx(ctx); + return -1; + } + val = (char*)ctx->attr + le16_to_cpu(ctx->attr->value_offset); + if (val < (char*)ctx->attr || val + + le32_to_cpu(ctx->attr->value_length) > + (char*)ctx->mrec + vol->mft_record_size) { + errno = EIO; + goto res_err_out; + } + memcpy(b, val + pos, count); + ntfs_attr_put_search_ctx(ctx); + return count; + } + total = total2 = 0; + /* Zero out reads beyond initialized size. */ + if (pos + count > na->initialized_size) { + if (pos >= na->initialized_size) { + memset(b, 0, count); + return count; + } + total2 = pos + count - na->initialized_size; + count -= total2; + memset((u8*)b + count, 0, total2); + } + /* Find the runlist element containing the vcn. */ + rl = ntfs_attr_find_vcn(na, pos >> vol->cluster_size_bits); + if (!rl) { + /* + * If the vcn is not present it is an out of bounds read. + * However, we already truncated the read to the data_size, + * so getting this here is an error. + */ + if (errno == ENOENT) + errno = EIO; + return -1; + } + /* + * Gather the requested data into the linear destination buffer. Note, + * a partial final vcn is taken care of by the @count capping of read + * length. + */ + ofs = pos - (rl->vcn << vol->cluster_size_bits); + for (; count; rl++, ofs = 0) { + if (rl->lcn == LCN_RL_NOT_MAPPED) { + rl = ntfs_attr_find_vcn(na, rl->vcn); + if (!rl) { + if (errno == ENOENT) + errno = EIO; + goto rl_err_out; + } + /* Needed for case when runs merged. */ + ofs = pos + total - (rl->vcn << vol->cluster_size_bits); + } + if (!rl->length) + goto rl_err_out; + if (rl->lcn < (LCN)0) { + if (rl->lcn != (LCN)LCN_HOLE) + goto rl_err_out; + /* It is a hole, just zero the matching @b range. */ + to_read = min(count, (rl->length << + vol->cluster_size_bits) - ofs); + memset(b, 0, to_read); + /* Update progress counters. */ + total += to_read; + count -= to_read; + b = (u8*)b + to_read; + continue; + } + /* It is a real lcn, read it into @dst. */ + to_read = min(count, (rl->length << vol->cluster_size_bits) - + ofs); +retry: + ntfs_log_trace("Reading 0x%llx bytes from vcn 0x%llx, lcn 0x%llx, " + "ofs 0x%llx.\n", to_read, rl->vcn, rl->lcn, ofs); + br = ntfs_pread(vol->dev, (rl->lcn << vol->cluster_size_bits) + + ofs, to_read, b); + /* If everything ok, update progress counters and continue. */ + if (br > 0) { + total += br; + count -= br; + b = (u8*)b + br; + continue; + } + /* If the syscall was interrupted, try again. */ + if (br == (s64)-1 && errno == EINTR) + goto retry; + if (total) + return total; + if (!br) + errno = EIO; + return -1; + } + /* Finally, return the number of bytes read. */ + return total + total2; +rl_err_out: + if (total) + return total; + errno = EIO; + return -1; +} + +static int ntfs_attr_fill_hole(ntfs_attr *na, s64 count, s64 *ofs, + runlist_element **rl, VCN *update_from) +{ + s64 to_write; + ntfs_volume *vol = na->ni->vol; + int eo, ret = -1; + runlist *rlc; + LCN lcn_seek_from = -1; + VCN cur_vcn, from_vcn; + + to_write = min(count, ((*rl)->length << vol->cluster_size_bits) - *ofs); + + /* Instantiate the hole. */ + cur_vcn = (*rl)->vcn; + from_vcn = (*rl)->vcn + (*ofs >> vol->cluster_size_bits); + ntfs_log_trace("Instantiate the hole with vcn 0x%llx.\n", cur_vcn); + /* + * Map whole runlist to be able update mapping pairs + * later. + */ + if (ntfs_attr_map_whole_runlist(na)) + goto err_out; + /* + * Restore @*rl, it probably get lost during runlist + * mapping. + */ + *rl = ntfs_attr_find_vcn(na, cur_vcn); + if (!*rl) { + ntfs_log_error("Failed to find run after mapping runlist. " + "Please report to %s.\n", NTFS_DEV_LIST); + errno = EIO; + goto err_out; + } + /* + * Search backwards to find the best lcn to start + * seek from. + */ + rlc = *rl; + while (rlc->vcn) { + rlc--; + if (rlc->lcn >= 0) { + lcn_seek_from = rlc->lcn + (from_vcn - rlc->vcn); + break; + } + } + if (lcn_seek_from == -1) { + /* Backwards search failed, search forwards. */ + rlc = *rl; + while (rlc->length) { + rlc++; + if (rlc->lcn >= 0) { + lcn_seek_from = rlc->lcn - (rlc->vcn - from_vcn); + break; + } + } + } + /* Allocate clusters to instantiate the hole. */ + rlc = ntfs_cluster_alloc(vol, from_vcn, + ((*ofs + to_write - 1) >> vol->cluster_size_bits) + + 1 + (*rl)->vcn - from_vcn, + lcn_seek_from, DATA_ZONE); + if (!rlc) { + ntfs_log_perror("Hole filling cluster allocation failed"); + goto err_out; + } + /* Merge runlists. */ + *rl = ntfs_runlists_merge(na->rl, rlc); + if (!*rl) { + eo = errno; + ntfs_log_perror("Failed to merge runlists"); + if (ntfs_cluster_free_from_rl(vol, rlc)) { + ntfs_log_perror("Failed to free hot clusters. " + "Please run chkdsk /f"); + } + errno = eo; + goto err_out; + } + na->rl = *rl; + if (*update_from == -1) + *update_from = from_vcn; + *rl = ntfs_attr_find_vcn(na, cur_vcn); + if (!*rl) { + /* + * It's definitely a BUG, if we failed to find @cur_vcn, because + * we missed it during instantiating of the hole. + */ + ntfs_log_error("Failed to find run after hole instantiation. " + "Please report to %s.\n", NTFS_DEV_LIST); + errno = EIO; + goto err_out; + } + /* If leaved part of the hole go to the next run. */ + if ((*rl)->lcn < 0) + (*rl)++; + /* Now LCN shoudn't be less than 0. */ + if ((*rl)->lcn < 0) { + ntfs_log_error("BUG! LCN is lesser than 0. " + "Please report to the %s.\n", NTFS_DEV_LIST); + errno = EIO; + goto err_out; + } + if (*ofs) { + /* + * Need to clear region between start of + * @cur_vcn cluster and @*ofs. + */ + char *buf; + + buf = ntfs_malloc(*ofs); + if (!buf) + goto err_out; + + memset(buf, 0, *ofs); + if (ntfs_rl_pwrite(vol, na->rl, cur_vcn << vol->cluster_size_bits, + *ofs, buf) < 0) { + ntfs_log_perror("Failed to zero area"); + free(buf); + goto err_out; + } + free(buf); + } + if ((*rl)->vcn < cur_vcn) { + /* + * Clusters that replaced hole are merged with + * previous run, so we need to update offset. + */ + *ofs += (cur_vcn - (*rl)->vcn) << vol->cluster_size_bits; + } + if ((*rl)->vcn > cur_vcn) { + /* + * We left part of the hole, so we need to update offset + */ + *ofs -= ((*rl)->vcn - cur_vcn) << vol->cluster_size_bits; + } + + ret = 0; +err_out: + return ret; +} + +/** + * ntfs_attr_pwrite - positioned write to an ntfs attribute + * @na: ntfs attribute to write to + * @pos: position in the attribute to write to + * @count: number of bytes to write + * @b: data buffer to write to disk + * + * This function will write @count bytes from data buffer @b to ntfs attribute + * @na at position @pos. + * + * On success, return the number of successfully written bytes. If this number + * is lower than @count this means that an error was encountered during the + * write so that the write is partial. 0 means nothing was written (also return + * 0 when @count is 0). + * + * On error and nothing has been written, return -1 with errno set + * 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) +{ + s64 written, to_write, ofs, old_initialized_size, old_data_size; + s64 total = 0; + VCN update_from = -1; + ntfs_volume *vol; + ntfs_attr_search_ctx *ctx = NULL; + runlist_element *rl; + int eo; + struct { + unsigned int undo_initialized_size : 1; + unsigned int undo_data_size : 1; + } need_to = { 0, 0 }; + + ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x, pos 0x%llx, count " + "0x%llx.\n", na->ni->mft_no, na->type, (long long)pos, + (long long)count); + if (!na || !na->ni || !na->ni->vol || !b || pos < 0 || count < 0) { + errno = EINVAL; + goto errno_set; + } + vol = na->ni->vol; + /* + * Encrypted non-resident attributes are not supported. We return + * access denied, which is what Windows NT4 does, too. + */ + if (NAttrEncrypted(na) && NAttrNonResident(na)) { + errno = EACCES; + goto errno_set; + } + /* If this is a compressed attribute it needs special treatment. */ + if (NAttrCompressed(na)) { + // TODO: Implement writing compressed attributes! (AIA) + // return ntfs_attr_pwrite_compressed(ntfs_attr *na, + // const s64 pos, s64 count, void *b); + errno = EOPNOTSUPP; + goto errno_set; + } + /* Update access and change times if needed. */ + if (na->type == AT_DATA || na->type == AT_INDEX_ROOT || + na->type == AT_INDEX_ALLOCATION) + ntfs_inode_update_time(na->ni); + if (!count) + goto out; + /* If the write reaches beyond the end, extend the attribute. */ + old_data_size = na->data_size; + if (pos + count > na->data_size) { + if (ntfs_attr_truncate(na, pos + count)) { + ntfs_log_perror("Failed to enlarge attribute"); + goto errno_set; + } + need_to.undo_data_size = 1; + } + old_initialized_size = na->initialized_size; + /* If it is a resident attribute, write the data to the mft record. */ + if (!NAttrNonResident(na)) { + char *val; + + ctx = ntfs_attr_get_search_ctx(na->ni, NULL); + if (!ctx) + goto err_out; + if (ntfs_attr_lookup(na->type, na->name, na->name_len, 0, + 0, NULL, 0, ctx)) + goto err_out; + val = (char*)ctx->attr + le16_to_cpu(ctx->attr->value_offset); + if (val < (char*)ctx->attr || val + + le32_to_cpu(ctx->attr->value_length) > + (char*)ctx->mrec + vol->mft_record_size) { + errno = EIO; + goto err_out; + } + memcpy(val + pos, b, count); + if (ntfs_mft_record_write(vol, ctx->ntfs_ino->mft_no, + ctx->mrec)) { + /* + * NOTE: We are in a bad state at this moment. We have + * dirtied the mft record but we failed to commit it to + * disk. Since we have read the mft record ok before, + * it is unlikely to fail writing it, so is ok to just + * return error here... (AIA) + */ + goto err_out; + } + ntfs_attr_put_search_ctx(ctx); + total = count; + goto out; + } + + /* Handle writes beyond initialized_size. */ + if (pos + count > na->initialized_size) { + if (ntfs_attr_map_whole_runlist(na)) + goto err_out; + /* Set initialized_size to @pos + @count. */ + ctx = ntfs_attr_get_search_ctx(na->ni, NULL); + if (!ctx) + goto err_out; + if (ntfs_attr_lookup(na->type, na->name, na->name_len, 0, + 0, NULL, 0, ctx)) + goto err_out; + /* If write starts beyond initialized_size, zero the gap. */ + if (pos > na->initialized_size) { + char *buf; + + buf = ntfs_malloc(NTFS_BUF_SIZE); + if (!buf) + goto err_out; + + memset(buf, 0, NTFS_BUF_SIZE); + ofs = na->initialized_size; + while (ofs < pos) { + to_write = min(pos - ofs, NTFS_BUF_SIZE); + written = ntfs_rl_pwrite(vol, na->rl, ofs, + to_write, buf); + if (written <= 0) { + ntfs_log_error("Failed to zero space " + "between initialized " + "size and @pos.\n"); + free(buf); + goto err_out; + } + ofs += written; + } + free(buf); + } + ctx->attr->initialized_size = cpu_to_sle64(pos + count); + if (ntfs_mft_record_write(vol, ctx->ntfs_ino->mft_no, + ctx->mrec)) { + /* + * Undo the change in the in-memory copy and send it + * back for writing. + */ + ctx->attr->initialized_size = + cpu_to_sle64(old_initialized_size); + ntfs_mft_record_write(vol, ctx->ntfs_ino->mft_no, + ctx->mrec); + goto err_out; + } + na->initialized_size = pos + count; + ntfs_attr_put_search_ctx(ctx); + ctx = NULL; + /* + * NOTE: At this point the initialized_size in the mft record + * has been updated BUT there is random data on disk thus if + * we decide to abort, we MUST change the initialized_size + * again. + */ + need_to.undo_initialized_size = 1; + } + /* Find the runlist element containing the vcn. */ + rl = ntfs_attr_find_vcn(na, pos >> vol->cluster_size_bits); + if (!rl) { + /* + * If the vcn is not present it is an out of bounds write. + * However, we already extended the size of the attribute, + * so getting this here must be an error of some kind. + */ + if (errno == ENOENT) + errno = EIO; + goto err_out; + } + /* + * Scatter the data from the linear data buffer to the volume. Note, a + * partial final vcn is taken care of by the @count capping of write + * length. + */ + ofs = pos - (rl->vcn << vol->cluster_size_bits); + for (; count; rl++, ofs = 0) { + if (rl->lcn == LCN_RL_NOT_MAPPED) { + rl = ntfs_attr_find_vcn(na, rl->vcn); + if (!rl) { + if (errno == ENOENT) + errno = EIO; + goto rl_err_out; + } + /* Needed for case when runs merged. */ + ofs = pos + total - (rl->vcn << vol->cluster_size_bits); + } + if (!rl->length) { + errno = EIO; + goto rl_err_out; + } + if (rl->lcn < (LCN)0) { + + if (rl->lcn != (LCN)LCN_HOLE) { + errno = EIO; + goto rl_err_out; + } + + if (ntfs_attr_fill_hole(na, count, &ofs, &rl, &update_from)) + goto err_out; + } + /* It is a real lcn, write it to the volume. */ + to_write = min(count, (rl->length << vol->cluster_size_bits) - ofs); +retry: + ntfs_log_trace("Writing %lld bytes to vcn %lld, lcn %lld, ofs " + "%lld.\n", to_write, rl->vcn, rl->lcn, ofs); + if (!NVolReadOnly(vol)) + written = ntfs_pwrite(vol->dev, (rl->lcn << + vol->cluster_size_bits) + ofs, + to_write, b); + else + written = to_write; + /* If everything ok, update progress counters and continue. */ + if (written > 0) { + total += written; + count -= written; + b = (const u8*)b + written; + continue; + } + /* If the syscall was interrupted, try again. */ + if (written == (s64)-1 && errno == EINTR) + goto retry; + if (!written) + errno = EIO; + goto rl_err_out; + } +done: + if (ctx) + ntfs_attr_put_search_ctx(ctx); + /* Update mapping pairs if needed. */ + if (update_from != -1) + ntfs_attr_update_mapping_pairs(na, 0 /*update_from*/); +out: + return total; +rl_err_out: + eo = errno; + if (total) { + if (need_to.undo_initialized_size) { + if (pos + total > na->initialized_size) + goto done; + /* + * TODO: Need to try to change initialized_size. If it + * succeeds goto done, otherwise goto err_out. (AIA) + */ + errno = EOPNOTSUPP; + goto err_out; + } + goto done; + } + errno = eo; +err_out: + eo = errno; + if (need_to.undo_initialized_size) { + int err; + + err = 0; + if (!ctx) { + ctx = ntfs_attr_get_search_ctx(na->ni, NULL); + if (!ctx) + err = 1; + } else + ntfs_attr_reinit_search_ctx(ctx); + if (!err) { + err = ntfs_attr_lookup(na->type, na->name, + na->name_len, 0, 0, NULL, 0, ctx); + if (!err) { + na->initialized_size = old_initialized_size; + ctx->attr->initialized_size = cpu_to_sle64( + old_initialized_size); + err = ntfs_mft_record_write(vol, + ctx->ntfs_ino->mft_no, + ctx->mrec); + } + } + if (err) { + /* + * FIXME: At this stage could try to recover by filling + * old_initialized_size -> new_initialized_size with + * data or at least zeroes. (AIA) + */ + ntfs_log_error("Eeek! Failed to recover from error. " + "Leaving metadata in inconsistent " + "state! Run chkdsk!\n"); + } + } + if (ctx) + ntfs_attr_put_search_ctx(ctx); + /* Update mapping pairs if needed. */ + if (update_from != -1) + ntfs_attr_update_mapping_pairs(na, 0 /*update_from*/); + /* Restore original data_size if needed. */ + if (need_to.undo_data_size && ntfs_attr_truncate(na, old_data_size)) + ntfs_log_perror("Failed to restore data_size"); + errno = eo; +errno_set: + total = -1; + goto out; +} + +/** + * ntfs_attr_mst_pread - multi sector transfer protected ntfs attribute read + * @na: multi sector transfer protected ntfs attribute to read from + * @pos: byte position in the attribute to begin reading from + * @bk_cnt: number of mst protected blocks to read + * @bk_size: size of each mst protected block in bytes + * @dst: output data buffer + * + * This function will read @bk_cnt blocks of size @bk_size bytes each starting + * at offset @pos from the ntfs attribute @na into the data buffer @b. + * + * On success, the multi sector transfer fixups are applied and the number of + * read blocks is returned. If this number is lower than @bk_cnt this means + * that the read has either reached end of attribute or that an error was + * encountered during the read so that the read is partial. 0 means end of + * attribute or nothing to read (also return 0 when @bk_cnt or @bk_size are 0). + * + * On error and nothing has been read, return -1 with errno set appropriately + * to the return code of ntfs_attr_pread() or to EINVAL in case of invalid + * arguments. + * + * NOTE: If an incomplete multi sector transfer is detected the magic is + * changed to BAAD but no error is returned, i.e. it is possible that any of + * the returned blocks have multi sector transfer errors. This should be + * detected by the caller by checking each block with is_baad_recordp(&block). + * The reasoning is that we want to fixup as many blocks as possible and we + * want to return even bad ones to the caller so, e.g. in case of ntfsck, the + * errors can be repaired. + */ +s64 ntfs_attr_mst_pread(ntfs_attr *na, const s64 pos, const s64 bk_cnt, + const u32 bk_size, void *dst) +{ + s64 br; + u8 *end; + + ntfs_log_trace("Entering for inode 0x%llx, attr type 0x%x, pos 0x%llx.\n", + (unsigned long long)na->ni->mft_no, na->type, + (long long)pos); + if (bk_cnt < 0 || bk_size % NTFS_BLOCK_SIZE) { + errno = EINVAL; + return -1; + } + br = ntfs_attr_pread(na, pos, bk_cnt * bk_size, dst); + if (br <= 0) + return br; + br /= bk_size; + for (end = (u8*)dst + br * bk_size; (u8*)dst < end; dst = (u8*)dst + + bk_size) + ntfs_mst_post_read_fixup((NTFS_RECORD*)dst, bk_size); + /* Finally, return the number of blocks read. */ + return br; +} + +/** + * ntfs_attr_mst_pwrite - multi sector transfer protected ntfs attribute write + * @na: multi sector transfer protected ntfs attribute to write to + * @pos: position in the attribute to write to + * @bk_cnt: number of mst protected blocks to write + * @bk_size: size of each mst protected block in bytes + * @src: data buffer to write to disk + * + * This function will write @bk_cnt blocks of size @bk_size bytes each from + * data buffer @b to multi sector transfer (mst) protected ntfs attribute @na + * at position @pos. + * + * On success, return the number of successfully written blocks. If this number + * is lower than @bk_cnt this means that an error was encountered during the + * write so that the write is partial. 0 means nothing was written (also + * return 0 when @bk_cnt or @bk_size are 0). + * + * On error and nothing has been written, return -1 with errno set + * appropriately to the return code of ntfs_attr_pwrite(), or to EINVAL in case + * of invalid arguments. + * + * NOTE: We mst protect the data, write it, then mst deprotect it using a quick + * deprotect algorithm (no checking). This saves us from making a copy before + * the write and at the same time causes the usn to be incremented in the + * buffer. This conceptually fits in better with the idea that cached data is + * always deprotected and protection is performed when the data is actually + * going to hit the disk and the cache is immediately deprotected again + * simulating an mst read on the written data. This way cache coherency is + * achieved. + */ +s64 ntfs_attr_mst_pwrite(ntfs_attr *na, const s64 pos, s64 bk_cnt, + const u32 bk_size, void *src) +{ + s64 written, i; + + ntfs_log_trace("Entering for inode 0x%llx, attr type 0x%x, pos 0x%llx.\n", + (unsigned long long)na->ni->mft_no, na->type, + (long long)pos); + if (bk_cnt < 0 || bk_size % NTFS_BLOCK_SIZE) { + errno = EINVAL; + return -1; + } + if (!bk_cnt) + return 0; + /* Prepare data for writing. */ + for (i = 0; i < bk_cnt; ++i) { + int err; + + err = ntfs_mst_pre_write_fixup((NTFS_RECORD*) + ((u8*)src + i * bk_size), bk_size); + if (err < 0) { + /* Abort write at this position. */ + if (!i) + return err; + bk_cnt = i; + break; + } + } + /* Write the prepared data. */ + written = ntfs_attr_pwrite(na, pos, bk_cnt * bk_size, src); + /* Quickly deprotect the data again. */ + for (i = 0; i < bk_cnt; ++i) + ntfs_mst_post_write_fixup((NTFS_RECORD*)((u8*)src + i * + bk_size)); + if (written <= 0) + return written; + /* Finally, return the number of complete blocks written. */ + return written / bk_size; +} + +/** + * ntfs_attr_find - find (next) attribute in mft record + * @type: attribute type to find + * @name: attribute name to find (optional, i.e. NULL means don't care) + * @name_len: attribute name length (only needed if @name present) + * @ic: IGNORE_CASE or CASE_SENSITIVE (ignored if @name not present) + * @val: attribute value to find (optional, resident attributes only) + * @val_len: attribute value length + * @ctx: search context with mft record and attribute to search from + * + * You shouldn't need to call this function directly. Use lookup_attr() instead. + * + * ntfs_attr_find() takes a search context @ctx as parameter and searches the + * mft record specified by @ctx->mrec, beginning at @ctx->attr, for an + * attribute of @type, optionally @name and @val. If found, ntfs_attr_find() + * returns 0 and @ctx->attr will point to the found attribute. + * + * If not found, ntfs_attr_find() returns -1, with errno set to ENOENT and + * @ctx->attr will point to the attribute before which the attribute being + * searched for would need to be inserted if such an action were to be desired. + * + * On actual error, ntfs_attr_find() returns -1 with errno set to the error + * code but not to ENOENT. In this case @ctx->attr is undefined and in + * particular do not rely on it not changing. + * + * If @ctx->is_first is TRUE, the search begins with @ctx->attr itself. If it + * is FALSE, the search begins after @ctx->attr. + * + * If @type is AT_UNUSED, return the first found attribute, i.e. one can + * enumerate all attributes by setting @type to AT_UNUSED and then calling + * ntfs_attr_find() repeatedly until it returns -1 with errno set to ENOENT to + * indicate that there are no more entries. During the enumeration, each + * successful call of ntfs_attr_find() will return the next attribute in the + * mft record @ctx->mrec. + * + * If @type is AT_END, seek to the end and return -1 with errno set to ENOENT. + * AT_END is not a valid attribute, its length is zero for example, thus it is + * safer to return error instead of success in this case. This also allows us + * to interoperate cleanly with ntfs_external_attr_find(). + * + * If @name is AT_UNNAMED search for an unnamed attribute. If @name is present + * but not AT_UNNAMED search for a named attribute matching @name. Otherwise, + * match both named and unnamed attributes. + * + * If @ic is IGNORE_CASE, the @name comparison is not case sensitive and + * @ctx->ntfs_ino must be set to the ntfs inode to which the mft record + * @ctx->mrec belongs. This is so we can get at the ntfs volume and hence at + * the upcase table. If @ic is CASE_SENSITIVE, the comparison is case + * sensitive. When @name is present, @name_len is the @name length in Unicode + * characters. + * + * If @name is not present (NULL), we assume that the unnamed attribute is + * being searched for. + * + * Finally, the resident attribute value @val is looked for, if present. + * If @val is not present (NULL), @val_len is ignored. + * + * ntfs_attr_find() only searches the specified mft record and it ignores the + * presence of an attribute list attribute (unless it is the one being searched + * for, obviously). If you need to take attribute lists into consideration, use + * ntfs_attr_lookup() instead (see below). This also means that you cannot use + * ntfs_attr_find() to search for extent records of non-resident attributes, as + * extents with lowest_vcn != 0 are usually described by the attribute list + * attribute only. - Note that it is possible that the first extent is only in + * the attribute list while the last extent is in the base mft record, so don't + * rely on being able to find the first extent in the base mft record. + * + * Warning: Never use @val when looking for attribute types which can be + * non-resident as this most likely will result in a crash! + */ +static int ntfs_attr_find(const ATTR_TYPES type, const ntfschar *name, + const u32 name_len, const IGNORE_CASE_BOOL ic, + const u8 *val, const u32 val_len, ntfs_attr_search_ctx *ctx) +{ + ATTR_RECORD *a; + ntfs_volume *vol; + ntfschar *upcase; + u32 upcase_len; + + ntfs_log_trace("attribute type 0x%x.\n", type); + + if (ctx->ntfs_ino) { + vol = ctx->ntfs_ino->vol; + upcase = vol->upcase; + upcase_len = vol->upcase_len; + } else { + if (name && name != AT_UNNAMED) { + errno = EINVAL; + return -1; + } + vol = NULL; + upcase = NULL; + upcase_len = 0; + } + /* + * Iterate over attributes in mft record starting at @ctx->attr, or the + * attribute following that, if @ctx->is_first is TRUE. + */ + if (ctx->is_first) { + a = ctx->attr; + ctx->is_first = FALSE; + } else + a = (ATTR_RECORD*)((char*)ctx->attr + + le32_to_cpu(ctx->attr->length)); + for (;; a = (ATTR_RECORD*)((char*)a + le32_to_cpu(a->length))) { + if (p2n(a) < p2n(ctx->mrec) || (char*)a > (char*)ctx->mrec + + le32_to_cpu(ctx->mrec->bytes_allocated)) + break; + ctx->attr = a; + if (((type != AT_UNUSED) && (le32_to_cpu(a->type) > + le32_to_cpu(type))) || + (a->type == AT_END)) { + errno = ENOENT; + return -1; + } + if (!a->length) + break; + /* If this is an enumeration return this attribute. */ + if (type == AT_UNUSED) + return 0; + if (a->type != type) + continue; + /* + * If @name is AT_UNNAMED we want an unnamed attribute. + * If @name is present, compare the two names. + * Otherwise, match any attribute. + */ + if (name == AT_UNNAMED) { + /* The search failed if the found attribute is named. */ + if (a->name_length) { + errno = ENOENT; + return -1; + } + } else if (name && !ntfs_names_are_equal(name, name_len, + (ntfschar*)((char*)a + le16_to_cpu(a->name_offset)), + a->name_length, ic, upcase, upcase_len)) { + register int rc; + + rc = ntfs_names_collate(name, name_len, + (ntfschar*)((char*)a + + le16_to_cpu(a->name_offset)), + a->name_length, 1, IGNORE_CASE, + upcase, upcase_len); + /* + * If @name collates before a->name, there is no + * matching attribute. + */ + if (rc == -1) { + errno = ENOENT; + return -1; + } + /* If the strings are not equal, continue search. */ + if (rc) + continue; + rc = ntfs_names_collate(name, name_len, + (ntfschar*)((char*)a + + le16_to_cpu(a->name_offset)), + a->name_length, 1, CASE_SENSITIVE, + upcase, upcase_len); + if (rc == -1) { + errno = ENOENT; + return -1; + } + if (rc) + continue; + } + /* + * The names match or @name not present and attribute is + * unnamed. If no @val specified, we have found the attribute + * and are done. + */ + if (!val) + return 0; + /* @val is present; compare values. */ + else { + register int rc; + + rc = memcmp(val, (char*)a +le16_to_cpu(a->value_offset), + min(val_len, + le32_to_cpu(a->value_length))); + /* + * If @val collates before the current attribute's + * value, there is no matching attribute. + */ + if (!rc) { + register u32 avl; + avl = le32_to_cpu(a->value_length); + if (val_len == avl) + return 0; + if (val_len < avl) { + errno = ENOENT; + return -1; + } + } else if (rc < 0) { + errno = ENOENT; + return -1; + } + } + } + ntfs_log_debug("ntfs_attr_find(): File is corrupt. Run chkdsk.\n"); + errno = EIO; + return -1; +} + +void ntfs_attr_name_free(char **name) +{ + if (*name) { + free(*name); + *name = NULL; + } +} + +char *ntfs_attr_name_get(const ntfschar *uname, const int uname_len) +{ + char *name = NULL; + int name_len; + + name_len = ntfs_ucstombs(uname, uname_len, &name, 0); + if (name_len < 0) { + ntfs_log_perror("ntfs_ucstombs"); + return NULL; + + } else if (name_len > 0) + return name; + + ntfs_attr_name_free(&name); + return NULL; +} + +/** + * ntfs_external_attr_find - find an attribute in the attribute list of an inode + * @type: attribute type to find + * @name: attribute name to find (optional, i.e. NULL means don't care) + * @name_len: attribute name length (only needed if @name present) + * @ic: IGNORE_CASE or CASE_SENSITIVE (ignored if @name not present) + * @lowest_vcn: lowest vcn to find (optional, non-resident attributes only) + * @val: attribute value to find (optional, resident attributes only) + * @val_len: attribute value length + * @ctx: search context with mft record and attribute to search from + * + * You shouldn't need to call this function directly. Use ntfs_attr_lookup() + * instead. + * + * Find an attribute by searching the attribute list for the corresponding + * attribute list entry. Having found the entry, map the mft record for read + * if the attribute is in a different mft record/inode, find the attribute in + * there and return it. + * + * If @type is AT_UNUSED, return the first found attribute, i.e. one can + * enumerate all attributes by setting @type to AT_UNUSED and then calling + * ntfs_external_attr_find() repeatedly until it returns -1 with errno set to + * ENOENT to indicate that there are no more entries. During the enumeration, + * each successful call of ntfs_external_attr_find() will return the next + * attribute described by the attribute list of the base mft record described + * by the search context @ctx. + * + * If @type is AT_END, seek to the end of the base mft record ignoring the + * attribute list completely and return -1 with errno set to ENOENT. AT_END is + * not a valid attribute, its length is zero for example, thus it is safer to + * return error instead of success in this case. + * + * If @name is AT_UNNAMED search for an unnamed attribute. If @name is present + * but not AT_UNNAMED search for a named attribute matching @name. Otherwise, + * match both named and unnamed attributes. + * + * On first search @ctx->ntfs_ino must be the inode of the base mft record and + * @ctx must have been obtained from a call to ntfs_attr_get_search_ctx(). + * On subsequent calls, @ctx->ntfs_ino can be any extent inode, too + * (@ctx->base_ntfs_ino is then the base inode). + * + * After finishing with the attribute/mft record you need to call + * ntfs_attr_put_search_ctx() to cleanup the search context (unmapping any + * mapped extent inodes, etc). + * + * Return 0 if the search was successful and -1 if not, with errno set to the + * error code. + * + * On success, @ctx->attr is the found attribute, it is in mft record + * @ctx->mrec, and @ctx->al_entry is the attribute list entry for this + * attribute with @ctx->base_* being the base mft record to which @ctx->attr + * belongs. + * + * On error ENOENT, i.e. attribute not found, @ctx->attr is set to the + * attribute which collates just after the attribute being searched for in the + * base ntfs inode, i.e. if one wants to add the attribute to the mft record + * this is the correct place to insert it into, and if there is not enough + * space, the attribute should be placed in an extent mft record. + * @ctx->al_entry points to the position within @ctx->base_ntfs_ino->attr_list + * at which the new attribute's attribute list entry should be inserted. The + * other @ctx fields, base_ntfs_ino, base_mrec, and base_attr are set to NULL. + * The only exception to this is when @type is AT_END, in which case + * @ctx->al_entry is set to NULL also (see above). + * + * The following error codes are defined: + * ENOENT Attribute not found, not an error as such. + * EINVAL Invalid arguments. + * EIO I/O error or corrupt data structures found. + * ENOMEM Not enough memory to allocate necessary buffers. + */ +static int ntfs_external_attr_find(ATTR_TYPES type, const ntfschar *name, + const u32 name_len, const IGNORE_CASE_BOOL ic, + const VCN lowest_vcn, const u8 *val, const u32 val_len, + ntfs_attr_search_ctx *ctx) +{ + ntfs_inode *base_ni, *ni; + ntfs_volume *vol; + ATTR_LIST_ENTRY *al_entry, *next_al_entry; + u8 *al_start, *al_end; + ATTR_RECORD *a; + ntfschar *al_name; + u32 al_name_len; + BOOL is_first_search = FALSE; + + ni = ctx->ntfs_ino; + base_ni = ctx->base_ntfs_ino; + ntfs_log_trace("Entering for inode 0x%llx, attribute type 0x%x.\n", + (unsigned long long)ni->mft_no, type); + if (!base_ni) { + /* First call happens with the base mft record. */ + base_ni = ctx->base_ntfs_ino = ctx->ntfs_ino; + ctx->base_mrec = ctx->mrec; + } + if (ni == base_ni) + ctx->base_attr = ctx->attr; + if (type == AT_END) + goto not_found; + vol = base_ni->vol; + al_start = base_ni->attr_list; + al_end = al_start + base_ni->attr_list_size; + if (!ctx->al_entry) { + ctx->al_entry = (ATTR_LIST_ENTRY*)al_start; + is_first_search = TRUE; + } + /* + * Iterate over entries in attribute list starting at @ctx->al_entry, + * or the entry following that, if @ctx->is_first is TRUE. + */ + if (ctx->is_first) { + al_entry = ctx->al_entry; + ctx->is_first = FALSE; + /* + * If an enumeration and the first attribute is higher than + * the attribute list itself, need to return the attribute list + * attribute. + */ + if ((type == AT_UNUSED) && is_first_search && + le32_to_cpu(al_entry->type) > + le32_to_cpu(AT_ATTRIBUTE_LIST)) + goto find_attr_list_attr; + } else { + al_entry = (ATTR_LIST_ENTRY*)((char*)ctx->al_entry + + le16_to_cpu(ctx->al_entry->length)); + /* + * If this is an enumeration and the attribute list attribute + * is the next one in the enumeration sequence, just return the + * attribute list attribute from the base mft record as it is + * not listed in the attribute list itself. + */ + if ((type == AT_UNUSED) && le32_to_cpu(ctx->al_entry->type) < + le32_to_cpu(AT_ATTRIBUTE_LIST) && + le32_to_cpu(al_entry->type) > + le32_to_cpu(AT_ATTRIBUTE_LIST)) { + int rc; +find_attr_list_attr: + + /* Check for bogus calls. */ + if (name || name_len || val || val_len || lowest_vcn) { + errno = EINVAL; + return -1; + } + + /* We want the base record. */ + ctx->ntfs_ino = base_ni; + ctx->mrec = ctx->base_mrec; + ctx->is_first = TRUE; + /* Sanity checks are performed elsewhere. */ + ctx->attr = (ATTR_RECORD*)((u8*)ctx->mrec + + le16_to_cpu(ctx->mrec->attrs_offset)); + + /* Find the attribute list attribute. */ + rc = ntfs_attr_find(AT_ATTRIBUTE_LIST, NULL, 0, + IGNORE_CASE, NULL, 0, ctx); + + /* + * Setup the search context so the correct + * attribute is returned next time round. + */ + ctx->al_entry = al_entry; + ctx->is_first = TRUE; + + /* Got it. Done. */ + if (!rc) + return 0; + + /* Error! If other than not found return it. */ + if (errno != ENOENT) + return rc; + + /* Not found?!? Absurd! Must be a bug... )-: */ + ntfs_log_error("Extant attribute list wasn't found\n"); + errno = EINVAL; + return -1; + } + } + for (;; al_entry = next_al_entry) { + /* Out of bounds check. */ + if ((u8*)al_entry < base_ni->attr_list || + (u8*)al_entry > al_end) + break; /* Inode is corrupt. */ + ctx->al_entry = al_entry; + /* Catch the end of the attribute list. */ + if ((u8*)al_entry == al_end) + goto not_found; + if (!al_entry->length) + break; + if ((u8*)al_entry + 6 > al_end || (u8*)al_entry + + le16_to_cpu(al_entry->length) > al_end) + break; + next_al_entry = (ATTR_LIST_ENTRY*)((u8*)al_entry + + le16_to_cpu(al_entry->length)); + if (type != AT_UNUSED) { + if (le32_to_cpu(al_entry->type) > le32_to_cpu(type)) + goto not_found; + if (type != al_entry->type) + continue; + } + al_name_len = al_entry->name_length; + al_name = (ntfschar*)((u8*)al_entry + al_entry->name_offset); + /* + * If !@type we want the attribute represented by this + * attribute list entry. + */ + if (type == AT_UNUSED) + goto is_enumeration; + /* + * If @name is AT_UNNAMED we want an unnamed attribute. + * If @name is present, compare the two names. + * Otherwise, match any attribute. + */ + if (name == AT_UNNAMED) { + if (al_name_len) + goto not_found; + } else if (name && !ntfs_names_are_equal(al_name, al_name_len, + name, name_len, ic, vol->upcase, + vol->upcase_len)) { + register int rc; + + rc = ntfs_names_collate(name, name_len, al_name, + al_name_len, 1, IGNORE_CASE, + vol->upcase, vol->upcase_len); + /* + * If @name collates before al_name, there is no + * matching attribute. + */ + if (rc == -1) + goto not_found; + /* If the strings are not equal, continue search. */ + if (rc) + continue; + /* + * FIXME: Reverse engineering showed 0, IGNORE_CASE but + * that is inconsistent with ntfs_attr_find(). The + * subsequent rc checks were also different. Perhaps I + * made a mistake in one of the two. Need to recheck + * which is correct or at least see what is going + * on... (AIA) + */ + rc = ntfs_names_collate(name, name_len, al_name, + al_name_len, 1, CASE_SENSITIVE, + vol->upcase, vol->upcase_len); + if (rc == -1) + goto not_found; + if (rc) + continue; + } + /* + * The names match or @name not present and attribute is + * unnamed. Now check @lowest_vcn. Continue search if the + * next attribute list entry still fits @lowest_vcn. Otherwise + * we have reached the right one or the search has failed. + */ + if (lowest_vcn && (u8*)next_al_entry >= al_start && + (u8*)next_al_entry + 6 < al_end && + (u8*)next_al_entry + le16_to_cpu( + next_al_entry->length) <= al_end && + sle64_to_cpu(next_al_entry->lowest_vcn) <= + lowest_vcn && + next_al_entry->type == al_entry->type && + next_al_entry->name_length == al_name_len && + ntfs_names_are_equal((ntfschar*)((char*) + next_al_entry + + next_al_entry->name_offset), + next_al_entry->name_length, + al_name, al_name_len, CASE_SENSITIVE, + vol->upcase, vol->upcase_len)) + continue; +is_enumeration: + if (MREF_LE(al_entry->mft_reference) == ni->mft_no) { + if (MSEQNO_LE(al_entry->mft_reference) != + le16_to_cpu( + ni->mrec->sequence_number)) { + ntfs_log_debug("Found stale mft reference in " + "attribute list!\n"); + break; + } + } else { /* Mft references do not match. */ + /* Do we want the base record back? */ + if (MREF_LE(al_entry->mft_reference) == + base_ni->mft_no) { + ni = ctx->ntfs_ino = base_ni; + ctx->mrec = ctx->base_mrec; + } else { + /* We want an extent record. */ + ni = ntfs_extent_inode_open(base_ni, + al_entry->mft_reference); + if (!ni) { + ntfs_log_perror("Failed to map extent inode"); + break; + } + ctx->ntfs_ino = ni; + ctx->mrec = ni->mrec; + } + } + a = ctx->attr = (ATTR_RECORD*)((char*)ctx->mrec + + le16_to_cpu(ctx->mrec->attrs_offset)); + /* + * ctx->ntfs_ino, ctx->mrec, and ctx->attr now point to the + * mft record containing the attribute represented by the + * current al_entry. + * + * We could call into ntfs_attr_find() to find the right + * attribute in this mft record but this would be less + * efficient and not quite accurate as ntfs_attr_find() ignores + * the attribute instance numbers for example which become + * important when one plays with attribute lists. Also, because + * a proper match has been found in the attribute list entry + * above, the comparison can now be optimized. So it is worth + * re-implementing a simplified ntfs_attr_find() here. + * + * Use a manual loop so we can still use break and continue + * with the same meanings as above. + */ +do_next_attr_loop: + if ((char*)a < (char*)ctx->mrec || (char*)a > (char*)ctx->mrec + + le32_to_cpu(ctx->mrec->bytes_allocated)) + break; + if (a->type == AT_END) + continue; + if (!a->length) + break; + if (al_entry->instance != a->instance) + goto do_next_attr; + /* + * If the type and/or the name are/is mismatched between the + * attribute list entry and the attribute record, there is + * corruption so we break and return error EIO. + */ + if (al_entry->type != a->type) + break; + if (!ntfs_names_are_equal((ntfschar*)((char*)a + + le16_to_cpu(a->name_offset)), + a->name_length, al_name, + al_name_len, CASE_SENSITIVE, + vol->upcase, vol->upcase_len)) + break; + ctx->attr = a; + /* + * If no @val specified or @val specified and it matches, we + * have found it! Also, if !@type, it is an enumeration, so we + * want the current attribute. + */ + if ((type == AT_UNUSED) || !val || (!a->non_resident && + le32_to_cpu(a->value_length) == val_len && + !memcmp((char*)a + le16_to_cpu(a->value_offset), + val, val_len))) { + return 0; + } +do_next_attr: + /* Proceed to the next attribute in the current mft record. */ + a = (ATTR_RECORD*)((char*)a + le32_to_cpu(a->length)); + goto do_next_attr_loop; + } + if (ni != base_ni) { + ctx->ntfs_ino = base_ni; + ctx->mrec = ctx->base_mrec; + ctx->attr = ctx->base_attr; + } + ntfs_log_debug("Inode is corrupt.\n"); + errno = EIO; + return -1; +not_found: + /* + * If we were looking for AT_END or we were enumerating and reached the + * end, we reset the search context @ctx and use ntfs_attr_find() to + * seek to the end of the base mft record. + */ + if (type == AT_UNUSED || type == AT_END) { + ntfs_attr_reinit_search_ctx(ctx); + return ntfs_attr_find(AT_END, name, name_len, ic, val, val_len, + ctx); + } + /* + * The attribute wasn't found. Before we return, we want to ensure + * @ctx->mrec and @ctx->attr indicate the position at which the + * attribute should be inserted in the base mft record. Since we also + * want to preserve @ctx->al_entry we cannot reinitialize the search + * context using ntfs_attr_reinit_search_ctx() as this would set + * @ctx->al_entry to NULL. Thus we do the necessary bits manually (see + * ntfs_attr_init_search_ctx() below). Note, we _only_ preserve + * @ctx->al_entry as the remaining fields (base_*) are identical to + * their non base_ counterparts and we cannot set @ctx->base_attr + * correctly yet as we do not know what @ctx->attr will be set to by + * the call to ntfs_attr_find() below. + */ + ctx->mrec = ctx->base_mrec; + ctx->attr = (ATTR_RECORD*)((u8*)ctx->mrec + + le16_to_cpu(ctx->mrec->attrs_offset)); + ctx->is_first = TRUE; + ctx->ntfs_ino = ctx->base_ntfs_ino; + ctx->base_ntfs_ino = NULL; + ctx->base_mrec = NULL; + ctx->base_attr = NULL; + /* + * In case there are multiple matches in the base mft record, need to + * keep enumerating until we get an attribute not found response (or + * another error), otherwise we would keep returning the same attribute + * over and over again and all programs using us for enumeration would + * lock up in a tight loop. + */ + { + int ret; + + do { + ret = ntfs_attr_find(type, name, name_len, ic, val, + val_len, ctx); + } while (!ret); + return ret; + } +} + +/** + * ntfs_attr_lookup - find an attribute in an ntfs inode + * @type: attribute type to find + * @name: attribute name to find (optional, i.e. NULL means don't care) + * @name_len: attribute name length (only needed if @name present) + * @ic: IGNORE_CASE or CASE_SENSITIVE (ignored if @name not present) + * @lowest_vcn: lowest vcn to find (optional, non-resident attributes only) + * @val: attribute value to find (optional, resident attributes only) + * @val_len: attribute value length + * @ctx: search context with mft record and attribute to search from + * + * Find an attribute in an ntfs inode. On first search @ctx->ntfs_ino must + * be the base mft record and @ctx must have been obtained from a call to + * ntfs_attr_get_search_ctx(). + * + * This function transparently handles attribute lists and @ctx is used to + * continue searches where they were left off at. + * + * If @type is AT_UNUSED, return the first found attribute, i.e. one can + * enumerate all attributes by setting @type to AT_UNUSED and then calling + * ntfs_attr_lookup() repeatedly until it returns -1 with errno set to ENOENT + * to indicate that there are no more entries. During the enumeration, each + * successful call of ntfs_attr_lookup() will return the next attribute, with + * the current attribute being described by the search context @ctx. + * + * If @type is AT_END, seek to the end of the base mft record ignoring the + * attribute list completely and return -1 with errno set to ENOENT. AT_END is + * not a valid attribute, its length is zero for example, thus it is safer to + * return error instead of success in this case. It should never be needed to + * do this, but we implement the functionality because it allows for simpler + * code inside ntfs_external_attr_find(). + * + * If @name is AT_UNNAMED search for an unnamed attribute. If @name is present + * but not AT_UNNAMED search for a named attribute matching @name. Otherwise, + * match both named and unnamed attributes. + * + * After finishing with the attribute/mft record you need to call + * ntfs_attr_put_search_ctx() to cleanup the search context (unmapping any + * mapped extent inodes, etc). + * + * Return 0 if the search was successful and -1 if not, with errno set to the + * error code. + * + * On success, @ctx->attr is the found attribute, it is in mft record + * @ctx->mrec, and @ctx->al_entry is the attribute list entry for this + * attribute with @ctx->base_* being the base mft record to which @ctx->attr + * belongs. If no attribute list attribute is present @ctx->al_entry and + * @ctx->base_* are NULL. + * + * On error ENOENT, i.e. attribute not found, @ctx->attr is set to the + * attribute which collates just after the attribute being searched for in the + * base ntfs inode, i.e. if one wants to add the attribute to the mft record + * this is the correct place to insert it into, and if there is not enough + * space, the attribute should be placed in an extent mft record. + * @ctx->al_entry points to the position within @ctx->base_ntfs_ino->attr_list + * at which the new attribute's attribute list entry should be inserted. The + * other @ctx fields, base_ntfs_ino, base_mrec, and base_attr are set to NULL. + * The only exception to this is when @type is AT_END, in which case + * @ctx->al_entry is set to NULL also (see above). + * + * + * The following error codes are defined: + * ENOENT Attribute not found, not an error as such. + * EINVAL Invalid arguments. + * EIO I/O error or corrupt data structures found. + * ENOMEM Not enough memory to allocate necessary buffers. + */ +int ntfs_attr_lookup(const ATTR_TYPES type, const ntfschar *name, + const u32 name_len, const IGNORE_CASE_BOOL ic, + const VCN lowest_vcn, const u8 *val, const u32 val_len, + ntfs_attr_search_ctx *ctx) +{ + ntfs_volume *vol; + ntfs_inode *base_ni; + + if (!ctx || !ctx->mrec || !ctx->attr || (name && name != AT_UNNAMED && + (!ctx->ntfs_ino || !(vol = ctx->ntfs_ino->vol) || + !vol->upcase || !vol->upcase_len))) { + errno = EINVAL; + return -1; + } + if (ctx->base_ntfs_ino) + base_ni = ctx->base_ntfs_ino; + else + base_ni = ctx->ntfs_ino; + if (!base_ni || !NInoAttrList(base_ni) || type == AT_ATTRIBUTE_LIST) + return ntfs_attr_find(type, name, name_len, ic, val, val_len, + ctx); + return ntfs_external_attr_find(type, name, name_len, ic, lowest_vcn, + val, val_len, ctx); +} + +/** + * ntfs_attr_init_search_ctx - initialize an attribute search context + * @ctx: attribute search context to initialize + * @ni: ntfs inode with which to initialize the search context + * @mrec: mft record with which to initialize the search context + * + * Initialize the attribute search context @ctx with @ni and @mrec. + */ +static void ntfs_attr_init_search_ctx(ntfs_attr_search_ctx *ctx, + ntfs_inode *ni, MFT_RECORD *mrec) +{ + if (!mrec) + mrec = ni->mrec; + ctx->mrec = mrec; + /* Sanity checks are performed elsewhere. */ + ctx->attr = (ATTR_RECORD*)((u8*)mrec + le16_to_cpu(mrec->attrs_offset)); + ctx->is_first = TRUE; + ctx->ntfs_ino = ni; + ctx->al_entry = NULL; + ctx->base_ntfs_ino = NULL; + ctx->base_mrec = NULL; + ctx->base_attr = NULL; +} + +/** + * ntfs_attr_reinit_search_ctx - reinitialize an attribute search context + * @ctx: attribute search context to reinitialize + * + * Reinitialize the attribute search context @ctx. + * + * This is used when a search for a new attribute is being started to reset + * the search context to the beginning. + */ +void ntfs_attr_reinit_search_ctx(ntfs_attr_search_ctx *ctx) +{ + if (!ctx->base_ntfs_ino) { + /* No attribute list. */ + ctx->is_first = TRUE; + /* Sanity checks are performed elsewhere. */ + ctx->attr = (ATTR_RECORD*)((u8*)ctx->mrec + + le16_to_cpu(ctx->mrec->attrs_offset)); + /* + * This needs resetting due to ntfs_external_attr_find() which + * can leave it set despite having zeroed ctx->base_ntfs_ino. + */ + ctx->al_entry = NULL; + return; + } /* Attribute list. */ + ntfs_attr_init_search_ctx(ctx, ctx->base_ntfs_ino, ctx->base_mrec); + return; +} + +/** + * ntfs_attr_get_search_ctx - allocate/initialize a new attribute search context + * @ni: ntfs inode with which to initialize the search context + * @mrec: mft record with which to initialize the search context + * + * Allocate a new attribute search context, initialize it with @ni and @mrec, + * and return it. Return NULL on error with errno set. + * + * @mrec can be NULL, in which case the mft record is taken from @ni. + * + * Note: For low level utilities which know what they are doing we allow @ni to + * be NULL and @mrec to be set. Do NOT do this unless you understand the + * implications!!! For example it is no longer safe to call ntfs_attr_lookup(). + */ +ntfs_attr_search_ctx *ntfs_attr_get_search_ctx(ntfs_inode *ni, MFT_RECORD *mrec) +{ + ntfs_attr_search_ctx *ctx; + + if (!ni && !mrec) { + errno = EINVAL; + ntfs_log_perror("NULL arguments"); + return NULL; + } + ctx = ntfs_malloc(sizeof(ntfs_attr_search_ctx)); + if (ctx) + ntfs_attr_init_search_ctx(ctx, ni, mrec); + return ctx; +} + +/** + * ntfs_attr_put_search_ctx - release an attribute search context + * @ctx: attribute search context to free + * + * Release the attribute search context @ctx. + */ +void ntfs_attr_put_search_ctx(ntfs_attr_search_ctx *ctx) +{ + // NOTE: save errno if it could change and function stays void! + free(ctx); +} + +/** + * ntfs_attr_find_in_attrdef - find an attribute in the $AttrDef system file + * @vol: ntfs volume to which the attribute belongs + * @type: attribute type which to find + * + * Search for the attribute definition record corresponding to the attribute + * @type in the $AttrDef system file. + * + * Return the attribute type definition record if found and NULL if not found + * or an error occurred. On error the error code is stored in errno. The + * following error codes are defined: + * ENOENT - The attribute @type is not specified in $AttrDef. + * EINVAL - Invalid parameters (e.g. @vol is not valid). + */ +ATTR_DEF *ntfs_attr_find_in_attrdef(const ntfs_volume *vol, + const ATTR_TYPES type) +{ + ATTR_DEF *ad; + + if (!vol || !vol->attrdef || !type) { + errno = EINVAL; + return NULL; + } + for (ad = vol->attrdef; (u8*)ad - (u8*)vol->attrdef < + vol->attrdef_len && ad->type; ++ad) { + /* We haven't found it yet, carry on searching. */ + if (le32_to_cpu(ad->type) < le32_to_cpu(type)) + continue; + /* We found the attribute; return it. */ + if (ad->type == type) + return ad; + /* We have gone too far already. No point in continuing. */ + break; + } + /* Attribute not found?!? */ + errno = ENOENT; + return NULL; +} + +/** + * ntfs_attr_size_bounds_check - check a size of an attribute type for validity + * @vol: ntfs volume to which the attribute belongs + * @type: attribute type which to check + * @size: size which to check + * + * Check whether the @size in bytes is valid for an attribute of @type on the + * ntfs volume @vol. This information is obtained from $AttrDef system file. + * + * Return 0 if valid and -1 if not valid or an error occurred. On error the + * error code is stored in errno. The following error codes are defined: + * ERANGE - @size is not valid for the attribute @type. + * ENOENT - The attribute @type is not specified in $AttrDef. + * EINVAL - Invalid parameters (e.g. @size is < 0 or @vol is not valid). + */ +int ntfs_attr_size_bounds_check(const ntfs_volume *vol, const ATTR_TYPES type, + const s64 size) +{ + ATTR_DEF *ad; + + if (size < 0) { + errno = EINVAL; + return -1; + } + + /* + * $ATTRIBUTE_LIST should be not greater than 0x40000, but this is not + * listed in the AttrDef. + */ + if (type == AT_ATTRIBUTE_LIST && size > 0x40000) { + errno = ERANGE; + return -1; + } + + ad = ntfs_attr_find_in_attrdef(vol, type); + if (!ad) + return -1; + /* We found the attribute. - Do the bounds check. */ + if ((sle64_to_cpu(ad->min_size) && size < + sle64_to_cpu(ad->min_size)) || + ((sle64_to_cpu(ad->max_size) > 0) && size > + sle64_to_cpu(ad->max_size))) { + /* @size is out of range! */ + errno = ERANGE; + return -1; + } + return 0; +} + +/** + * ntfs_attr_can_be_non_resident - check if an attribute can be non-resident + * @vol: ntfs volume to which the attribute belongs + * @type: attribute type which to check + * + * Check whether the attribute of @type on the ntfs volume @vol is allowed to + * be non-resident. This information is obtained from $AttrDef system file. + * + * Return 0 if the attribute is allowed to be non-resident and -1 if not or an + * error occurred. On error the error code is stored in errno. The following + * error codes are defined: + * EPERM - The attribute is not allowed to be non-resident. + * ENOENT - The attribute @type is not specified in $AttrDef. + * EINVAL - Invalid parameters (e.g. @vol is not valid). + */ +int ntfs_attr_can_be_non_resident(const ntfs_volume *vol, const ATTR_TYPES type) +{ + ATTR_DEF *ad; + + /* Find the attribute definition record in $AttrDef. */ + ad = ntfs_attr_find_in_attrdef(vol, type); + if (!ad) + return -1; + /* Check the flags and return the result. */ + if (ad->flags & ATTR_DEF_RESIDENT) { + errno = EPERM; + ntfs_log_trace("Attribute can't be non-resident\n"); + return -1; + } + return 0; +} + +/** + * ntfs_attr_can_be_resident - check if an attribute can be resident + * @vol: ntfs volume to which the attribute belongs + * @type: attribute type which to check + * + * Check whether the attribute of @type on the ntfs volume @vol is allowed to + * be resident. This information is derived from our ntfs knowledge and may + * not be completely accurate, especially when user defined attributes are + * present. Basically we allow everything to be resident except for index + * allocation and extended attribute attributes. + * + * Return 0 if the attribute is allowed to be resident and -1 if not or an + * error occurred. On error the error code is stored in errno. The following + * error codes are defined: + * EPERM - The attribute is not allowed to be resident. + * EINVAL - Invalid parameters (e.g. @vol is not valid). + * + * Warning: In the system file $MFT the attribute $Bitmap must be non-resident + * otherwise windows will not boot (blue screen of death)! We cannot + * check for this here as we don't know which inode's $Bitmap is being + * asked about so the caller needs to special case this. + */ +int ntfs_attr_can_be_resident(const ntfs_volume *vol, const ATTR_TYPES type) +{ + if (!vol || !vol->attrdef || !type) { + errno = EINVAL; + return -1; + } + if (type != AT_INDEX_ALLOCATION) + return 0; + + ntfs_log_trace("Attribute can't be resident\n"); + errno = EPERM; + return -1; +} + +/** + * ntfs_make_room_for_attr - make room for an attribute inside an mft record + * @m: mft record + * @pos: position at which to make space + * @size: byte size to make available at this position + * + * @pos points to the attribute in front of which we want to make space. + * + * Return 0 on success or -1 on error. On error the error code is stored in + * errno. Possible error codes are: + * ENOSPC - There is not enough space available to complete operation. The + * caller has to make space before calling this. + * EINVAL - Input parameters were faulty. + */ +int ntfs_make_room_for_attr(MFT_RECORD *m, u8 *pos, u32 size) +{ + u32 biu; + + ntfs_log_trace("Entering for pos 0x%d, size %u.\n", + (int)(pos - (u8*)m), (unsigned) size); + + /* Make size 8-byte alignment. */ + size = (size + 7) & ~7; + + /* Rigorous consistency checks. */ + if (!m || !pos || pos < (u8*)m || pos + size > + (u8*)m + le32_to_cpu(m->bytes_allocated)) { + errno = EINVAL; + return -1; + } + /* The -8 is for the attribute terminator. */ + if (pos - (u8*)m > (int)le32_to_cpu(m->bytes_in_use) - 8) { + errno = EINVAL; + return -1; + } + /* Nothing to do. */ + if (!size) + return 0; + + biu = le32_to_cpu(m->bytes_in_use); + /* Do we have enough space? */ + if (biu + size > le32_to_cpu(m->bytes_allocated)) { + errno = ENOSPC; + ntfs_log_trace("No enough space in the MFT record\n"); + return -1; + } + /* Move everything after pos to pos + size. */ + memmove(pos + size, pos, biu - (pos - (u8*)m)); + /* Update mft record. */ + m->bytes_in_use = cpu_to_le32(biu + size); + return 0; +} + +/** + * ntfs_resident_attr_record_add - add resident attribute to inode + * @ni: opened ntfs inode to which MFT record add attribute + * @type: type of the new attribute + * @name: name of the new attribute + * @name_len: name length of the new attribute + * @val: value of the new attribute + * @size: size of new attribute (length of @val, if @val != NULL) + * @flags: flags of the new attribute + * + * Return offset to attribute from the beginning of the mft record on success + * and -1 on error. On error the error code is stored in errno. + * Possible error codes are: + * EINVAL - Invalid arguments passed to function. + * EEXIST - Attribute of such type and with same name already exists. + * EIO - I/O error occurred or damaged filesystem. + */ +int ntfs_resident_attr_record_add(ntfs_inode *ni, ATTR_TYPES type, + ntfschar *name, u8 name_len, u8 *val, u32 size, + ATTR_FLAGS flags) +{ + ntfs_attr_search_ctx *ctx; + u32 length; + ATTR_RECORD *a; + MFT_RECORD *m; + int err, offset; + ntfs_inode *base_ni; + + ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x, flags 0x%x.\n", + (long long) ni->mft_no, (unsigned) type, (unsigned) flags); + + if (!ni || (!name && name_len)) { + errno = EINVAL; + return -1; + } + + if (ntfs_attr_can_be_resident(ni->vol, type)) { + if (errno == EPERM) + ntfs_log_trace("Attribute can't be resident.\n"); + else + ntfs_log_trace("ntfs_attr_can_be_resident failed.\n"); + return -1; + } + + /* Locate place where record should be. */ + ctx = ntfs_attr_get_search_ctx(ni, NULL); + if (!ctx) + return -1; + /* + * Use ntfs_attr_find instead of ntfs_attr_lookup to find place for + * attribute in @ni->mrec, not any extent inode in case if @ni is base + * file record. + */ + if (!ntfs_attr_find(type, name, name_len, CASE_SENSITIVE, val, size, + ctx)) { + err = EEXIST; + ntfs_log_trace("Attribute already present.\n"); + goto put_err_out; + } + if (errno != ENOENT) { + err = EIO; + goto put_err_out; + } + a = ctx->attr; + m = ctx->mrec; + + /* Make room for attribute. */ + length = offsetof(ATTR_RECORD, resident_end) + + ((name_len * sizeof(ntfschar) + 7) & ~7) + + ((size + 7) & ~7); + if (ntfs_make_room_for_attr(ctx->mrec, (u8*) ctx->attr, length)) { + err = errno; + ntfs_log_trace("Failed to make room for attribute.\n"); + goto put_err_out; + } + + /* Setup record fields. */ + offset = ((u8*)a - (u8*)m); + a->type = type; + a->length = cpu_to_le32(length); + a->non_resident = 0; + a->name_length = name_len; + a->name_offset = cpu_to_le16(offsetof(ATTR_RECORD, resident_end)); + a->flags = flags; + a->instance = m->next_attr_instance; + a->value_length = cpu_to_le32(size); + a->value_offset = cpu_to_le16(length - ((size + 7) & ~7)); + if (val) + memcpy((u8*)a + le16_to_cpu(a->value_offset), val, size); + else + memset((u8*)a + le16_to_cpu(a->value_offset), 0, size); + if (type == AT_FILE_NAME) + a->resident_flags = RESIDENT_ATTR_IS_INDEXED; + else + a->resident_flags = 0; + if (name_len) + memcpy((u8*)a + le16_to_cpu(a->name_offset), + name, sizeof(ntfschar) * name_len); + m->next_attr_instance = + cpu_to_le16((le16_to_cpu(m->next_attr_instance) + 1) & 0xffff); + if (ni->nr_extents == -1) + base_ni = ni->base_ni; + else + base_ni = ni; + if (type != AT_ATTRIBUTE_LIST && NInoAttrList(base_ni)) { + if (ntfs_attrlist_entry_add(ni, a)) { + err = errno; + ntfs_attr_record_resize(m, a, 0); + ntfs_log_trace("Failed add attribute entry to " + "ATTRIBUTE_LIST.\n"); + goto put_err_out; + } + } + ntfs_inode_mark_dirty(ni); + ntfs_attr_put_search_ctx(ctx); + return offset; +put_err_out: + ntfs_attr_put_search_ctx(ctx); + errno = err; + return -1; +} + +/** + * ntfs_non_resident_attr_record_add - add extent of non-resident attribute + * @ni: opened ntfs inode to which MFT record add attribute + * @type: type of the new attribute extent + * @name: name of the new attribute extent + * @name_len: name length of the new attribute extent + * @lowest_vcn: lowest vcn of the new attribute extent + * @dataruns_size: dataruns size of the new attribute extent + * @flags: flags of the new attribute extent + * + * Return offset to attribute from the beginning of the mft record on success + * and -1 on error. On error the error code is stored in errno. + * Possible error codes are: + * EINVAL - Invalid arguments passed to function. + * EEXIST - Attribute of such type, with same lowest vcn and with same + * name already exists. + * EIO - I/O error occurred or damaged filesystem. + */ +int ntfs_non_resident_attr_record_add(ntfs_inode *ni, ATTR_TYPES type, + ntfschar *name, u8 name_len, VCN lowest_vcn, int dataruns_size, + ATTR_FLAGS flags) +{ + ntfs_attr_search_ctx *ctx; + u32 length; + ATTR_RECORD *a; + MFT_RECORD *m; + ntfs_inode *base_ni; + int err, offset; + + ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x, lowest_vcn %lld, " + "dataruns_size %d, flags 0x%x.\n", + (long long) ni->mft_no, (unsigned) type, + (long long) lowest_vcn, dataruns_size, (unsigned) flags); + + if (!ni || dataruns_size <= 0 || (!name && name_len)) { + errno = EINVAL; + return -1; + } + + if (ntfs_attr_can_be_non_resident(ni->vol, type)) { + if (errno == EPERM) + ntfs_log_trace("Attribute can't be non resident.\n"); + else + ntfs_log_trace("ntfs_attr_can_be_non_resident failed.\n"); + return -1; + } + + /* Locate place where record should be. */ + ctx = ntfs_attr_get_search_ctx(ni, NULL); + if (!ctx) + return -1; + /* + * Use ntfs_attr_find instead of ntfs_attr_lookup to find place for + * attribute in @ni->mrec, not any extent inode in case if @ni is base + * file record. + */ + if (!ntfs_attr_find(type, name, name_len, CASE_SENSITIVE, NULL, 0, + ctx)) { + err = EEXIST; + ntfs_log_trace("Attribute already present.\n"); + goto put_err_out; + } + if (errno != ENOENT) { + err = EIO; + goto put_err_out; + } + a = ctx->attr; + m = ctx->mrec; + + /* Make room for attribute. */ + dataruns_size = (dataruns_size + 7) & ~7; + length = offsetof(ATTR_RECORD, compressed_size) + ((sizeof(ntfschar) * + name_len + 7) & ~7) + dataruns_size + + ((flags & (ATTR_IS_COMPRESSED | ATTR_IS_SPARSE)) ? + sizeof(a->compressed_size) : 0); + if (ntfs_make_room_for_attr(ctx->mrec, (u8*) ctx->attr, length)) { + err = errno; + ntfs_log_trace("Failed to make room for attribute.\n"); + goto put_err_out; + } + + /* Setup record fields. */ + a->type = type; + a->length = cpu_to_le32(length); + a->non_resident = 1; + a->name_length = name_len; + a->name_offset = cpu_to_le16(offsetof(ATTR_RECORD, compressed_size) + + ((flags & (ATTR_IS_COMPRESSED | ATTR_IS_SPARSE)) ? + sizeof(a->compressed_size) : 0)); + a->flags = flags; + a->instance = m->next_attr_instance; + a->lowest_vcn = cpu_to_sle64(lowest_vcn); + a->mapping_pairs_offset = cpu_to_le16(length - dataruns_size); + a->compression_unit = (flags & ATTR_IS_COMPRESSED) ? 4 : 0; + /* If @lowest_vcn == 0, than setup empty attribute. */ + if (!lowest_vcn) { + a->highest_vcn = cpu_to_sle64(-1); + a->allocated_size = 0; + a->data_size = 0; + a->initialized_size = 0; + /* Set empty mapping pairs. */ + *((u8*)a + le16_to_cpu(a->mapping_pairs_offset)) = 0; + } + if (name_len) + memcpy((u8*)a + le16_to_cpu(a->name_offset), + name, sizeof(ntfschar) * name_len); + m->next_attr_instance = + cpu_to_le16((le16_to_cpu(m->next_attr_instance) + 1) & 0xffff); + if (ni->nr_extents == -1) + base_ni = ni->base_ni; + else + base_ni = ni; + if (type != AT_ATTRIBUTE_LIST && NInoAttrList(base_ni)) { + if (ntfs_attrlist_entry_add(ni, a)) { + err = errno; + ntfs_attr_record_resize(m, a, 0); + ntfs_log_trace("Failed add attribute entry to " + "ATTRIBUTE_LIST.\n"); + goto put_err_out; + } + } + ntfs_inode_mark_dirty(ni); + /* + * Locate offset from start of the MFT record where new attribute is + * placed. We need relookup it, because record maybe moved during + * update of attribute list. + */ + ntfs_attr_reinit_search_ctx(ctx); + if (ntfs_attr_lookup(type, name, name_len, CASE_SENSITIVE, + lowest_vcn, NULL, 0, ctx)) { + ntfs_log_trace("Attribute lookup failed. Probably leaving inconstant " + "metadata.\n"); + ntfs_attr_put_search_ctx(ctx); + return -1; + + } + offset = (u8*)ctx->attr - (u8*)ctx->mrec; + ntfs_attr_put_search_ctx(ctx); + return offset; +put_err_out: + ntfs_attr_put_search_ctx(ctx); + errno = err; + return -1; +} + +/** + * ntfs_attr_record_rm - remove attribute extent + * @ctx: search context describing the attribute which should be removed + * + * If this function succeed, user should reinit search context if he/she wants + * use it anymore. + * + * Return 0 on success and -1 on error. On error the error code is stored in + * errno. Possible error codes are: + * EINVAL - Invalid arguments passed to function. + * EIO - I/O error occurred or damaged filesystem. + */ +int ntfs_attr_record_rm(ntfs_attr_search_ctx *ctx) +{ + ntfs_inode *base_ni, *ni; + ATTR_TYPES type; + int err; + + if (!ctx || !ctx->ntfs_ino || !ctx->mrec || !ctx->attr) { + errno = EINVAL; + return -1; + } + + ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x.\n", + (long long) ctx->ntfs_ino->mft_no, + (unsigned) le32_to_cpu(ctx->attr->type)); + type = ctx->attr->type; + ni = ctx->ntfs_ino; + if (ctx->base_ntfs_ino) + base_ni = ctx->base_ntfs_ino; + else + base_ni = ctx->ntfs_ino; + + /* Remove attribute itself. */ + if (ntfs_attr_record_resize(ctx->mrec, ctx->attr, 0)) { + ntfs_log_trace("Couldn't remove attribute record. Bug or damaged MFT " + "record.\n"); + if (NInoAttrList(base_ni) && type != AT_ATTRIBUTE_LIST) + if (ntfs_attrlist_entry_add(ni, ctx->attr)) + ntfs_log_trace("Rollback failed. Leaving inconstant " + "metadata.\n"); + err = EIO; + return -1; + } + ntfs_inode_mark_dirty(ni); + + /* + * Remove record from $ATTRIBUTE_LIST if present and we don't want + * delete $ATTRIBUTE_LIST itself. + */ + if (NInoAttrList(base_ni) && type != AT_ATTRIBUTE_LIST) { + if (ntfs_attrlist_entry_rm(ctx)) { + ntfs_log_trace("Couldn't delete record from " + "$ATTRIBUTE_LIST.\n"); + return -1; + } + } + + /* Post $ATTRIBUTE_LIST delete setup. */ + if (type == AT_ATTRIBUTE_LIST) { + if (NInoAttrList(base_ni) && base_ni->attr_list) + free(base_ni->attr_list); + base_ni->attr_list = NULL; + NInoClearAttrList(base_ni); + NInoAttrListClearDirty(base_ni); + } + + /* Free MFT record, if it isn't contain attributes. */ + if (le32_to_cpu(ctx->mrec->bytes_in_use) - + le16_to_cpu(ctx->mrec->attrs_offset) == 8) { + if (ntfs_mft_record_free(ni->vol, ni)) { + // FIXME: We need rollback here. + ntfs_log_trace("Couldn't free MFT record.\n"); + errno = EIO; + return -1; + } + /* Remove done if we freed base inode. */ + if (ni == base_ni) + return 0; + } + + if (type == AT_ATTRIBUTE_LIST || !NInoAttrList(base_ni)) + return 0; + + /* Remove attribute list if we don't need it any more. */ + if (!ntfs_attrlist_need(base_ni)) { + ntfs_attr_reinit_search_ctx(ctx); + if (ntfs_attr_lookup(AT_ATTRIBUTE_LIST, NULL, 0, CASE_SENSITIVE, + 0, NULL, 0, ctx)) { + /* + * FIXME: Should we succeed here? Definitely something + * goes wrong because NInoAttrList(base_ni) returned + * that we have got attribute list. + */ + ntfs_log_trace("Couldn't find attribute list. Succeed " + "anyway.\n"); + return 0; + } + /* Deallocate clusters. */ + if (ctx->attr->non_resident) { + runlist *al_rl; + + al_rl = ntfs_mapping_pairs_decompress(base_ni->vol, + ctx->attr, NULL); + if (!al_rl) { + ntfs_log_trace("Couldn't decompress attribute list " + "runlist. Succeed anyway.\n"); + return 0; + } + if (ntfs_cluster_free_from_rl(base_ni->vol, al_rl)) { + ntfs_log_trace("Leaking clusters! Run chkdsk. " + "Couldn't free clusters from " + "attribute list runlist.\n"); + } + free(al_rl); + } + /* Remove attribute record itself. */ + if (ntfs_attr_record_rm(ctx)) { + /* + * FIXME: Should we succeed here? BTW, chkdsk doesn't + * complain if it find MFT record with attribute list, + * but without extents. + */ + ntfs_log_trace("Couldn't remove attribute list. Succeed " + "anyway.\n"); + return 0; + } + } + return 0; +} + +/** + * ntfs_attr_add - add attribute to inode + * @ni: opened ntfs inode to which add attribute + * @type: type of the new attribute + * @name: name in unicode of the new attribute + * @name_len: name length in unicode characters of the new attribute + * @val: value of new attribute + * @size: size of the new attribute / length of @val (if specified) + * + * @val should always be specified for always resident attributes (eg. FILE_NAME + * attribute), for attributes that can become non-resident @val can be NULL + * (eg. DATA attribute). @size can be specified even if @val is NULL, in this + * case data size will be equal to @size and initialized size will be equal + * to 0. + * + * If inode haven't got enough space to add attribute, add attribute to one of + * it extents, if no extents present or no one of them have enough space, than + * allocate new extent and add attribute to it. + * + * If on one of this steps attribute list is needed but not present, than it is + * added transparently to caller. So, this function should not be called with + * @type == AT_ATTRIBUTE_LIST, if you really need to add attribute list call + * ntfs_inode_add_attrlist instead. + * + * On success return 0. On error return -1 with errno set to the error code. + */ +int ntfs_attr_add(ntfs_inode *ni, ATTR_TYPES type, + ntfschar *name, u8 name_len, u8 *val, s64 size) +{ + u32 attr_rec_size; + int err, i, offset; + BOOL is_resident; + ntfs_inode *attr_ni; + ntfs_attr *na; + + if (!ni || size < 0 || type == AT_ATTRIBUTE_LIST) { + ntfs_log_trace("Invalid arguments passed.\n"); + errno = EINVAL; + return -1; + } + + ntfs_log_trace("Entering for inode 0x%llx, attr %x, size %lld.\n", + (long long) ni->mft_no, type, size); + + if (ni->nr_extents == -1) + ni = ni->base_ni; + + /* Check the attribute type and the size. */ + if (ntfs_attr_size_bounds_check(ni->vol, type, size)) { + if (errno == ERANGE) { + ntfs_log_trace("Size bounds check failed. Aborting...\n"); + } else if (errno == ENOENT) { + ntfs_log_trace("Invalid attribute type. Aborting...\n"); + errno = EIO; + } + return -1; + } + + /* Sanity checks for always resident attributes. */ + if (ntfs_attr_can_be_non_resident(ni->vol, type)) { + if (errno != EPERM) { + err = errno; + ntfs_log_trace("ntfs_attr_can_be_non_resident failed.\n"); + goto err_out; + } + /* @val is mandatory. */ + if (!val) { + ntfs_log_trace("val is mandatory for always resident " + "attributes.\n"); + errno = EINVAL; + return -1; + } + if (size > ni->vol->mft_record_size) { + ntfs_log_trace("Attribute is too big.\n"); + errno = ERANGE; + return -1; + } + } + + /* + * Determine resident or not will be new attribute. We add 8 to size in + * non resident case for mapping pairs. + */ + if (!ntfs_attr_can_be_resident(ni->vol, type)) { + is_resident = TRUE; + } else { + if (errno != EPERM) { + err = errno; + ntfs_log_trace("ntfs_attr_can_be_resident failed.\n"); + goto err_out; + } + is_resident = FALSE; + } + /* Calculate attribute record size. */ + if (is_resident) + attr_rec_size = offsetof(ATTR_RECORD, resident_end) + + ((name_len * sizeof(ntfschar) + 7) & ~7) + + ((size + 7) & ~7); + else + attr_rec_size = offsetof(ATTR_RECORD, non_resident_end) + + ((name_len * sizeof(ntfschar) + 7) & ~7) + 8; + + /* + * If we have enough free space for the new attribute in the base MFT + * record, then add attribute to it. + */ + if (le32_to_cpu(ni->mrec->bytes_allocated) - + le32_to_cpu(ni->mrec->bytes_in_use) >= attr_rec_size) { + attr_ni = ni; + goto add_attr_record; + } + + /* Try to add to extent inodes. */ + if (ntfs_inode_attach_all_extents(ni)) { + err = errno; + ntfs_log_trace("Failed to attach all extents to inode.\n"); + goto err_out; + } + for (i = 0; i < ni->nr_extents; i++) { + attr_ni = ni->extent_nis[i]; + if (le32_to_cpu(attr_ni->mrec->bytes_allocated) - + le32_to_cpu(attr_ni->mrec->bytes_in_use) >= + attr_rec_size) + goto add_attr_record; + } + + /* There is no extent that contain enough space for new attribute. */ + if (!NInoAttrList(ni)) { + /* Add attribute list not present, add it and retry. */ + if (ntfs_inode_add_attrlist(ni)) { + err = errno; + ntfs_log_trace("Failed to add attribute list.\n"); + goto err_out; + } + return ntfs_attr_add(ni, type, name, name_len, val, size); + } + /* Allocate new extent. */ + attr_ni = ntfs_mft_record_alloc(ni->vol, ni); + if (!attr_ni) { + err = errno; + ntfs_log_trace("Failed to allocate extent record.\n"); + goto err_out; + } + +add_attr_record: + if (is_resident) { + /* Add resident attribute. */ + offset = ntfs_resident_attr_record_add(attr_ni, type, name, + name_len, val, size, 0); + if (offset < 0) { + err = errno; + ntfs_log_trace("Failed to add resident attribute.\n"); + goto free_err_out; + } + return 0; + } + + /* Add non resident attribute. */ + offset = ntfs_non_resident_attr_record_add(attr_ni, type, name, + name_len, 0, 8, 0); + if (offset < 0) { + err = errno; + ntfs_log_trace("Failed to add non resident attribute.\n"); + goto free_err_out; + } + + /* If @size == 0, we are done. */ + if (!size) + return 0; + + /* Open new attribute and resize it. */ + na = ntfs_attr_open(ni, type, name, name_len); + if (!na) { + err = errno; + ntfs_log_trace("Failed to open just added attribute.\n"); + goto rm_attr_err_out; + } + /* Resize and set attribute value. */ + if (ntfs_attr_truncate(na, size) || + (val && (ntfs_attr_pwrite(na, 0, size, val) != size))) { + err = errno; + ntfs_log_trace("Failed to initialize just added attribute.\n"); + if (ntfs_attr_rm(na)) { + ntfs_log_trace("Failed to remove just added attribute. " + "Probably leaving inconstant metadata.\n"); + ntfs_attr_close(na); + } + goto err_out; + } + ntfs_attr_close(na); + /* Done !*/ + return 0; + +rm_attr_err_out: + /* Remove just added attribute. */ + if (ntfs_attr_record_resize(attr_ni->mrec, + (ATTR_RECORD*)((u8*)attr_ni->mrec + offset), 0)) { + ntfs_log_trace("Failed to remove just added attribute.\n"); + } +free_err_out: + /* Free MFT record, if it isn't contain attributes. */ + if (le32_to_cpu(attr_ni->mrec->bytes_in_use) - + le32_to_cpu(attr_ni->mrec->attrs_offset) == 8) { + if (ntfs_mft_record_free(attr_ni->vol, attr_ni)) { + ntfs_log_trace("Failed to free MFT record. Leaving " + "inconstant metadata.\n"); + } + } +err_out: + errno = err; + return -1; +} + +/** + * ntfs_attr_rm - remove attribute from ntfs inode + * @na: opened ntfs attribute to delete + * + * Remove attribute and all it's extents from ntfs inode. If attribute was non + * resident also free all clusters allocated by attribute. + * + * Return 0 on success or -1 on error with errno set to the error code. + */ +int ntfs_attr_rm(ntfs_attr *na) +{ + ntfs_attr_search_ctx *ctx; + int ret = 0; + + if (!na) { + ntfs_log_trace("Invalid arguments passed.\n"); + errno = EINVAL; + return -1; + } + + ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x.\n", + (long long) na->ni->mft_no, na->type); + + /* Free cluster allocation. */ + if (NAttrNonResident(na)) { + if (ntfs_attr_map_whole_runlist(na)) + return -1; + if (ntfs_cluster_free(na->ni->vol, na, 0, -1) < 0) { + ntfs_log_trace("Failed to free cluster allocation. Leaving " + "inconstant metadata.\n"); + ret = -1; + } + } + + /* Search for attribute extents and remove them all. */ + ctx = ntfs_attr_get_search_ctx(na->ni, NULL); + if (!ctx) + return -1; + while (!ntfs_attr_lookup(na->type, na->name, na->name_len, + CASE_SENSITIVE, 0, NULL, 0, ctx)) { + if (ntfs_attr_record_rm(ctx)) { + ntfs_log_trace("Failed to remove attribute extent. Leaving " + "inconstant metadata.\n"); + ret = -1; + } + ntfs_attr_reinit_search_ctx(ctx); + } + if (errno != ENOENT) { + ntfs_log_trace("Attribute lookup failed. Probably leaving inconstant " + "metadata.\n"); + ret = -1; + } + + /* Throw away now non-exist attribute. */ + ntfs_attr_close(na); + /* Done. */ + return ret; +} + +/** + * ntfs_attr_record_resize - resize an attribute record + * @m: mft record containing attribute record + * @a: attribute record to resize + * @new_size: new size in bytes to which to resize the attribute record @a + * + * Resize the attribute record @a, i.e. the resident part of the attribute, in + * the mft record @m to @new_size bytes. + * + * Return 0 on success and -1 on error with errno set to the error code. + * The following error codes are defined: + * ENOSPC - Not enough space in the mft record @m to perform the resize. + * Note that on error no modifications have been performed whatsoever. + * + * Warning: If you make a record smaller without having copied all the data you + * are interested in the data may be overwritten! + */ +int ntfs_attr_record_resize(MFT_RECORD *m, ATTR_RECORD *a, u32 new_size) +{ + u32 old_size, alloc_size, attr_size; + + old_size = le32_to_cpu(m->bytes_in_use); + alloc_size = le32_to_cpu(m->bytes_allocated); + attr_size = le32_to_cpu(a->length); + + ntfs_log_trace("Sizes: old=%u alloc=%u attr=%u new=%u\n", + (unsigned)old_size, (unsigned)alloc_size, + (unsigned)attr_size, (unsigned)new_size); + + /* Align to 8 bytes, just in case the caller hasn't. */ + new_size = (new_size + 7) & ~7; + + /* If the actual attribute length has changed, move things around. */ + if (new_size != attr_size) { + + u32 new_muse = old_size - attr_size + new_size; + + /* Not enough space in this mft record. */ + if (new_muse > alloc_size) { + errno = ENOSPC; + ntfs_log_trace("Not enough space in the MFT record " + "(%u > %u)\n", new_muse, alloc_size); + return -1; + } + + /* Move attributes following @a to their new location. */ + memmove((u8 *)a + new_size, (u8 *)a + attr_size, + old_size - ((u8 *)a - (u8 *)m) - attr_size); + + /* Adjust @m to reflect the change in used space. */ + m->bytes_in_use = cpu_to_le32(new_muse); + + /* Adjust @a to reflect the new size. */ + if (new_size >= offsetof(ATTR_REC, length) + sizeof(a->length)) + a->length = cpu_to_le32(new_size); + } + return 0; +} + +/** + * ntfs_resident_attr_value_resize - resize the value of a resident attribute + * @m: mft record containing attribute record + * @a: attribute record whose value to resize + * @new_size: new size in bytes to which to resize the attribute value of @a + * + * Resize the value of the attribute @a in the mft record @m to @new_size bytes. + * If the value is made bigger, the newly "allocated" space is cleared. + * + * Return 0 on success and -1 on error with errno set to the error code. + * The following error codes are defined: + * ENOSPC - Not enough space in the mft record @m to perform the resize. + * Note that on error no modifications have been performed whatsoever. + */ +int ntfs_resident_attr_value_resize(MFT_RECORD *m, ATTR_RECORD *a, + const u32 new_size) +{ + ntfs_log_trace("Entering for new size %u.\n", (unsigned)new_size); + + /* Resize the resident part of the attribute record. */ + if (ntfs_attr_record_resize(m, a, (le16_to_cpu(a->value_offset) + + new_size + 7) & ~7) < 0) + return -1; + /* + * If we made the attribute value bigger, clear the area between the + * old size and @new_size. + */ + if (new_size > le32_to_cpu(a->value_length)) + memset((u8*)a + le16_to_cpu(a->value_offset) + + le32_to_cpu(a->value_length), 0, new_size - + le32_to_cpu(a->value_length)); + /* Finally update the length of the attribute value. */ + a->value_length = cpu_to_le32(new_size); + return 0; +} + +/** + * ntfs_attr_record_move_to - move attribute record to target inode + * @ctx: attribute search context describing the attribute record + * @ni: opened ntfs inode to which move attribute record + * + * If this function succeed, user should reinit search context if he/she wants + * use it anymore. + * + * Return 0 on success and -1 on error with errno set to the error code. + */ +int ntfs_attr_record_move_to(ntfs_attr_search_ctx *ctx, ntfs_inode *ni) +{ + ntfs_attr_search_ctx *nctx; + ATTR_RECORD *a; + int err; + + if (!ctx || !ctx->attr || !ctx->ntfs_ino || !ni) { + ntfs_log_trace("Invalid arguments passed.\n"); + errno = EINVAL; + return -1; + } + + ntfs_log_trace("Entering for ctx->attr->type 0x%x, ctx->ntfs_ino->mft_no " + "0x%llx, ni->mft_no 0x%llx.\n", + (unsigned) le32_to_cpu(ctx->attr->type), + (long long) ctx->ntfs_ino->mft_no, + (long long) ni->mft_no); + + if (ctx->ntfs_ino == ni) + return 0; + + if (!ctx->al_entry) { + ntfs_log_trace("Inode should contain attribute list to use this " + "function.\n"); + errno = EINVAL; + return -1; + } + + /* Find place in MFT record where attribute will be moved. */ + a = ctx->attr; + nctx = ntfs_attr_get_search_ctx(ni, NULL); + if (!nctx) { + ntfs_log_trace("Couldn't obtain search context.\n"); + return -1; + } + /* + * Use ntfs_attr_find instead of ntfs_attr_lookup to find place for + * attribute in @ni->mrec, not any extent inode in case if @ni is base + * file record. + */ + if (!ntfs_attr_find(a->type, (ntfschar*)((u8*)a + le16_to_cpu( + a->name_offset)), a->name_length, CASE_SENSITIVE, NULL, + 0, nctx)) { + ntfs_log_trace("Attribute of such type, with same name already " + "present in this MFT record.\n"); + err = EEXIST; + goto put_err_out; + } + if (errno != ENOENT) { + err = errno; + ntfs_log_debug("Attribute lookup failed.\n"); + goto put_err_out; + } + + /* Make space and move attribute. */ + if (ntfs_make_room_for_attr(ni->mrec, (u8*) nctx->attr, + le32_to_cpu(a->length))) { + err = errno; + ntfs_log_trace("Couldn't make space for attribute.\n"); + goto put_err_out; + } + memcpy(nctx->attr, a, le32_to_cpu(a->length)); + nctx->attr->instance = nctx->mrec->next_attr_instance; + nctx->mrec->next_attr_instance = cpu_to_le16( + (le16_to_cpu(nctx->mrec->next_attr_instance) + 1) & 0xffff); + ntfs_attr_record_resize(ctx->mrec, a, 0); + ntfs_inode_mark_dirty(ctx->ntfs_ino); + ntfs_inode_mark_dirty(ni); + + /* Update attribute list. */ + ctx->al_entry->mft_reference = + MK_LE_MREF(ni->mft_no, le16_to_cpu(ni->mrec->sequence_number)); + ctx->al_entry->instance = nctx->attr->instance; + ntfs_attrlist_mark_dirty(ni); + + ntfs_attr_put_search_ctx(nctx); + return 0; +put_err_out: + ntfs_attr_put_search_ctx(nctx); + errno = err; + return -1; +} + +/** + * ntfs_attr_record_move_away - move away attribute record from it's mft record + * @ctx: attribute search context describing the attribute record + * @extra: minimum amount of free space in the new holder of record + * + * New attribute record holder must have free @extra bytes after moving + * attribute record to it. + * + * If this function succeed, user should reinit search context if he/she wants + * use it anymore. + * + * Return 0 on success and -1 on error with errno set to the error code. + */ +int ntfs_attr_record_move_away(ntfs_attr_search_ctx *ctx, int extra) +{ + ntfs_inode *base_ni, *ni; + MFT_RECORD *m; + int i; + + if (!ctx || !ctx->attr || !ctx->ntfs_ino || extra < 0) { + ntfs_log_trace("Invalid arguments passed.\n"); + errno = EINVAL; + return -1; + } + + ntfs_log_trace("Entering for attr 0x%x, inode 0x%llx.\n", + (unsigned) le32_to_cpu(ctx->attr->type), + (long long) ctx->ntfs_ino->mft_no); + + if (ctx->ntfs_ino->nr_extents == -1) + base_ni = ctx->base_ntfs_ino; + else + base_ni = ctx->ntfs_ino; + + if (!NInoAttrList(base_ni)) { + ntfs_log_trace("Inode should contain attribute list to use this " + "function.\n"); + errno = EINVAL; + return -1; + } + + if (ntfs_inode_attach_all_extents(ctx->ntfs_ino)) { + ntfs_log_trace("Couldn't attach extent inode.\n"); + return -1; + } + + /* Walk through all extents and try to move attribute to them. */ + for (i = 0; i < base_ni->nr_extents; i++) { + ni = base_ni->extent_nis[i]; + m = ni->mrec; + + if (ctx->ntfs_ino->mft_no == ni->mft_no) + continue; + + if (le32_to_cpu(m->bytes_allocated) - + le32_to_cpu(m->bytes_in_use) < + le32_to_cpu(ctx->attr->length) + extra) + continue; + + /* + * ntfs_attr_record_move_to can fail if extent with other lowest + * VCN already present in inode we trying move record to. So, + * do not return error. + */ + if (!ntfs_attr_record_move_to(ctx, ni)) + return 0; + } + + /* + * Failed to move attribute to one of the current extents, so allocate + * new extent and move attribute to it. + */ + ni = ntfs_mft_record_alloc(base_ni->vol, base_ni); + if (!ni) { + ntfs_log_trace("Couldn't allocate new MFT record.\n"); + return -1; + } + if (ntfs_attr_record_move_to(ctx, ni)) { + ntfs_log_trace("Couldn't move attribute to new MFT record.\n"); + return -1; + } + return 0; +} + +/** + * ntfs_attr_make_non_resident - convert a resident to a non-resident attribute + * @na: open ntfs attribute to make non-resident + * @ctx: ntfs search context describing the attribute + * + * Convert a resident ntfs attribute to a non-resident one. + * + * Return 0 on success and -1 on error with errno set to the error code. The + * following error codes are defined: + * EPERM - The attribute is not allowed to be non-resident. + * TODO: others... + * + * NOTE to self: No changes in the attribute list are required to move from + * a resident to a non-resident attribute. + * + * Warning: We do not set the inode dirty and we do not write out anything! + * We expect the caller to do this as this is a fairly low level + * function and it is likely there will be further changes made. + */ +static int ntfs_attr_make_non_resident(ntfs_attr *na, + ntfs_attr_search_ctx *ctx) +{ + s64 new_allocated_size, bw; + ntfs_volume *vol = na->ni->vol; + ATTR_REC *a = ctx->attr; + runlist *rl; + int mp_size, mp_ofs, name_ofs, arec_size, err; + + ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x.\n", (unsigned long + long)na->ni->mft_no, na->type); + + /* Some preliminary sanity checking. */ + if (NAttrNonResident(na)) { + ntfs_log_trace("Eeek! Trying to make non-resident attribute " + "non-resident. Aborting...\n"); + errno = EINVAL; + return -1; + } + + /* Check that the attribute is allowed to be non-resident. */ + if (ntfs_attr_can_be_non_resident(vol, na->type)) + return -1; + + new_allocated_size = (le32_to_cpu(a->value_length) + vol->cluster_size + - 1) & ~(vol->cluster_size - 1); + + if (new_allocated_size > 0) { + /* Start by allocating clusters to hold the attribute value. */ + rl = ntfs_cluster_alloc(vol, 0, new_allocated_size >> + vol->cluster_size_bits, -1, DATA_ZONE); + if (!rl) { + if (errno != ENOSPC) + ntfs_log_trace("Eeek! Failed to allocate " + "cluster(s). Aborting...\n"); + return -1; + } + } else + rl = NULL; + /* + * Setup the in-memory attribute structure to be non-resident so that + * we can use ntfs_attr_pwrite(). + */ + NAttrSetNonResident(na); + na->rl = rl; + na->allocated_size = new_allocated_size; + na->data_size = na->initialized_size = le32_to_cpu(a->value_length); + /* + * FIXME: For now just clear all of these as we don't support them when + * writing. + */ + NAttrClearCompressed(na); + NAttrClearSparse(na); + NAttrClearEncrypted(na); + + if (rl) { + /* Now copy the attribute value to the allocated cluster(s). */ + bw = ntfs_attr_pwrite(na, 0, le32_to_cpu(a->value_length), + (u8*)a + le16_to_cpu(a->value_offset)); + if (bw != le32_to_cpu(a->value_length)) { + err = errno; + ntfs_log_debug("Eeek! Failed to write out attribute value " + "(bw = %lli, errno = %i). " + "Aborting...\n", (long long)bw, err); + if (bw >= 0) + err = EIO; + goto cluster_free_err_out; + } + } + /* Determine the size of the mapping pairs array. */ + mp_size = ntfs_get_size_for_mapping_pairs(vol, rl, 0); + if (mp_size < 0) { + err = errno; + ntfs_log_debug("Eeek! Failed to get size for mapping pairs array. " + "Aborting...\n"); + goto cluster_free_err_out; + } + /* Calculate new offsets for the name and the mapping pairs array. */ + name_ofs = (sizeof(ATTR_REC) - sizeof(a->compressed_size) + 7) & ~7; + mp_ofs = (name_ofs + a->name_length * sizeof(ntfschar) + 7) & ~7; + /* + * Determine the size of the resident part of the non-resident + * attribute record. (Not compressed thus no compressed_size element + * present.) + */ + arec_size = (mp_ofs + mp_size + 7) & ~7; + + /* Resize the resident part of the attribute record. */ + if (ntfs_attr_record_resize(ctx->mrec, a, arec_size) < 0) { + err = errno; + goto cluster_free_err_out; + } + + /* + * Convert the resident part of the attribute record to describe a + * non-resident attribute. + */ + a->non_resident = 1; + + /* Move the attribute name if it exists and update the offset. */ + if (a->name_length) + memmove((u8*)a + name_ofs, (u8*)a + le16_to_cpu(a->name_offset), + a->name_length * sizeof(ntfschar)); + a->name_offset = cpu_to_le16(name_ofs); + + /* Update the flags to match the in-memory ones. */ + a->flags &= ~(ATTR_IS_SPARSE | ATTR_IS_ENCRYPTED | + ATTR_COMPRESSION_MASK); + + /* Setup the fields specific to non-resident attributes. */ + a->lowest_vcn = cpu_to_sle64(0); + a->highest_vcn = cpu_to_sle64((new_allocated_size - 1) >> + vol->cluster_size_bits); + + a->mapping_pairs_offset = cpu_to_le16(mp_ofs); + + a->compression_unit = 0; + + memset(&a->reserved1, 0, sizeof(a->reserved1)); + + a->allocated_size = cpu_to_sle64(new_allocated_size); + a->data_size = a->initialized_size = cpu_to_sle64(na->data_size); + + /* Generate the mapping pairs array in the attribute record. */ + if (ntfs_mapping_pairs_build(vol, (u8*)a + mp_ofs, arec_size - mp_ofs, + rl, 0, NULL) < 0) { + // FIXME: Eeek! We need rollback! (AIA) + ntfs_log_trace("Eeek! Failed to build mapping pairs. Leaving " + "corrupt attribute record on disk. In memory " + "runlist is still intact! Error code is %i. " + "FIXME: Need to rollback instead!\n", errno); + return -1; + } + + /* Done! */ + return 0; + +cluster_free_err_out: + if (rl && ntfs_cluster_free(vol, na, 0, -1) < 0) + ntfs_log_trace("Eeek! Failed to release allocated clusters in error " + "code path. Leaving inconsistent metadata...\n"); + NAttrClearNonResident(na); + na->allocated_size = na->data_size; + na->rl = NULL; + free(rl); + errno = err; + return -1; +} + +/** + * ntfs_resident_attr_resize - resize a resident, open ntfs attribute + * @na: resident ntfs attribute to resize + * @newsize: new size (in bytes) to which to resize the attribute + * + * Change the size of a resident, open ntfs attribute @na to @newsize bytes. + * + * On success return 0 + * On error return values are: + * STATUS_RESIDENT_ATTRIBUTE_FILLED_MFT + * STATUS_ERROR - otherwise + * The following error codes are defined: + * ENOMEM - Not enough memory to complete operation. + * ERANGE - @newsize is not valid for the attribute type of @na. + * ENOSPC - There is no enough space in base mft to resize $ATTRIBUTE_LIST. + */ +static int ntfs_resident_attr_resize(ntfs_attr *na, const s64 newsize) +{ + ntfs_attr_search_ctx *ctx; + ntfs_volume *vol; + ntfs_inode *ni; + int err, ret = STATUS_ERROR; + + ntfs_log_trace("Inode 0x%llx attr 0x%x new size %lld\n", + (unsigned long long)na->ni->mft_no, na->type, + (long long)newsize); + + /* Get the attribute record that needs modification. */ + ctx = ntfs_attr_get_search_ctx(na->ni, NULL); + if (!ctx) + return -1; + if (ntfs_attr_lookup(na->type, na->name, na->name_len, 0, 0, NULL, 0, + ctx)) { + err = errno; + goto put_err_out; + } + vol = na->ni->vol; + /* + * Check the attribute type and the corresponding minimum and maximum + * sizes against @newsize and fail if @newsize is out of bounds. + */ + if (ntfs_attr_size_bounds_check(vol, na->type, newsize) < 0) { + err = errno; + if (err == ERANGE) { + ntfs_log_trace("Eeek! Size bounds check failed. " + "Aborting...\n"); + } else if (err == ENOENT) + err = EIO; + goto put_err_out; + } + /* + * If @newsize is bigger than the mft record we need to make the + * attribute non-resident if the attribute type supports it. If it is + * smaller we can go ahead and attempt the resize. + */ + if (newsize < vol->mft_record_size) { + /* Perform the resize of the attribute record. */ + if (!ntfs_resident_attr_value_resize(ctx->mrec, ctx->attr, + newsize)) { + /* Update attribute size everywhere. */ + na->data_size = na->initialized_size = newsize; + na->allocated_size = (newsize + 7) & ~7; + if (NAttrCompressed(na) || NAttrSparse(na)) + na->compressed_size = na->allocated_size; + if (na->type == AT_DATA && na->name == AT_UNNAMED) { + na->ni->data_size = na->data_size; + na->ni->allocated_size = na->allocated_size; + NInoFileNameSetDirty(na->ni); + } + goto resize_done; + } + } + /* There is not enough space in the mft record to perform the resize. */ + + /* Make the attribute non-resident if possible. */ + if (!ntfs_attr_make_non_resident(na, ctx)) { + ntfs_inode_mark_dirty(ctx->ntfs_ino); + ntfs_attr_put_search_ctx(ctx); + /* Resize non-resident attribute */ + return ntfs_attr_truncate(na, newsize); + } else if (errno != ENOSPC && errno != EPERM) { + err = errno; + ntfs_log_trace("Eeek! Failed to make attribute non-resident. " + "Aborting...\n"); + goto put_err_out; + } + + /* Try to make other attributes non-resident and retry each time. */ + ntfs_attr_init_search_ctx(ctx, NULL, na->ni->mrec); + while (!ntfs_attr_lookup(AT_UNUSED, NULL, 0, 0, 0, NULL, 0, ctx)) { + ntfs_attr *tna; + ATTR_RECORD *a; + + a = ctx->attr; + if (a->non_resident) + continue; + + /* + * Check out whether convert is reasonable. Assume that mapping + * pairs will take 8 bytes. + */ + if (le32_to_cpu(a->length) <= offsetof(ATTR_RECORD, + compressed_size) + ((a->name_length * + sizeof(ntfschar) + 7) & ~7) + 8) + continue; + + tna = ntfs_attr_open(na->ni, a->type, (ntfschar*)((u8*)a + + le16_to_cpu(a->name_offset)), a->name_length); + if (!tna) { + err = errno; + ntfs_log_trace("Couldn't open attribute.\n"); + goto put_err_out; + } + if (ntfs_attr_make_non_resident(tna, ctx)) { + ntfs_attr_close(tna); + continue; + } + ntfs_inode_mark_dirty(tna->ni); + ntfs_attr_close(tna); + ntfs_attr_put_search_ctx(ctx); + return ntfs_resident_attr_resize(na, newsize); + } + /* Check whether error occurred. */ + if (errno != ENOENT) { + err = errno; + ntfs_log_trace("Attribute lookup failed.\n"); + goto put_err_out; + } + + /* We can't move out attribute list, thus move out others. */ + if (na->type == AT_ATTRIBUTE_LIST) { + ntfs_attr_put_search_ctx(ctx); + if (ntfs_inode_free_space(na->ni, offsetof(ATTR_RECORD, + non_resident_end) + 8)) { + ntfs_log_trace("Couldn't free space in the MFT record to " + "make attribute list non resident.\n"); + return -1; + } + return ntfs_resident_attr_resize(na, newsize); + } + + /* + * Move the attribute to a new mft record, creating an attribute list + * attribute or modifying it if it is already present. + */ + + /* Point search context back to attribute which we need resize. */ + ntfs_attr_init_search_ctx(ctx, na->ni, NULL); + if (ntfs_attr_lookup(na->type, na->name, na->name_len, CASE_SENSITIVE, + 0, NULL, 0, ctx)) { + ntfs_log_trace("Attribute lookup failed.\n"); + err = errno; + goto put_err_out; + } + + /* + * Check whether attribute is already single in this MFT record. + * 8 added for the attribute terminator. + */ + if (le32_to_cpu(ctx->mrec->bytes_in_use) == + le16_to_cpu(ctx->mrec->attrs_offset) + + le32_to_cpu(ctx->attr->length) + 8) { + err = ENOSPC; + ntfs_log_trace("MFT record is filled with one attribute\n"); + ret = STATUS_RESIDENT_ATTRIBUTE_FILLED_MFT; + goto put_err_out; + } + + /* Add attribute list if not present. */ + if (na->ni->nr_extents == -1) + ni = na->ni->base_ni; + else + ni = na->ni; + if (!NInoAttrList(ni)) { + ntfs_attr_put_search_ctx(ctx); + if (ntfs_inode_add_attrlist(ni)) + return -1; + return ntfs_resident_attr_resize(na, newsize); + } + /* Allocate new mft record. */ + ni = ntfs_mft_record_alloc(vol, ni); + if (!ni) { + err = errno; + ntfs_log_trace("Couldn't allocate new MFT record.\n"); + goto put_err_out; + } + /* Move attribute to it. */ + if (ntfs_attr_record_move_to(ctx, ni)) { + err = errno; + ntfs_log_trace("Couldn't move attribute to new MFT record.\n"); + goto put_err_out; + } + /* Update ntfs attribute. */ + if (na->ni->nr_extents == -1) + na->ni = ni; + + ntfs_attr_put_search_ctx(ctx); + /* Try to perform resize once again. */ + return ntfs_resident_attr_resize(na, newsize); + +resize_done: + /* + * Set the inode (and its base inode if it exists) dirty so it is + * written out later. + */ + ntfs_inode_mark_dirty(ctx->ntfs_ino); + /* Done! */ + ntfs_attr_put_search_ctx(ctx); + return 0; +put_err_out: + ntfs_attr_put_search_ctx(ctx); + errno = err; + return ret; +} + +/** + * ntfs_attr_make_resident - convert a non-resident to a resident attribute + * @na: open ntfs attribute to make resident + * @ctx: ntfs search context describing the attribute + * + * Convert a non-resident ntfs attribute to a resident one. + * + * Return 0 on success and -1 on error with errno set to the error code. The + * following error codes are defined: + * EINVAL - Invalid arguments passed. + * EPERM - The attribute is not allowed to be resident. + * EIO - I/O error, damaged inode or bug. + * ENOSPC - There is no enough space to perform conversion. + * EOPNOTSUPP - Requested conversion is not supported yet. + * + * Warning: We do not set the inode dirty and we do not write out anything! + * We expect the caller to do this as this is a fairly low level + * function and it is likely there will be further changes made. + */ +static int ntfs_attr_make_resident(ntfs_attr *na, ntfs_attr_search_ctx *ctx) +{ + ntfs_volume *vol = na->ni->vol; + ATTR_REC *a = ctx->attr; + int name_ofs, val_ofs, err = EIO; + s64 arec_size, bytes_read; + + ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x.\n", (unsigned long + long)na->ni->mft_no, na->type); + + /* Should be called for the first extent of the attribute. */ + if (sle64_to_cpu(a->lowest_vcn)) { + ntfs_log_trace("Eeek! Should be called for the first extent of the " + "attribute. Aborting...\n"); + err = EINVAL; + return -1; + } + + /* Some preliminary sanity checking. */ + if (!NAttrNonResident(na)) { + ntfs_log_trace("Eeek! Trying to make resident attribute resident. " + "Aborting...\n"); + errno = EINVAL; + return -1; + } + + /* Make sure this is not $MFT/$BITMAP or Windows will not boot! */ + if (na->type == AT_BITMAP && na->ni->mft_no == FILE_MFT) { + errno = EPERM; + return -1; + } + + /* Check that the attribute is allowed to be resident. */ + if (ntfs_attr_can_be_resident(vol, na->type)) + return -1; + + if (NAttrCompressed(na) || NAttrEncrypted(na)) { + ntfs_log_trace("Making compressed or encrypted files resident is not " + "implemented yet.\n"); + errno = EOPNOTSUPP; + return -1; + } + + /* Work out offsets into and size of the resident attribute. */ + name_ofs = 24; /* = sizeof(resident_ATTR_REC); */ + val_ofs = (name_ofs + a->name_length * sizeof(ntfschar) + 7) & ~7; + arec_size = (val_ofs + na->data_size + 7) & ~7; + + /* Sanity check the size before we start modifying the attribute. */ + if (le32_to_cpu(ctx->mrec->bytes_in_use) - le32_to_cpu(a->length) + + arec_size > le32_to_cpu(ctx->mrec->bytes_allocated)) { + errno = ENOSPC; + ntfs_log_trace("Not enough space to make attribute resident\n"); + return -1; + } + + /* Read and cache the whole runlist if not already done. */ + if (ntfs_attr_map_whole_runlist(na)) + return -1; + + /* Move the attribute name if it exists and update the offset. */ + if (a->name_length) { + memmove((u8*)a + name_ofs, (u8*)a + le16_to_cpu(a->name_offset), + a->name_length * sizeof(ntfschar)); + } + a->name_offset = cpu_to_le16(name_ofs); + + /* Resize the resident part of the attribute record. */ + if (ntfs_attr_record_resize(ctx->mrec, a, arec_size) < 0) { + /* + * Bug, because ntfs_attr_record_resize should not fail (we + * already checked that attribute fits MFT record). + */ + ntfs_log_error("BUG! Failed to resize attribute record. " + "Please report to the %s. Aborting...\n", + NTFS_DEV_LIST); + errno = EIO; + return -1; + } + + /* Convert the attribute record to describe a resident attribute. */ + a->non_resident = 0; + a->flags = 0; + a->value_length = cpu_to_le32(na->data_size); + a->value_offset = cpu_to_le16(val_ofs); + /* + * File names cannot be non-resident so we would never see this here + * but at least it serves as a reminder that there may be attributes + * for which we do need to set this flag. (AIA) + */ + if (a->type == AT_FILE_NAME) + a->resident_flags = RESIDENT_ATTR_IS_INDEXED; + else + a->resident_flags = 0; + a->reservedR = 0; + + /* Sanity fixup... Shouldn't really happen. (AIA) */ + if (na->initialized_size > na->data_size) + na->initialized_size = na->data_size; + + /* Copy data from run list to resident attribute value. */ + bytes_read = ntfs_rl_pread(vol, na->rl, 0, na->initialized_size, + (u8*)a + val_ofs); + if (bytes_read != na->initialized_size) { + if (bytes_read < 0) + err = errno; + ntfs_log_trace("Eeek! Failed to read attribute data. Leaving " + "inconstant metadata. Run chkdsk. " + "Aborting...\n"); + errno = err; + return -1; + } + + /* Clear memory in gap between initialized_size and data_size. */ + if (na->initialized_size < na->data_size) + memset((u8*)a + val_ofs + na->initialized_size, 0, + na->data_size - na->initialized_size); + + /* + * Deallocate clusters from the runlist. + * + * NOTE: We can use ntfs_cluster_free() because we have already mapped + * the whole run list and thus it doesn't matter that the attribute + * record is in a transiently corrupted state at this moment in time. + */ + if (ntfs_cluster_free(vol, na, 0, -1) < 0) { + err = errno; + ntfs_log_perror("Eeek! Failed to release allocated clusters"); + ntfs_log_trace("Ignoring error and leaving behind wasted " + "clusters.\n"); + } + + /* Throw away the now unused runlist. */ + free(na->rl); + na->rl = NULL; + + /* Update in-memory struct ntfs_attr. */ + NAttrClearNonResident(na); + NAttrClearCompressed(na); + NAttrClearSparse(na); + NAttrClearEncrypted(na); + na->initialized_size = na->data_size; + na->allocated_size = na->compressed_size = (na->data_size + 7) & ~7; + na->compression_block_size = 0; + na->compression_block_size_bits = na->compression_block_clusters = 0; + return 0; +} + +#define NTFS_VCN_DELETE_MARK -2 +/** + * ntfs_attr_update_mapping_pairs - update mapping pairs for ntfs attribute + * @na: non-resident ntfs open attribute for which we need update + * @from_vcn: update runlist starting this VCN + * + * Build mapping pairs from @na->rl and write them to the disk. Also, this + * function updates sparse bit, allocated and compressed size (allocates/frees + * space for this field if required). + * + * @na->allocated_size should be set to correct value for the new runlist before + * call to this function. Vice-versa @na->compressed_size will be calculated and + * set to correct value during this function. + * + * FIXME: This function does not update sparse bit and compressed size correctly + * if called with @from_vcn != 0. + * + * FIXME: Rewrite without using NTFS_VCN_DELETE_MARK define. + * + * On success return 0 and on error return -1 with errno set to the error code. + * The following error codes are defined: + * EINVAL - Invalid arguments passed. + * ENOMEM - Not enough memory to complete operation. + * ENOSPC - There is no enough space in base mft to resize $ATTRIBUTE_LIST + * or there is no free MFT records left to allocate. + */ +int ntfs_attr_update_mapping_pairs(ntfs_attr *na, VCN from_vcn) +{ + ntfs_attr_search_ctx *ctx; + ntfs_inode *ni, *base_ni; + MFT_RECORD *m; + ATTR_RECORD *a; + VCN stop_vcn; + int err, mp_size, cur_max_mp_size, exp_max_mp_size, ret = -1; + BOOL finished_build; + +retry: + if (!na || !na->rl || from_vcn) { + ntfs_log_trace("Invalid parameters passed.\n"); + errno = EINVAL; + return -1; + } + + if (!NAttrNonResident(na)) { + ntfs_log_trace("Attribute should be non resident.\n"); + errno = EINVAL; + return -1; + } + + ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x.\n", (unsigned long + long)na->ni->mft_no, na->type); + + if (na->ni->nr_extents == -1) + base_ni = na->ni->base_ni; + else + base_ni = na->ni; + + ctx = ntfs_attr_get_search_ctx(base_ni, NULL); + if (!ctx) { + ntfs_log_trace("Couldn't get search context.\n"); + return -1; + } + + /* Fill attribute records with new mapping pairs. */ + stop_vcn = 0; + finished_build = FALSE; + while (!ntfs_attr_lookup(na->type, na->name, na->name_len, + CASE_SENSITIVE, from_vcn, NULL, 0, ctx)) { + a = ctx->attr; + m = ctx->mrec; + /* + * If runlist is updating not from the beginning, then set + * @stop_vcn properly, i.e. to the lowest vcn of record that + * contain @from_vcn. Also we do not need @from_vcn anymore, + * set it to 0 to make ntfs_attr_lookup enumerate attributes. + */ + if (from_vcn) { + LCN first_lcn; + + stop_vcn = sle64_to_cpu(a->lowest_vcn); + from_vcn = 0; + /* + * Check whether the first run we need to update is + * the last run in runlist, if so, then deallocate + * all attrubute extents starting this one. + */ + first_lcn = ntfs_rl_vcn_to_lcn(na->rl, stop_vcn); + if (first_lcn == LCN_EINVAL) { + ntfs_log_trace("BUG! Incorrect runlist.\n"); + err = EIO; + goto put_err_out; + } + if (first_lcn == LCN_ENOENT || + first_lcn == LCN_RL_NOT_MAPPED) + finished_build = TRUE; + } + + /* + * Check whether we finished mapping pairs build, if so mark + * extent as need to delete (by setting highest vcn to + * NTFS_VCN_DELETE_MARK (-2), we shall check it later and + * delete extent) and continue search. + */ + if (finished_build) { + ntfs_log_trace("Mark attr 0x%x for delete in inode " + "0x%llx.\n", (unsigned)le32_to_cpu( + a->type), ctx->ntfs_ino->mft_no); + a->highest_vcn = cpu_to_sle64(NTFS_VCN_DELETE_MARK); + ntfs_inode_mark_dirty(ctx->ntfs_ino); + continue; + } + + /* + * If we in the first extent, then set/clean sparse bit, + * update allocated and compressed size. + */ + if (!a->lowest_vcn) { + int sparse; + + /* Update allocated size. */ + a->allocated_size = cpu_to_sle64(na->allocated_size); + /* Update sparse bit. */ + sparse = ntfs_rl_sparse(na->rl); + if (sparse == -1) { + ntfs_log_trace("Bad runlist.\n"); + err = EIO; + goto put_err_out; + } + /* Attribute become sparse. */ + if (sparse && !(a->flags & (ATTR_IS_SPARSE | + ATTR_IS_COMPRESSED))) { + /* + * We need to move attribute to another mft + * record, if attribute is to small to add + * compressed_size field to it and we have no + * free space in the current mft record. + */ + if ((le32_to_cpu(a->length) - le16_to_cpu( + a->mapping_pairs_offset) + == 8) && !(le32_to_cpu( + m->bytes_allocated) - + le32_to_cpu(m->bytes_in_use))) { + if (!NInoAttrList(na->ni)) { + ntfs_attr_put_search_ctx(ctx); + if (ntfs_inode_add_attrlist( + na->ni)) + return -1; + goto retry; + } + if (ntfs_attr_record_move_away(ctx, + 8)) { + ntfs_log_trace("Failed to move " + "attribute to another " + "extent. Aborting..\n"); + err = errno; + goto put_err_out; + } + ntfs_attr_put_search_ctx(ctx); + goto retry; + } + if (!(le32_to_cpu(a->length) - le16_to_cpu( + a->mapping_pairs_offset))) { + ntfs_log_trace("Size of the space " + "allocated for mapping " + "pairs should not be 0." + " Aborting ...\n"); + err = EIO; + goto put_err_out; + } + NAttrSetSparse(na); + a->flags |= ATTR_IS_SPARSE; + a->compression_unit = 4; /* Windows set it so, + even if attribute + is not actually + compressed. */ + memmove((u8*)a + le16_to_cpu(a->name_offset) + + 8, (u8*)a + le16_to_cpu(a->name_offset), + a->name_length * sizeof(ntfschar)); + a->name_offset = cpu_to_le16(le16_to_cpu( + a->name_offset) + 8); + a->mapping_pairs_offset = + cpu_to_le16(le16_to_cpu( + a->mapping_pairs_offset) + 8); + } + /* Attribute no longer sparse. */ + if (!sparse && (a->flags & ATTR_IS_SPARSE) && + !(a->flags & ATTR_IS_COMPRESSED)) { + NAttrClearSparse(na); + a->flags &= ~ATTR_IS_SPARSE; + a->compression_unit = 0; + memmove((u8*)a + le16_to_cpu(a->name_offset) - + 8, (u8*)a + le16_to_cpu(a->name_offset), + a->name_length * sizeof(ntfschar)); + a->name_offset = cpu_to_le16(le16_to_cpu( + a->name_offset) - 8); + a->mapping_pairs_offset = + cpu_to_le16(le16_to_cpu( + a->mapping_pairs_offset) - 8); + } + /* Update compressed size if required. */ + if (sparse) { + s64 new_compr_size; + + new_compr_size = ntfs_rl_get_compressed_size( + na->ni->vol, na->rl); + if (new_compr_size == -1) { + err = errno; + ntfs_log_trace("BUG! Leaving inconstant" + " metadata.\n"); + goto put_err_out; + } + na->compressed_size = new_compr_size; + a->compressed_size = cpu_to_sle64( + new_compr_size); + } + /* + * Set FILE_NAME dirty flag, to update sparse bit and + * allocated size in the index. + */ + if (na->type == AT_DATA && na->name == AT_UNNAMED) { + if (sparse) + na->ni->allocated_size = + na->compressed_size; + else + na->ni->allocated_size = + na->allocated_size; + NInoFileNameSetDirty(na->ni); + } + } + /* Get the size for the rest of mapping pairs array. */ + mp_size = ntfs_get_size_for_mapping_pairs(na->ni->vol, na->rl, + stop_vcn); + if (mp_size <= 0) { + err = errno; + ntfs_log_trace("Get size for mapping pairs failed.\n"); + goto put_err_out; + } + /* + * Determine maximum possible length of mapping pairs, + * if we shall *not* expand space for mapping pairs. + */ + cur_max_mp_size = le32_to_cpu(a->length) - + le16_to_cpu(a->mapping_pairs_offset); + /* + * Determine maximum possible length of mapping pairs in the + * current mft record, if we shall expand space for mapping + * pairs. + */ + exp_max_mp_size = le32_to_cpu(m->bytes_allocated) - + le32_to_cpu(m->bytes_in_use) + cur_max_mp_size; + /* Test mapping pairs for fitting in the current mft record. */ + if (mp_size > exp_max_mp_size) { + /* + * Mapping pairs of $ATTRIBUTE_LIST attribute must fit + * in the base mft record. Try to move out other + * attributes and try again. + */ + if (na->type == AT_ATTRIBUTE_LIST) { + ntfs_attr_put_search_ctx(ctx); + if (ntfs_inode_free_space(na->ni, mp_size - + cur_max_mp_size)) { + if (errno != ENOSPC) + return -1; + ntfs_log_error("Attribute list mapping " + "pairs size to big, " + "can't fit them in the " + "base MFT record. " + "Defragment volume and " + "try once again.\n"); + errno = ENOSPC; + return -1; + } + goto retry; + } + + /* Add attribute list if it isn't present, and retry. */ + if (!NInoAttrList(base_ni)) { + ntfs_attr_put_search_ctx(ctx); + if (ntfs_inode_add_attrlist(base_ni)) { + ntfs_log_trace("Couldn't add attribute " + "list.\n"); + return -1; + } + goto retry; + } + + /* + * Set mapping pairs size to maximum possible for this + * mft record. We shall write the rest of mapping pairs + * to another MFT records. + */ + mp_size = exp_max_mp_size; + } + + /* Change space for mapping pairs if we need it. */ + if (((mp_size + 7) & ~7) != cur_max_mp_size) { + if (ntfs_attr_record_resize(m, a, + le16_to_cpu(a->mapping_pairs_offset) + + mp_size)) { + ntfs_log_error("BUG! Ran out of space in mft " + "record. Please run chkdsk and " + "if that doesn't find any " + "errors please report you saw " + "this message to %s.\n", + NTFS_DEV_LIST); + err = EIO; + goto put_err_out; + } + } + + /* Update lowest vcn. */ + a->lowest_vcn = cpu_to_sle64(stop_vcn); + ntfs_inode_mark_dirty(ctx->ntfs_ino); + if ((ctx->ntfs_ino->nr_extents == -1 || + NInoAttrList(ctx->ntfs_ino)) && + ctx->attr->type != AT_ATTRIBUTE_LIST) { + ctx->al_entry->lowest_vcn = cpu_to_sle64(stop_vcn); + ntfs_attrlist_mark_dirty(ctx->ntfs_ino); + } + + /* + * Generate the new mapping pairs array directly into the + * correct destination, i.e. the attribute record itself. + */ + if (!ntfs_mapping_pairs_build(na->ni->vol, (u8*)a + le16_to_cpu( + a->mapping_pairs_offset), mp_size, na->rl, + stop_vcn, &stop_vcn)) + finished_build = TRUE; + if (!finished_build && errno != ENOSPC) { + err = errno; + ntfs_log_error("BUG! Mapping pairs build failed. " + "Please run chkdsk and if that doesn't " + "find any errors please report you saw " + "this message to %s.\n", NTFS_DEV_LIST); + goto put_err_out; + } + a->highest_vcn = cpu_to_sle64(stop_vcn - 1); + } + /* Check whether error occurred. */ + if (errno != ENOENT) { + err = errno; + ntfs_log_trace("Attribute lookup failed.\n"); + goto put_err_out; + } + + /* Deallocate not used attribute extents and return with success. */ + if (finished_build) { + ntfs_attr_reinit_search_ctx(ctx); + ntfs_log_trace("Deallocate marked extents.\n"); + while (!ntfs_attr_lookup(na->type, na->name, na->name_len, + CASE_SENSITIVE, 0, NULL, 0, ctx)) { + if (sle64_to_cpu(ctx->attr->highest_vcn) != + NTFS_VCN_DELETE_MARK) + continue; + /* Remove unused attribute record. */ + if (ntfs_attr_record_rm(ctx)) { + err = errno; + ntfs_log_trace("Couldn't remove unused " + "attribute record.\n"); + goto put_err_out; + } + ntfs_attr_reinit_search_ctx(ctx); + } + if (errno != ENOENT) { + err = errno; + ntfs_log_trace("Attribute lookup failed.\n"); + goto put_err_out; + } + ntfs_log_trace("Deallocate done.\n"); + ntfs_attr_put_search_ctx(ctx); + goto ok; + } + ntfs_attr_put_search_ctx(ctx); + ctx = NULL; + + /* Allocate new MFT records for the rest of mapping pairs. */ + while (1) { + /* Calculate size of rest mapping pairs. */ + mp_size = ntfs_get_size_for_mapping_pairs(na->ni->vol, + na->rl, stop_vcn); + if (mp_size <= 0) { + err = errno; + ntfs_log_trace("Get size for mapping pairs failed.\n"); + goto put_err_out; + } + /* Allocate new mft record. */ + ni = ntfs_mft_record_alloc(na->ni->vol, base_ni); + if (!ni) { + err = errno; + ntfs_log_trace("Couldn't allocate new MFT record.\n"); + goto put_err_out; + } + m = ni->mrec; + /* + * If mapping size exceed available space, set them to + * possible maximum. + */ + cur_max_mp_size = le32_to_cpu(m->bytes_allocated) - + le32_to_cpu(m->bytes_in_use) - + (offsetof(ATTR_RECORD, compressed_size) + + ((NAttrCompressed(na) || NAttrSparse(na)) ? + sizeof(a->compressed_size) : 0)) - + ((sizeof(ntfschar) * na->name_len + 7) & ~7); + if (mp_size > cur_max_mp_size) + mp_size = cur_max_mp_size; + /* Add attribute extent to new record. */ + err = ntfs_non_resident_attr_record_add(ni, na->type, + na->name, na->name_len, stop_vcn, mp_size, 0); + if (err == -1) { + err = errno; + ntfs_log_trace("Couldn't add attribute extent into the " + "MFT record.\n"); + if (ntfs_mft_record_free(na->ni->vol, ni)) { + ntfs_log_trace("Couldn't free MFT record.\n"); + } + goto put_err_out; + } + a = (ATTR_RECORD*)((u8*)m + err); + + err = ntfs_mapping_pairs_build(na->ni->vol, (u8*)a + + le16_to_cpu(a->mapping_pairs_offset), mp_size, na->rl, + stop_vcn, &stop_vcn); + if (err < 0 && errno != ENOSPC) { + err = errno; + ntfs_log_error("BUG! Mapping pairs build failed. " + "Please run chkdsk and if that doesn't " + "find any errors please report you saw " + "this message to %s.\n", NTFS_DEV_LIST); + if (ntfs_mft_record_free(na->ni->vol, ni)) + ntfs_log_trace("Couldn't free MFT record.\n"); + goto put_err_out; + } + a->highest_vcn = cpu_to_sle64(stop_vcn - 1); + ntfs_inode_mark_dirty(ni); + /* All mapping pairs has been written. */ + if (!err) + break; + } +ok: + ret = 0; +out: + return ret; +put_err_out: + if (ctx) + ntfs_attr_put_search_ctx(ctx); + errno = err; + goto out; +} +#undef NTFS_VCN_DELETE_MARK + +/** + * ntfs_non_resident_attr_shrink - shrink a non-resident, open ntfs attribute + * @na: non-resident ntfs attribute to shrink + * @newsize: new size (in bytes) to which to shrink the attribute + * + * Reduce the size of a non-resident, open ntfs attribute @na to @newsize bytes. + * + * On success return 0 and on error return -1 with errno set to the error code. + * The following error codes are defined: + * ENOMEM - Not enough memory to complete operation. + * ERANGE - @newsize is not valid for the attribute type of @na. + */ +static int ntfs_non_resident_attr_shrink(ntfs_attr *na, const s64 newsize) +{ + ntfs_volume *vol; + ntfs_attr_search_ctx *ctx; + VCN first_free_vcn; + s64 nr_freed_clusters; + int err; + + ntfs_log_trace("Inode 0x%llx attr 0x%x new size %lld\n", (unsigned long long) + na->ni->mft_no, na->type, (long long)newsize); + + vol = na->ni->vol; + + /* + * Check the attribute type and the corresponding minimum size + * against @newsize and fail if @newsize is too small. + */ + if (ntfs_attr_size_bounds_check(vol, na->type, newsize) < 0) { + if (errno == ERANGE) { + ntfs_log_trace("Eeek! Size bounds check failed. " + "Aborting...\n"); + } else if (errno == ENOENT) + errno = EIO; + return -1; + } + + /* The first cluster outside the new allocation. */ + first_free_vcn = (newsize + vol->cluster_size - 1) >> + vol->cluster_size_bits; + /* + * Compare the new allocation with the old one and only deallocate + * clusters if there is a change. + */ + if ((na->allocated_size >> vol->cluster_size_bits) != first_free_vcn) { + if (ntfs_attr_map_whole_runlist(na)) { + ntfs_log_trace("Eeek! ntfs_attr_map_whole_runlist " + "failed.\n"); + return -1; + } + /* Deallocate all clusters starting with the first free one. */ + nr_freed_clusters = ntfs_cluster_free(vol, na, first_free_vcn, + -1); + if (nr_freed_clusters < 0) { + ntfs_log_trace("Eeek! Freeing of clusters failed. " + "Aborting...\n"); + return -1; + } + + /* Truncate the runlist itself. */ + if (ntfs_rl_truncate(&na->rl, first_free_vcn)) { + /* + * Failed to truncate the runlist, so just throw it + * away, it will be mapped afresh on next use. + */ + free(na->rl); + na->rl = NULL; + ntfs_log_trace("Eeek! Run list truncation failed.\n"); + return -1; + } + + /* Prepare to mapping pairs update. */ + na->allocated_size = first_free_vcn << vol->cluster_size_bits; + /* Write mapping pairs for new runlist. */ + if (ntfs_attr_update_mapping_pairs(na, 0 /*first_free_vcn*/)) { + ntfs_log_trace("Eeek! Mapping pairs update failed. " + "Leaving inconstant metadata. " + "Run chkdsk.\n"); + return -1; + } + } + + /* Get the first attribute record. */ + ctx = ntfs_attr_get_search_ctx(na->ni, NULL); + if (!ctx) { + ntfs_log_trace("Couldn't get attribute search context.\n"); + return -1; + } + if (ntfs_attr_lookup(na->type, na->name, na->name_len, CASE_SENSITIVE, + 0, NULL, 0, ctx)) { + err = errno; + if (err == ENOENT) + err = EIO; + ntfs_log_trace("Eeek! Lookup of first attribute extent failed. " + "Leaving inconstant metadata.\n"); + goto put_err_out; + } + + /* Update data and initialized size. */ + na->data_size = newsize; + ctx->attr->data_size = cpu_to_sle64(newsize); + if (newsize < na->initialized_size) { + na->initialized_size = newsize; + ctx->attr->initialized_size = cpu_to_sle64(newsize); + } + /* Update data size in the index. */ + if (na->type == AT_DATA && na->name == AT_UNNAMED) { + na->ni->data_size = na->data_size; + NInoFileNameSetDirty(na->ni); + } + + /* If the attribute now has zero size, make it resident. */ + if (!newsize) { + if (ntfs_attr_make_resident(na, ctx)) { + /* If couldn't make resident, just continue. */ + if (errno != EPERM) + ntfs_log_error("Failed to make attribute " + "resident. Leaving as is...\n"); + } + } + + /* Set the inode dirty so it is written out later. */ + ntfs_inode_mark_dirty(ctx->ntfs_ino); + /* Done! */ + ntfs_attr_put_search_ctx(ctx); + return 0; +put_err_out: + ntfs_attr_put_search_ctx(ctx); + errno = err; + return -1; +} + +/** + * ntfs_non_resident_attr_expand - expand a non-resident, open ntfs attribute + * @na: non-resident ntfs attribute to expand + * @newsize: new size (in bytes) to which to expand the attribute + * + * Expand the size of a non-resident, open ntfs attribute @na to @newsize bytes, + * by allocating new clusters. + * + * On success return 0 and on error return -1 with errno set to the error code. + * The following error codes are defined: + * ENOMEM - Not enough memory to complete operation. + * ERANGE - @newsize is not valid for the attribute type of @na. + * ENOSPC - There is no enough space in base mft to resize $ATTRIBUTE_LIST. + */ +static int ntfs_non_resident_attr_expand(ntfs_attr *na, const s64 newsize) +{ + LCN lcn_seek_from; + VCN first_free_vcn; + ntfs_volume *vol; + ntfs_attr_search_ctx *ctx; + runlist *rl, *rln; + s64 org_alloc_size; + int err; + + ntfs_log_trace("Inode 0x%llx, attr 0x%x, new size %lld old size %lld\n", + (unsigned long long)na->ni->mft_no, na->type, + (long long)newsize, (long long)na->data_size); + + vol = na->ni->vol; + + /* + * Check the attribute type and the corresponding maximum size + * against @newsize and fail if @newsize is too big. + */ + if (ntfs_attr_size_bounds_check(vol, na->type, newsize) < 0) { + if (errno == ERANGE) { + ntfs_log_trace("Eeek! Size bounds check failed. " + "Aborting...\n"); + } else if (errno == ENOENT) + errno = EIO; + return -1; + } + + /* Save for future use. */ + org_alloc_size = na->allocated_size; + /* The first cluster outside the new allocation. */ + first_free_vcn = (newsize + vol->cluster_size - 1) >> + vol->cluster_size_bits; + /* + * Compare the new allocation with the old one and only allocate + * clusters if there is a change. + */ + if ((na->allocated_size >> vol->cluster_size_bits) < first_free_vcn) { + if (ntfs_attr_map_whole_runlist(na)) { + ntfs_log_trace("Eeek! ntfs_attr_map_whole_runlist " + "failed.\n"); + return -1; + } + + /* + * If we extend $DATA attribute on NTFS 3+ volume, we can add + * sparse runs instead of real allocation of clusters. + */ + if (na->type == AT_DATA && vol->major_ver >= 3) { + rl = ntfs_malloc(0x1000); + if (!rl) + return -1; + + rl[0].vcn = (na->allocated_size >> + vol->cluster_size_bits); + rl[0].lcn = LCN_HOLE; + rl[0].length = first_free_vcn - + (na->allocated_size >> vol->cluster_size_bits); + rl[1].vcn = first_free_vcn; + rl[1].lcn = LCN_ENOENT; + rl[1].length = 0; + } else { + /* + * Determine first after last LCN of attribute. + * We will start seek clusters from this LCN to avoid + * fragmentation. If there are no valid LCNs in the + * attribute let the cluster allocator choose the + * starting LCN. + */ + lcn_seek_from = -1; + if (na->rl->length) { + /* Seek to the last run list element. */ + for (rl = na->rl; (rl + 1)->length; rl++) + ; + /* + * If the last LCN is a hole or similar seek + * back to last valid LCN. + */ + while (rl->lcn < 0 && rl != na->rl) + rl--; + /* + * Only set lcn_seek_from it the LCN is valid. + */ + if (rl->lcn >= 0) + lcn_seek_from = rl->lcn + rl->length; + } + + rl = ntfs_cluster_alloc(vol, na->allocated_size >> + vol->cluster_size_bits, first_free_vcn - + (na->allocated_size >> + vol->cluster_size_bits), lcn_seek_from, + DATA_ZONE); + if (!rl) { + ntfs_log_trace("Eeek! Cluster allocation failed.\n"); + return -1; + } + } + + /* Append new clusters to attribute runlist. */ + rln = ntfs_runlists_merge(na->rl, rl); + if (!rln) { + /* Failed, free just allocated clusters. */ + err = errno; + ntfs_log_trace("Eeek! Run list merge failed.\n"); + ntfs_cluster_free_from_rl(vol, rl); + free(rl); + errno = err; + return -1; + } + na->rl = rln; + + /* Prepare to mapping pairs update. */ + na->allocated_size = first_free_vcn << vol->cluster_size_bits; + /* Write mapping pairs for new runlist. */ + if (ntfs_attr_update_mapping_pairs(na, 0 /*na->allocated_size >> + vol->cluster_size_bits*/)) { + err = errno; + ntfs_log_trace("Eeek! Mapping pairs update failed.\n"); + goto rollback; + } + } + + ctx = ntfs_attr_get_search_ctx(na->ni, NULL); + if (!ctx) { + err = errno; + ntfs_log_trace("Failed to get search context.\n"); + if (na->allocated_size == org_alloc_size) { + errno = err; + return -1; + } else + goto rollback; + } + + if (ntfs_attr_lookup(na->type, na->name, na->name_len, CASE_SENSITIVE, + 0, NULL, 0, ctx)) { + err = errno; + ntfs_log_trace("Lookup of first attribute extent failed.\n"); + if (err == ENOENT) + err = EIO; + if (na->allocated_size != org_alloc_size) { + ntfs_attr_put_search_ctx(ctx); + goto rollback; + } else + goto put_err_out; + } + + /* Update data size. */ + na->data_size = newsize; + ctx->attr->data_size = cpu_to_sle64(newsize); + /* Update data size in the index. */ + if (na->type == AT_DATA && na->name == AT_UNNAMED) { + na->ni->data_size = na->data_size; + NInoFileNameSetDirty(na->ni); + } + /* Set the inode dirty so it is written out later. */ + ntfs_inode_mark_dirty(ctx->ntfs_ino); + /* Done! */ + ntfs_attr_put_search_ctx(ctx); + return 0; +rollback: + /* Free allocated clusters. */ + if (ntfs_cluster_free(vol, na, org_alloc_size >> + vol->cluster_size_bits, -1) < 0) { + ntfs_log_trace("Eeek! Leaking clusters. Run chkdsk!\n"); + err = EIO; + } + /* Now, truncate the runlist itself. */ + if (ntfs_rl_truncate(&na->rl, org_alloc_size >> + vol->cluster_size_bits)) { + /* + * Failed to truncate the runlist, so just throw it away, it + * will be mapped afresh on next use. + */ + free(na->rl); + na->rl = NULL; + ntfs_log_trace("Couldn't truncate runlist. Rollback failed.\n"); + } else { + /* Prepare to mapping pairs update. */ + na->allocated_size = org_alloc_size << vol->cluster_size_bits; + /* Restore mapping pairs. */ + if (ntfs_attr_update_mapping_pairs(na, 0 /*na->allocated_size >> + vol->cluster_size_bits*/)) { + ntfs_log_trace("Failed to restore old mapping pairs. " + "Rollback failed.\n"); + } + } + errno = err; + return -1; +put_err_out: + ntfs_attr_put_search_ctx(ctx); + errno = err; + return -1; +} + +/** + * ntfs_attr_truncate - resize an ntfs attribute + * @na: open ntfs attribute to resize + * @newsize: new size (in bytes) to which to resize the attribute + * + * Change the size of an open ntfs attribute @na to @newsize bytes. If the + * attribute is made bigger and the attribute is resident the newly + * "allocated" space is cleared and if the attribute is non-resident the + * newly allocated space is marked as not initialised and no real allocation + * on disk is performed. + * + * On success return 0. + * On error return values are: + * STATUS_RESIDENT_ATTRIBUTE_FILLED_MFT + * STATUS_ERROR - otherwise + * The following error codes are defined: + * EINVAL - Invalid arguments were passed to the function. + * EOPNOTSUPP - The desired resize is not implemented yet. + * EACCES - Encrypted attribute. + */ +int ntfs_attr_truncate(ntfs_attr *na, const s64 newsize) +{ + int ret; + + if (!na || newsize < 0 || + (na->ni->mft_no == FILE_MFT && na->type == AT_DATA)) { + ntfs_log_trace("Invalid arguments passed.\n"); + errno = EINVAL; + return STATUS_ERROR; + } + + ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x, size %lld\n", + (unsigned long long)na->ni->mft_no, na->type, newsize); + + if (na->data_size == newsize) { + ntfs_log_trace("Size is already ok\n"); + return STATUS_OK; + } + /* + * Encrypted attributes are not supported. We return access denied, + * which is what Windows NT4 does, too. + */ + if (NAttrEncrypted(na)) { + errno = EACCES; + ntfs_log_perror("Failed to truncate encrypted attribute"); + return STATUS_ERROR; + } + /* + * TODO: Implement making handling of compressed attributes. + */ + if (NAttrCompressed(na)) { + errno = EOPNOTSUPP; + ntfs_log_perror("Failed to truncate compressed attribute"); + return STATUS_ERROR; + } + if (NAttrNonResident(na)) { + if (newsize > na->data_size) + ret = ntfs_non_resident_attr_expand(na, newsize); + else + ret = ntfs_non_resident_attr_shrink(na, newsize); + } else + ret = ntfs_resident_attr_resize(na, newsize); + /* Update access and change times if needed. */ + if (na->type == AT_DATA || na->type == AT_INDEX_ROOT || + na->type == AT_INDEX_ALLOCATION) + ntfs_inode_update_time(na->ni); + + ntfs_log_trace("Return status %d\n", ret); + return ret; +} + +/** + * ntfs_attr_readall - read the entire data from an ntfs attribute + * @ni: open ntfs inode in which the ntfs attribute resides + * @type: attribute type + * @name: attribute name in little endian Unicode or AT_UNNAMED or NULL + * @name_len: length of attribute @name in Unicode characters (if @name given) + * @data_size: if non-NULL then store here the data size + * + * This function will read the entire content of an ntfs attribute. + * If @name is AT_UNNAMED then look specifically for an unnamed attribute. + * If @name is NULL then the attribute could be either named or not. + * In both those cases @name_len is not used at all. + * + * On success a buffer is allocated with the content of the attribute + * and which needs to be freed when it's not needed anymore. If the + * @data_size parameter is non-NULL then the data size is set there. + * + * On error NULL is returned with errno set to the error code. + */ +void *ntfs_attr_readall(ntfs_inode *ni, const ATTR_TYPES type, + ntfschar *name, u32 name_len, s64 *data_size) +{ + ntfs_attr *na; + void *data, *ret = NULL; + s64 size; + + na = ntfs_attr_open(ni, type, name, name_len); + if (!na) { + ntfs_log_perror("ntfs_attr_open failed"); + return NULL; + } + data = ntfs_malloc(na->data_size); + if (!data) + goto out; + + size = ntfs_attr_pread(na, 0, na->data_size, data); + if (size != na->data_size) { + ntfs_log_perror("ntfs_attr_pread failed"); + free(data); + goto out; + } + ret = data; + if (data_size) + *data_size = size; +out: + ntfs_attr_close(na); + return ret; +} + + + +int ntfs_attr_exist(ntfs_inode *ni, const ATTR_TYPES type, ntfschar *name, + u32 name_len) +{ + ntfs_attr_search_ctx *ctx; + int ret; + + ntfs_log_trace("Entering\n"); + + ctx = ntfs_attr_get_search_ctx(ni, NULL); + if (!ctx) + return 0; + + ret = ntfs_attr_lookup(type, name, name_len, CASE_SENSITIVE, 0, NULL, 0, + ctx); + + ntfs_attr_put_search_ctx(ctx); + + return !ret; +} + diff --git a/libntfs-3g/attrlist.c b/libntfs-3g/attrlist.c new file mode 100644 index 00000000..33cb3e27 --- /dev/null +++ b/libntfs-3g/attrlist.c @@ -0,0 +1,315 @@ +/** + * attrlist.c - Attribute list attribute handling code. Originated from the Linux-NTFS + * project. + * + * Copyright (c) 2004-2005 Anton Altaparmakov + * Copyright (c) 2004-2005 Yura Pakhuchiy + * Copyright (c) 2006 Szabolcs Szakacsits + * + * 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 + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program/include file 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 + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_STRING_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_ERRNO_H +#include +#endif + +#include "types.h" +#include "layout.h" +#include "attrib.h" +#include "attrlist.h" +#include "debug.h" +#include "unistr.h" +#include "logging.h" +#include "misc.h" + +/** + * ntfs_attrlist_need - check whether inode need attribute list + * @ni: opened ntfs inode for which perform check + * + * Check whether all are attributes belong to one MFT record, in that case + * attribute list is not needed. + * + * Return 1 if inode need attribute list, 0 if not, -1 on error with errno set + * to the error code. If function succeed errno set to 0. The following error + * codes are defined: + * EINVAL - Invalid arguments passed to function or attribute haven't got + * attribute list. + */ +int ntfs_attrlist_need(ntfs_inode *ni) +{ + ATTR_LIST_ENTRY *ale; + + if (!ni) { + ntfs_log_trace("Invalid arguments.\n"); + errno = EINVAL; + return -1; + } + + ntfs_log_trace("Entering for inode 0x%llx.\n", (long long) ni->mft_no); + + if (!NInoAttrList(ni)) { + ntfs_log_trace("Inode haven't got attribute list.\n"); + errno = EINVAL; + return -1; + } + + if (!ni->attr_list) { + ntfs_log_trace("Corrupt in-memory struct.\n"); + errno = EINVAL; + return -1; + } + + errno = 0; + ale = (ATTR_LIST_ENTRY *)ni->attr_list; + while ((u8*)ale < ni->attr_list + ni->attr_list_size) { + if (MREF_LE(ale->mft_reference) != ni->mft_no) + return 1; + ale = (ATTR_LIST_ENTRY *)((u8*)ale + le16_to_cpu(ale->length)); + } + return 0; +} + +/** + * ntfs_attrlist_entry_add - add an attribute list attribute entry + * @ni: opened ntfs inode, which contains that attribute + * @attr: attribute record to add to attribute list + * + * Return 0 on success and -1 on error with errno set to the error code. The + * following error codes are defined: + * EINVAL - Invalid arguments passed to function. + * ENOMEM - Not enough memory to allocate necessary buffers. + * EIO - I/O error occurred or damaged filesystem. + * EEXIST - Such attribute already present in attribute list. + */ +int ntfs_attrlist_entry_add(ntfs_inode *ni, ATTR_RECORD *attr) +{ + ATTR_LIST_ENTRY *ale; + MFT_REF mref; + ntfs_attr *na = NULL; + ntfs_attr_search_ctx *ctx; + u8 *new_al; + int entry_len, entry_offset, err; + + ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x.\n", + (long long) ni->mft_no, + (unsigned) le32_to_cpu(attr->type)); + + if (!ni || !attr) { + ntfs_log_trace("Invalid arguments.\n"); + errno = EINVAL; + return -1; + } + + mref = MK_LE_MREF(ni->mft_no, le16_to_cpu(ni->mrec->sequence_number)); + + if (ni->nr_extents == -1) + ni = ni->base_ni; + + if (!NInoAttrList(ni)) { + ntfs_log_trace("Attribute list isn't present.\n"); + errno = ENOENT; + return -1; + } + + /* Determine size and allocate memory for new attribute list. */ + entry_len = (sizeof(ATTR_LIST_ENTRY) + sizeof(ntfschar) * + attr->name_length + 7) & ~7; + new_al = ntfs_calloc(ni->attr_list_size + entry_len); + if (!new_al) + return -1; + + /* Find place for the new entry. */ + ctx = ntfs_attr_get_search_ctx(ni, NULL); + if (!ctx) { + err = errno; + ntfs_log_trace("Failed to obtain attribute search context.\n"); + goto err_out; + } + if (!ntfs_attr_lookup(attr->type, (attr->name_length) ? (ntfschar*) + ((u8*)attr + le16_to_cpu(attr->name_offset)) : + AT_UNNAMED, attr->name_length, CASE_SENSITIVE, + (attr->non_resident) ? le64_to_cpu(attr->lowest_vcn) : + 0, (attr->non_resident) ? NULL : ((u8*)attr + + le16_to_cpu(attr->value_offset)), (attr->non_resident) ? + 0 : le32_to_cpu(attr->value_length), ctx)) { + /* Found some extent, check it to be before new extent. */ + if (ctx->al_entry->lowest_vcn == attr->lowest_vcn) { + err = EEXIST; + ntfs_log_trace("Such attribute already present in the " + "attribute list.\n"); + ntfs_attr_put_search_ctx(ctx); + goto err_out; + } + /* Add new entry after this extent. */ + ale = (ATTR_LIST_ENTRY*)((u8*)ctx->al_entry + + le16_to_cpu(ctx->al_entry->length)); + } else { + /* Check for real errors. */ + if (errno != ENOENT) { + err = errno; + ntfs_log_trace("Attribute lookup failed.\n"); + ntfs_attr_put_search_ctx(ctx); + goto err_out; + } + /* No previous extents found. */ + ale = ctx->al_entry; + } + /* Don't need it anymore, @ctx->al_entry points to @ni->attr_list. */ + ntfs_attr_put_search_ctx(ctx); + + /* Determine new entry offset. */ + entry_offset = ((u8 *)ale - ni->attr_list); + /* Set pointer to new entry. */ + ale = (ATTR_LIST_ENTRY *)(new_al + entry_offset); + /* Zero it to fix valgrind warning. */ + memset(ale, 0, entry_len); + /* Form new entry. */ + ale->type = attr->type; + ale->length = cpu_to_le16(entry_len); + ale->name_length = attr->name_length; + ale->name_offset = offsetof(ATTR_LIST_ENTRY, name); + if (attr->non_resident) + ale->lowest_vcn = attr->lowest_vcn; + else + ale->lowest_vcn = 0; + ale->mft_reference = mref; + ale->instance = attr->instance; + memcpy(ale->name, (u8 *)attr + le16_to_cpu(attr->name_offset), + attr->name_length * sizeof(ntfschar)); + + /* Resize $ATTRIBUTE_LIST to new length. */ + na = ntfs_attr_open(ni, AT_ATTRIBUTE_LIST, AT_UNNAMED, 0); + if (!na) { + err = errno; + ntfs_log_trace("Failed to open $ATTRIBUTE_LIST attribute.\n"); + goto err_out; + } + if (ntfs_attr_truncate(na, ni->attr_list_size + entry_len)) { + err = errno; + ntfs_log_trace("$ATTRIBUTE_LIST resize failed.\n"); + goto err_out; + } + + /* Copy entries from old attribute list to new. */ + memcpy(new_al, ni->attr_list, entry_offset); + memcpy(new_al + entry_offset + entry_len, ni->attr_list + + entry_offset, ni->attr_list_size - entry_offset); + + /* Set new runlist. */ + free(ni->attr_list); + ni->attr_list = new_al; + ni->attr_list_size = ni->attr_list_size + entry_len; + NInoAttrListSetDirty(ni); + /* Done! */ + ntfs_attr_close(na); + return 0; +err_out: + if (na) + ntfs_attr_close(na); + free(new_al); + errno = err; + return -1; +} + +/** + * ntfs_attrlist_entry_rm - remove an attribute list attribute entry + * @ctx: attribute search context describing the attribute list entry + * + * Remove the attribute list entry @ctx->al_entry from the attribute list. + * + * Return 0 on success and -1 on error with errno set to the error code. + */ +int ntfs_attrlist_entry_rm(ntfs_attr_search_ctx *ctx) +{ + u8 *new_al; + int new_al_len; + ntfs_inode *base_ni; + ntfs_attr *na; + ATTR_LIST_ENTRY *ale; + int err; + + if (!ctx || !ctx->ntfs_ino || !ctx->al_entry) { + ntfs_log_trace("Invalid arguments.\n"); + errno = EINVAL; + return -1; + } + + if (ctx->base_ntfs_ino) + base_ni = ctx->base_ntfs_ino; + else + base_ni = ctx->ntfs_ino; + ale = ctx->al_entry; + + ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x, lowest_vcn %lld.\n", + (long long) ctx->ntfs_ino->mft_no, + (unsigned) le32_to_cpu(ctx->al_entry->type), + (long long) le64_to_cpu(ctx->al_entry->lowest_vcn)); + + if (!NInoAttrList(base_ni)) { + ntfs_log_trace("Attribute list isn't present.\n"); + errno = ENOENT; + return -1; + } + + /* Allocate memory for new attribute list. */ + new_al_len = base_ni->attr_list_size - le16_to_cpu(ale->length); + new_al = ntfs_calloc(new_al_len); + if (!new_al) + return -1; + + /* Reisze $ATTRIBUTE_LIST to new length. */ + na = ntfs_attr_open(base_ni, AT_ATTRIBUTE_LIST, AT_UNNAMED, 0); + if (!na) { + err = errno; + ntfs_log_trace("Failed to open $ATTRIBUTE_LIST attribute.\n"); + goto err_out; + } + if (ntfs_attr_truncate(na, new_al_len)) { + err = errno; + ntfs_log_trace("$ATTRIBUTE_LIST resize failed.\n"); + goto err_out; + } + + /* Copy entries from old attribute list to new. */ + memcpy(new_al, base_ni->attr_list, (u8*)ale - base_ni->attr_list); + memcpy(new_al + ((u8*)ale - base_ni->attr_list), (u8*)ale + le16_to_cpu( + ale->length), new_al_len - ((u8*)ale - base_ni->attr_list)); + + /* Set new runlist. */ + free(base_ni->attr_list); + base_ni->attr_list = new_al; + base_ni->attr_list_size = new_al_len; + NInoAttrListSetDirty(base_ni); + /* Done! */ + ntfs_attr_close(na); + return 0; +err_out: + if (na) + ntfs_attr_close(na); + free(new_al); + errno = err; + return -1; +} diff --git a/libntfs-3g/bitmap.c b/libntfs-3g/bitmap.c new file mode 100644 index 00000000..097386c2 --- /dev/null +++ b/libntfs-3g/bitmap.c @@ -0,0 +1,289 @@ +/** + * bitmap.c - Bitmap handling code. Originated from the Linux-NTFS project. + * + * Copyright (c) 2002-2006 Anton Altaparmakov + * Copyright (c) 2004-2005 Richard Russon + * Copyright (c) 2004-2006 Szabolcs Szakacsits + * + * 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 + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program/include file 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 + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STDIO_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif +#ifdef HAVE_ERRNO_H +#include +#endif + +#include "types.h" +#include "attrib.h" +#include "bitmap.h" +#include "debug.h" +#include "logging.h" +#include "misc.h" + +/** + * ntfs_bit_set - set a bit in a field of bits + * @bitmap: field of bits + * @bit: bit to set + * @new_value: value to set bit to (0 or 1) + * + * Set the bit @bit in the @bitmap to @new_value. Ignore all errors. + */ +void ntfs_bit_set(u8 *bitmap, const u64 bit, const u8 new_value) +{ + if (!bitmap || new_value > 1) + return; + if (!new_value) + bitmap[bit >> 3] &= ~(1 << (bit & 7)); + else + bitmap[bit >> 3] |= (1 << (bit & 7)); +} + +/** + * ntfs_bit_get - get value of a bit in a field of bits + * @bitmap: field of bits + * @bit: bit to get + * + * Get and return the value of the bit @bit in @bitmap (0 or 1). + * Return -1 on error. + */ +char ntfs_bit_get(const u8 *bitmap, const u64 bit) +{ + if (!bitmap) + return -1; + return (bitmap[bit >> 3] >> (bit & 7)) & 1; +} + +/** + * ntfs_bit_get_and_set - get value of a bit in a field of bits and set it + * @bitmap: field of bits + * @bit: bit to get/set + * @new_value: value to set bit to (0 or 1) + * + * Return the value of the bit @bit and set it to @new_value (0 or 1). + * Return -1 on error. + */ +char ntfs_bit_get_and_set(u8 *bitmap, const u64 bit, const u8 new_value) +{ + register u8 old_bit, shift; + + if (!bitmap || new_value > 1) + return -1; + shift = bit & 7; + old_bit = (bitmap[bit >> 3] >> shift) & 1; + if (new_value != old_bit) + bitmap[bit >> 3] ^= 1 << shift; + return old_bit; +} + +/** + * ntfs_bitmap_set_bits_in_run - set a run of bits in a bitmap to a value + * @na: attribute containing the bitmap + * @start_bit: first bit to set + * @count: number of bits to set + * @value: value to set the bits to (i.e. 0 or 1) + * + * Set @count bits starting at bit @start_bit in the bitmap described by the + * attribute @na to @value, where @value is either 0 or 1. + * + * On success return 0 and on error return -1 with errno set to the error code. + */ +static int ntfs_bitmap_set_bits_in_run(ntfs_attr *na, s64 start_bit, + s64 count, int value) +{ + s64 bufsize, br; + u8 *buf, *lastbyte_buf; + int bit, firstbyte, lastbyte, lastbyte_pos, tmp, err; + + if (!na || start_bit < 0 || count < 0) { + errno = EINVAL; + return -1; + } + + bit = start_bit & 7; + if (bit) + firstbyte = 1; + else + firstbyte = 0; + + /* Calculate the required buffer size in bytes, capping it at 8kiB. */ + bufsize = ((count - (bit ? 8 - bit : 0) + 7) >> 3) + firstbyte; + if (bufsize > 8192) + bufsize = 8192; + + buf = ntfs_malloc(bufsize); + if (!buf) + return -1; + + /* Depending on @value, zero or set all bits in the allocated buffer. */ + memset(buf, value ? 0xff : 0, bufsize); + + /* If there is a first partial byte... */ + if (bit) { + /* read it in... */ + br = ntfs_attr_pread(na, start_bit >> 3, 1, buf); + if (br != 1) { + free(buf); + errno = EIO; + return -1; + } + /* and set or clear the appropriate bits in it. */ + while ((bit & 7) && count--) { + if (value) + *buf |= 1 << bit++; + else + *buf &= ~(1 << bit++); + } + /* Update @start_bit to the new position. */ + start_bit = (start_bit + 7) & ~7; + } + + /* Loop until @count reaches zero. */ + lastbyte = 0; + lastbyte_buf = NULL; + bit = count & 7; + do { + /* If there is a last partial byte... */ + if (count > 0 && bit) { + lastbyte_pos = ((count + 7) >> 3) + firstbyte; + if (!lastbyte_pos) { + // FIXME: Eeek! BUG! + ntfs_log_trace("Eeek! lastbyte is zero. Leaving " + "inconsistent metadata.\n"); + err = EIO; + goto free_err_out; + } + /* and it is in the currently loaded bitmap window... */ + if (lastbyte_pos <= bufsize) { + lastbyte_buf = buf + lastbyte_pos - 1; + + /* read the byte in... */ + br = ntfs_attr_pread(na, (start_bit + count) >> + 3, 1, lastbyte_buf); + if (br != 1) { + // FIXME: Eeek! We need rollback! (AIA) + ntfs_log_trace("Eeek! Read of last byte " + "failed. Leaving " + "inconsistent metadata.\n"); + err = EIO; + goto free_err_out; + } + /* and set/clear the appropriate bits in it. */ + while (bit && count--) { + if (value) + *lastbyte_buf |= 1 << --bit; + else + *lastbyte_buf &= ~(1 << --bit); + } + /* We don't want to come back here... */ + bit = 0; + /* We have a last byte that we have handled. */ + lastbyte = 1; + } + } + + /* Write the prepared buffer to disk. */ + tmp = (start_bit >> 3) - firstbyte; + br = ntfs_attr_pwrite(na, tmp, bufsize, buf); + if (br != bufsize) { + // FIXME: Eeek! We need rollback! (AIA) + ntfs_log_trace("Eeek! Failed to write buffer to bitmap. " + "Leaving inconsistent metadata.\n"); + err = EIO; + goto free_err_out; + } + + /* Update counters. */ + tmp = (bufsize - firstbyte - lastbyte) << 3; + if (firstbyte) { + firstbyte = 0; + /* + * Re-set the partial first byte so a subsequent write + * of the buffer does not have stale, incorrect bits. + */ + *buf = value ? 0xff : 0; + } + start_bit += tmp; + count -= tmp; + if (bufsize > (tmp = (count + 7) >> 3)) + bufsize = tmp; + + if (lastbyte && count != 0) { + // FIXME: Eeek! BUG! + ntfs_log_trace("Eeek! Last buffer but count is not zero (= " + "%lli). Leaving inconsistent metadata.\n", + (long long)count); + err = EIO; + goto free_err_out; + } + } while (count > 0); + + /* Done! */ + free(buf); + return 0; + +free_err_out: + free(buf); + errno = err; + return -1; +} + +/** + * ntfs_bitmap_set_run - set a run of bits in a bitmap + * @na: attribute containing the bitmap + * @start_bit: first bit to set + * @count: number of bits to set + * + * Set @count bits starting at bit @start_bit in the bitmap described by the + * attribute @na. + * + * On success return 0 and on error return -1 with errno set to the error code. + */ +int ntfs_bitmap_set_run(ntfs_attr *na, s64 start_bit, s64 count) +{ + return ntfs_bitmap_set_bits_in_run(na, start_bit, count, 1); +} + +/** + * ntfs_bitmap_clear_run - clear a run of bits in a bitmap + * @na: attribute containing the bitmap + * @start_bit: first bit to clear + * @count: number of bits to clear + * + * Clear @count bits starting at bit @start_bit in the bitmap described by the + * attribute @na. + * + * On success return 0 and on error return -1 with errno set to the error code. + */ +int ntfs_bitmap_clear_run(ntfs_attr *na, s64 start_bit, s64 count) +{ + ntfs_log_trace("Dealloc from bit 0x%llx, count 0x%llx.\n", + (long long)start_bit, (long long)count); + + return ntfs_bitmap_set_bits_in_run(na, start_bit, count, 0); +} + diff --git a/libntfs-3g/bootsect.c b/libntfs-3g/bootsect.c new file mode 100644 index 00000000..bd9dc69d --- /dev/null +++ b/libntfs-3g/bootsect.c @@ -0,0 +1,273 @@ +/** + * bootsect.c - Boot sector handling code. Originated from the Linux-NTFS project. + * + * Copyright (c) 2000-2006 Anton Altaparmakov + * Copyright (c) 2003-2004 Szabolcs Szakacsits + * Copyright (c) 2005 Yura Pakhuchiy + * + * 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 + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program/include file 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 + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_STDIO_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif +#ifdef HAVE_ERRNO_H +#include +#endif + +#include "compat.h" +#include "bootsect.h" +#include "debug.h" +#include "logging.h" + +/** + * ntfs_boot_sector_is_ntfs - check if buffer contains a valid ntfs boot sector + * @b: buffer containing putative boot sector to analyze + * @silent: if zero, output progress messages to stderr + * + * Check if the buffer @b contains a valid ntfs boot sector. The buffer @b + * must be at least 512 bytes in size. + * + * If @silent is zero, output progress messages to stderr. Otherwise, do not + * output any messages (except when configured with --enable-debug in which + * case warning/debug messages may be displayed). + * + * Return TRUE if @b contains a valid ntfs boot sector and FALSE if not. + */ +BOOL ntfs_boot_sector_is_ntfs(NTFS_BOOT_SECTOR *b, const BOOL silent __attribute__((unused))) +{ + u32 i; + + ntfs_log_debug("\nBeginning bootsector check...\n"); + + /* Calculate the checksum. Note, this is just a simple addition of + all u32 values in the bootsector starting at the beginning and + finishing at the offset of the checksum itself (i.e. not including + the checksum...). */ + if ((void*)b < (void*)&b->checksum) { + u32 *u = (u32 *)b; + u32 *bi = (u32 *)(&b->checksum); + + ntfs_log_debug("Calculating bootsector checksum... "); + + for (i = 0; u < bi; ++u) + i += le32_to_cpup(u); + + if (le32_to_cpu(b->checksum) && le32_to_cpu(b->checksum) != i) + goto not_ntfs; + ntfs_log_debug("OK\n"); + } + + /* Check OEMidentifier is "NTFS " */ + ntfs_log_debug("Checking OEMid... "); + if (b->oem_id != cpu_to_le64(0x202020205346544eULL)) /* "NTFS " */ + goto not_ntfs; + ntfs_log_debug("OK\n"); + + /* Check bytes per sector value is between 256 and 4096. */ + ntfs_log_debug("Checking bytes per sector... "); + if (le16_to_cpu(b->bpb.bytes_per_sector) < 0x100 || + le16_to_cpu(b->bpb.bytes_per_sector) > 0x1000) + goto not_ntfs; + ntfs_log_debug("OK\n"); + + /* Check sectors per cluster value is valid. */ + ntfs_log_debug("Checking sectors per cluster... "); + switch (b->bpb.sectors_per_cluster) { + case 1: case 2: case 4: case 8: case 16: case 32: case 64: case 128: + break; + default: + goto not_ntfs; + } + ntfs_log_debug("OK\n"); + + /* Check the cluster size is not above 65536 bytes. */ + ntfs_log_debug("Checking cluster size... "); + if ((u32)le16_to_cpu(b->bpb.bytes_per_sector) * + b->bpb.sectors_per_cluster > 0x10000) + goto not_ntfs; + ntfs_log_debug("OK\n"); + + /* Check reserved/unused fields are really zero. */ + ntfs_log_debug("Checking reserved fields are zero... "); + if (le16_to_cpu(b->bpb.reserved_sectors) || + le16_to_cpu(b->bpb.root_entries) || + le16_to_cpu(b->bpb.sectors) || + le16_to_cpu(b->bpb.sectors_per_fat) || + le32_to_cpu(b->bpb.large_sectors) || + b->bpb.fats) + goto not_ntfs; + ntfs_log_debug("OK\n"); + + /* Check clusters per file mft record value is valid. */ + ntfs_log_debug("Checking clusters per mft record... "); + if ((u8)b->clusters_per_mft_record < 0xe1 || + (u8)b->clusters_per_mft_record > 0xf7) { + switch (b->clusters_per_mft_record) { + case 1: case 2: case 4: case 8: case 0x10: case 0x20: case 0x40: + break; + default: + goto not_ntfs; + } + } + ntfs_log_debug("OK\n"); + + /* Check clusters per index block value is valid. */ + ntfs_log_debug("Checking clusters per index block... "); + if ((u8)b->clusters_per_index_record < 0xe1 || + (u8)b->clusters_per_index_record > 0xf7) { + switch (b->clusters_per_index_record) { + case 1: case 2: case 4: case 8: case 0x10: case 0x20: case 0x40: + break; + default: + goto not_ntfs; + } + } + ntfs_log_debug("OK\n"); + + if (b->end_of_sector_marker != cpu_to_le16(0xaa55)) + ntfs_log_debug("Warning: Bootsector has invalid end of sector marker.\n"); + + ntfs_log_debug("Bootsector check completed successfully.\n"); + + return TRUE; +not_ntfs: + ntfs_log_debug("FAILED\n"); + ntfs_log_debug("Bootsector check failed. Aborting...\n"); + return FALSE; +} + +/** + * ntfs_boot_sector_parse - setup an ntfs volume from an ntfs boot sector + * @vol: ntfs_volume to setup + * @bs: buffer containing ntfs boot sector to parse + * + * Parse the ntfs bootsector @bs and setup the ntfs volume @vol with the + * obtained values. + * + * Return 0 on success or -1 on error with errno set to the error code EINVAL. + */ +int ntfs_boot_sector_parse(ntfs_volume *vol, const NTFS_BOOT_SECTOR *bs) +{ + u8 sectors_per_cluster; + s8 c; + + /* We return -1 with errno = EINVAL on error. */ + errno = EINVAL; + + vol->sector_size = le16_to_cpu(bs->bpb.bytes_per_sector); + vol->sector_size_bits = ffs(vol->sector_size) - 1; + ntfs_log_debug("SectorSize = 0x%x\n", vol->sector_size); + ntfs_log_debug("SectorSizeBits = %u\n", vol->sector_size_bits); + /* + * The bounds checks on mft_lcn and mft_mirr_lcn (i.e. them being + * below or equal the number_of_clusters) really belong in the + * ntfs_boot_sector_is_ntfs but in this way we can just do this once. + */ + sectors_per_cluster = bs->bpb.sectors_per_cluster; + ntfs_log_debug("NumberOfSectors = %lli\n", sle64_to_cpu(bs->number_of_sectors)); + ntfs_log_debug("SectorsPerCluster = 0x%x\n", sectors_per_cluster); + if (sectors_per_cluster & (sectors_per_cluster - 1)) { + ntfs_log_debug("Error: %s is not a valid NTFS partition! " + "sectors_per_cluster is not a power of 2.\n", + vol->dev->d_name); + return -1; + } + vol->nr_clusters = sle64_to_cpu(bs->number_of_sectors) >> + (ffs(sectors_per_cluster) - 1); + + vol->mft_lcn = sle64_to_cpu(bs->mft_lcn); + vol->mftmirr_lcn = sle64_to_cpu(bs->mftmirr_lcn); + ntfs_log_debug("MFT LCN = 0x%llx\n", vol->mft_lcn); + ntfs_log_debug("MFTMirr LCN = 0x%llx\n", vol->mftmirr_lcn); + if (vol->mft_lcn > vol->nr_clusters || + vol->mftmirr_lcn > vol->nr_clusters) { + ntfs_log_debug("Error: %s is not a valid NTFS partition!\n", + vol->dev->d_name); + ntfs_log_debug("($Mft LCN or $MftMirr LCN is greater than the " + "number of clusters!)\n"); + return -1; + } + vol->cluster_size = sectors_per_cluster * vol->sector_size; + if (vol->cluster_size & (vol->cluster_size - 1)) { + ntfs_log_debug("Error: %s is not a valid NTFS partition! " + "cluster_size is not a power of 2.\n", + vol->dev->d_name); + return -1; + } + vol->cluster_size_bits = ffs(vol->cluster_size) - 1; + /* + * Need to get the clusters per mft record and handle it if it is + * negative. Then calculate the mft_record_size. A value of 0x80 is + * illegal, thus signed char is actually ok! + */ + c = bs->clusters_per_mft_record; + ntfs_log_debug("ClusterSize = 0x%x\n", (unsigned)vol->cluster_size); + ntfs_log_debug("ClusterSizeBits = %u\n", vol->cluster_size_bits); + ntfs_log_debug("ClustersPerMftRecord = 0x%x\n", c); + /* + * When clusters_per_mft_record is negative, it means that it is to + * be taken to be the negative base 2 logarithm of the mft_record_size + * min bytes. Then: + * mft_record_size = 2^(-clusters_per_mft_record) bytes. + */ + if (c < 0) + vol->mft_record_size = 1 << -c; + else + vol->mft_record_size = c << vol->cluster_size_bits; + if (vol->mft_record_size & (vol->mft_record_size - 1)) { + ntfs_log_debug("Error: %s is not a valid NTFS partition! " + "mft_record_size is not a power of 2.\n", + vol->dev->d_name); + return -1; + } + vol->mft_record_size_bits = ffs(vol->mft_record_size) - 1; + ntfs_log_debug("MftRecordSize = 0x%x\n", (unsigned)vol->mft_record_size); + ntfs_log_debug("MftRecordSizeBits = %u\n", vol->mft_record_size_bits); + /* Same as above for INDX record. */ + c = bs->clusters_per_index_record; + ntfs_log_debug("ClustersPerINDXRecord = 0x%x\n", c); + if (c < 0) + vol->indx_record_size = 1 << -c; + else + vol->indx_record_size = c << vol->cluster_size_bits; + vol->indx_record_size_bits = ffs(vol->indx_record_size) - 1; + ntfs_log_debug("INDXRecordSize = 0x%x\n", (unsigned)vol->indx_record_size); + ntfs_log_debug("INDXRecordSizeBits = %u\n", vol->indx_record_size_bits); + /* + * Work out the size of the MFT mirror in number of mft records. If the + * cluster size is less than or equal to the size taken by four mft + * records, the mft mirror stores the first four mft records. If the + * cluster size is bigger than the size taken by four mft records, the + * mft mirror contains as many mft records as will fit into one + * cluster. + */ + if (vol->cluster_size <= 4 * vol->mft_record_size) + vol->mftmirr_size = 4; + else + vol->mftmirr_size = vol->cluster_size / vol->mft_record_size; + return 0; +} diff --git a/libntfs-3g/collate.c b/libntfs-3g/collate.c new file mode 100644 index 00000000..2352423f --- /dev/null +++ b/libntfs-3g/collate.c @@ -0,0 +1,221 @@ +/** + * collate.c - NTFS collation handling. Originated from the Linux-NTFS project. + * + * Copyright (c) 2004 Anton Altaparmakov + * Copyright (c) 2005 Yura Pakhuchiy + * + * 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 + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program/include file 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 + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_STRING_H +#include +#endif + +#include "collate.h" +#include "debug.h" +#include "unistr.h" +#include "logging.h" + +BOOL ntfs_is_collation_rule_supported(COLLATION_RULES cr) +{ + int i; + + /* + * FIXME: At the moment we only support COLLATION_BINARY, + * COLLATION_NTOFS_ULONG and COLLATION_FILE_NAME so we return false + * for everything else. + */ + if (cr != COLLATION_BINARY && cr != COLLATION_NTOFS_ULONG && + cr != COLLATION_FILE_NAME) + return FALSE; + i = le32_to_cpu(cr); + if (((i >= 0) && (i <= 0x02)) || + ((i >= 0x10) && (i <= 0x13))) + return TRUE; + + return FALSE; +} + +/** + * ntfs_collate_binary - Which of two binary objects should be listed first + * @vol: unused + * @data1: + * @data1_len: + * @data2: + * @data2_len: + * + * Description... + * + * Returns: + */ +static int ntfs_collate_binary(ntfs_volume *vol __attribute__((unused)), + const void *data1, const int data1_len, + const void *data2, const int data2_len) +{ + int rc; + + ntfs_log_trace("Entering.\n"); + rc = memcmp(data1, data2, min(data1_len, data2_len)); + if (!rc && (data1_len != data2_len)) { + if (data1_len < data2_len) + rc = -1; + else + rc = 1; + } + ntfs_log_trace("Done, returning %i.\n", rc); + return rc; +} + +/** + * ntfs_collate_ntofs_ulong - Which of two long ints should be listed first + * @vol: unused + * @data1: + * @data1_len: + * @data2: + * @data2_len: + * + * Description... + * + * Returns: + */ +static int ntfs_collate_ntofs_ulong(ntfs_volume *vol __attribute__((unused)), + const void *data1, const int data1_len, + const void *data2, const int data2_len) +{ + int rc; + u32 d1, d2; + + ntfs_log_trace("Entering.\n"); + if (data1_len != data2_len || data1_len != 4) { + ntfs_log_error("data1_len or/and data2_len not equal to 4.\n"); + return NTFS_COLLATION_ERROR; + } + d1 = le32_to_cpup(data1); + d2 = le32_to_cpup(data2); + if (d1 < d2) + rc = -1; + else { + if (d1 == d2) + rc = 0; + else + rc = 1; + } + ntfs_log_trace("Done, returning %i.\n", rc); + return rc; +} + +/** + * ntfs_collate_file_name - Which of two filenames should be listed first + * @vol: + * @data1: + * @data1_len: unused + * @data2: + * @data2_len: unused + * + * Description... + * + * Returns: + */ +static int ntfs_collate_file_name(ntfs_volume *vol, + const void *data1, const int data1_len __attribute__((unused)), + const void *data2, const int data2_len __attribute__((unused))) +{ + int rc; + + ntfs_log_trace("Entering.\n"); + rc = ntfs_file_values_compare(data1, data2, NTFS_COLLATION_ERROR, + IGNORE_CASE, vol->upcase, vol->upcase_len); + if (!rc) + rc = ntfs_file_values_compare(data1, data2, + NTFS_COLLATION_ERROR, CASE_SENSITIVE, + vol->upcase, vol->upcase_len); + ntfs_log_trace("Done, returning %i.\n", rc); + return rc; +} + +typedef int (*ntfs_collate_func_t)(ntfs_volume *, const void *, const int, + const void *, const int); + +static ntfs_collate_func_t ntfs_do_collate0x0[3] = { + ntfs_collate_binary, + ntfs_collate_file_name, + NULL/*ntfs_collate_unicode_string*/, +}; + +static ntfs_collate_func_t ntfs_do_collate0x1[4] = { + ntfs_collate_ntofs_ulong, + NULL/*ntfs_collate_ntofs_sid*/, + NULL/*ntfs_collate_ntofs_security_hash*/, + NULL/*ntfs_collate_ntofs_ulongs*/, +}; + +/** + * ntfs_collate - collate two data items using a specified collation rule + * @vol: ntfs volume to which the data items belong + * @cr: collation rule to use when comparing the items + * @data1: first data item to collate + * @data1_len: length in bytes of @data1 + * @data2: second data item to collate + * @data2_len: length in bytes of @data2 + * + * Collate the two data items @data1 and @data2 using the collation rule @cr + * and return -1, 0, or 1 if @data1 is found, respectively, to collate before, + * to match, or to collate after @data2. + * + * For speed we use the collation rule @cr as an index into two tables of + * function pointers to call the appropriate collation function. + * + * Return NTFS_COLLATION_ERROR if error occurred. + */ +int ntfs_collate(ntfs_volume *vol, COLLATION_RULES cr, + const void *data1, const int data1_len, + const void *data2, const int data2_len) +{ + int i; + + ntfs_log_trace("Entering.\n"); + if (!vol || !data1 || !data2 || data1_len < 0 || data2_len < 0) { + ntfs_log_error("Invalid arguments passed.\n"); + return NTFS_COLLATION_ERROR; + } + /* + * FIXME: At the moment we only support COLLATION_BINARY, + * COLLATION_NTOFS_ULONG and COLLATION_FILE_NAME so we return error + * for everything else. + */ + if (cr != COLLATION_BINARY && cr != COLLATION_NTOFS_ULONG && + cr != COLLATION_FILE_NAME) + goto err; + i = le32_to_cpu(cr); + if (i < 0) + goto err; + if (i <= 0x02) + return ntfs_do_collate0x0[i](vol, data1, data1_len, + data2, data2_len); + if (i < 0x10) + goto err; + i -= 0x10; + if (i <= 3) + return ntfs_do_collate0x1[i](vol, data1, data1_len, + data2, data2_len); +err: + ntfs_log_debug("Unknown collation rule.\n"); + return NTFS_COLLATION_ERROR; +} diff --git a/libntfs-3g/compat.c b/libntfs-3g/compat.c new file mode 100644 index 00000000..4d8c41a4 --- /dev/null +++ b/libntfs-3g/compat.c @@ -0,0 +1,73 @@ +/** + * compat.c - Tweaks for Windows compatibility + * + * Copyright (c) 2002 Richard Russon + * Copyright (c) 2002-2004 Anton Altaparmakov + * + * 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 + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program/include file 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 + */ + +#ifdef WINDOWS + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "compat.h" + +/* TODO: Add check for FFS in the configure script... (AIA) */ + +#ifndef HAVE_FFS +/** + * ffs - Find the first set bit in an int + * @x: + * + * Description... + * + * Returns: + */ +int ffs(int x) +{ + int r = 1; + + if (!x) + return 0; + if (!(x & 0xffff)) { + x >>= 16; + r += 16; + } + if (!(x & 0xff)) { + x >>= 8; + r += 8; + } + if (!(x & 0xf)) { + x >>= 4; + r += 4; + } + if (!(x & 3)) { + x >>= 2; + r += 2; + } + if (!(x & 1)) { + x >>= 1; + r += 1; + } + return r; +} +#endif /* HAVE_FFS */ + +#endif /* WINDOWS */ + diff --git a/libntfs-3g/compress.c b/libntfs-3g/compress.c new file mode 100644 index 00000000..a6be8275 --- /dev/null +++ b/libntfs-3g/compress.c @@ -0,0 +1,552 @@ +/** + * compress.c - Compressed attribute handling code. Originated from the Linux-NTFS + * project. + * + * Copyright (c) 2004-2005 Anton Altaparmakov + * Copyright (c) 2004-2006 Szabolcs Szakacsits + * Copyright (c) 2005 Yura Pakhuchiy + * + * 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 + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program/include file 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 + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_STDIO_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_ERRNO_H +#include +#endif + +#include "attrib.h" +#include "debug.h" +#include "volume.h" +#include "types.h" +#include "layout.h" +#include "runlist.h" +#include "compress.h" +#include "logging.h" +#include "misc.h" + +/** + * enum ntfs_compression_constants - constants used in the compression code + */ +typedef enum { + /* Token types and access mask. */ + NTFS_SYMBOL_TOKEN = 0, + NTFS_PHRASE_TOKEN = 1, + NTFS_TOKEN_MASK = 1, + + /* Compression sub-block constants. */ + NTFS_SB_SIZE_MASK = 0x0fff, + NTFS_SB_SIZE = 0x1000, + NTFS_SB_IS_COMPRESSED = 0x8000, +} ntfs_compression_constants; + +/** + * ntfs_decompress - decompress a compression block into an array of pages + * @dest: buffer to which to write the decompressed data + * @dest_size: size of buffer @dest in bytes + * @cb_start: compression block to decompress + * @cb_size: size of compression block @cb_start in bytes + * + * This decompresses the compression block @cb_start into the destination + * buffer @dest. + * + * @cb_start is a pointer to the compression block which needs decompressing + * and @cb_size is the size of @cb_start in bytes (8-64kiB). + * + * Return 0 if success or -EOVERFLOW on error in the compressed stream. + */ +static int ntfs_decompress(u8 *dest, const u32 dest_size, + u8 *const cb_start, const u32 cb_size) +{ + /* + * Pointers into the compressed data, i.e. the compression block (cb), + * and the therein contained sub-blocks (sb). + */ + u8 *cb_end = cb_start + cb_size; /* End of cb. */ + u8 *cb = cb_start; /* Current position in cb. */ + u8 *cb_sb_start = cb; /* Beginning of the current sb in the cb. */ + u8 *cb_sb_end; /* End of current sb / beginning of next sb. */ + /* Variables for uncompressed data / destination. */ + u8 *dest_end = dest + dest_size; /* End of dest buffer. */ + u8 *dest_sb_start; /* Start of current sub-block in dest. */ + u8 *dest_sb_end; /* End of current sb in dest. */ + /* Variables for tag and token parsing. */ + u8 tag; /* Current tag. */ + int token; /* Loop counter for the eight tokens in tag. */ + + ntfs_log_trace("Entering, cb_size = 0x%x.\n", (unsigned)cb_size); +do_next_sb: + ntfs_log_debug("Beginning sub-block at offset = 0x%x in the cb.\n", + cb - cb_start); + /* + * Have we reached the end of the compression block or the end of the + * decompressed data? The latter can happen for example if the current + * position in the compression block is one byte before its end so the + * first two checks do not detect it. + */ + if (cb == cb_end || !le16_to_cpup((u16*)cb) || dest == dest_end) { + ntfs_log_debug("Completed. Returning success (0).\n"); + return 0; + } + /* Setup offset for the current sub-block destination. */ + dest_sb_start = dest; + dest_sb_end = dest + NTFS_SB_SIZE; + /* Check that we are still within allowed boundaries. */ + if (dest_sb_end > dest_end) + goto return_overflow; + /* Does the minimum size of a compressed sb overflow valid range? */ + if (cb + 6 > cb_end) + goto return_overflow; + /* Setup the current sub-block source pointers and validate range. */ + cb_sb_start = cb; + cb_sb_end = cb_sb_start + (le16_to_cpup((u16*)cb) & NTFS_SB_SIZE_MASK) + + 3; + if (cb_sb_end > cb_end) + goto return_overflow; + /* Now, we are ready to process the current sub-block (sb). */ + if (!(le16_to_cpup((u16*)cb) & NTFS_SB_IS_COMPRESSED)) { + ntfs_log_debug("Found uncompressed sub-block.\n"); + /* This sb is not compressed, just copy it into destination. */ + /* Advance source position to first data byte. */ + cb += 2; + /* An uncompressed sb must be full size. */ + if (cb_sb_end - cb != NTFS_SB_SIZE) + goto return_overflow; + /* Copy the block and advance the source position. */ + memcpy(dest, cb, NTFS_SB_SIZE); + cb += NTFS_SB_SIZE; + /* Advance destination position to next sub-block. */ + dest += NTFS_SB_SIZE; + goto do_next_sb; + } + ntfs_log_debug("Found compressed sub-block.\n"); + /* This sb is compressed, decompress it into destination. */ + /* Forward to the first tag in the sub-block. */ + cb += 2; +do_next_tag: + if (cb == cb_sb_end) { + /* Check if the decompressed sub-block was not full-length. */ + if (dest < dest_sb_end) { + int nr_bytes = dest_sb_end - dest; + + ntfs_log_debug("Filling incomplete sub-block with zeroes.\n"); + /* Zero remainder and update destination position. */ + memset(dest, 0, nr_bytes); + dest += nr_bytes; + } + /* We have finished the current sub-block. */ + goto do_next_sb; + } + /* Check we are still in range. */ + if (cb > cb_sb_end || dest > dest_sb_end) + goto return_overflow; + /* Get the next tag and advance to first token. */ + tag = *cb++; + /* Parse the eight tokens described by the tag. */ + for (token = 0; token < 8; token++, tag >>= 1) { + u16 lg, pt, length, max_non_overlap; + register u16 i; + u8 *dest_back_addr; + + /* Check if we are done / still in range. */ + if (cb >= cb_sb_end || dest > dest_sb_end) + break; + /* Determine token type and parse appropriately.*/ + if ((tag & NTFS_TOKEN_MASK) == NTFS_SYMBOL_TOKEN) { + /* + * We have a symbol token, copy the symbol across, and + * advance the source and destination positions. + */ + *dest++ = *cb++; + /* Continue with the next token. */ + continue; + } + /* + * We have a phrase token. Make sure it is not the first tag in + * the sb as this is illegal and would confuse the code below. + */ + if (dest == dest_sb_start) + goto return_overflow; + /* + * Determine the number of bytes to go back (p) and the number + * of bytes to copy (l). We use an optimized algorithm in which + * we first calculate log2(current destination position in sb), + * which allows determination of l and p in O(1) rather than + * O(n). We just need an arch-optimized log2() function now. + */ + lg = 0; + for (i = dest - dest_sb_start - 1; i >= 0x10; i >>= 1) + lg++; + /* Get the phrase token into i. */ + pt = le16_to_cpup((u16*)cb); + /* + * Calculate starting position of the byte sequence in + * the destination using the fact that p = (pt >> (12 - lg)) + 1 + * and make sure we don't go too far back. + */ + dest_back_addr = dest - (pt >> (12 - lg)) - 1; + if (dest_back_addr < dest_sb_start) + goto return_overflow; + /* Now calculate the length of the byte sequence. */ + length = (pt & (0xfff >> lg)) + 3; + /* Verify destination is in range. */ + if (dest + length > dest_sb_end) + goto return_overflow; + /* The number of non-overlapping bytes. */ + max_non_overlap = dest - dest_back_addr; + if (length <= max_non_overlap) { + /* The byte sequence doesn't overlap, just copy it. */ + memcpy(dest, dest_back_addr, length); + /* Advance destination pointer. */ + dest += length; + } else { + /* + * The byte sequence does overlap, copy non-overlapping + * part and then do a slow byte by byte copy for the + * overlapping part. Also, advance the destination + * pointer. + */ + memcpy(dest, dest_back_addr, max_non_overlap); + dest += max_non_overlap; + dest_back_addr += max_non_overlap; + length -= max_non_overlap; + while (length--) + *dest++ = *dest_back_addr++; + } + /* Advance source position and continue with the next token. */ + cb += 2; + } + /* No tokens left in the current tag. Continue with the next tag. */ + goto do_next_tag; +return_overflow: + errno = EOVERFLOW; + ntfs_log_perror("Failed to decompress file"); + return -1; +} + +/** + * ntfs_is_cb_compressed - internal function, do not use + * + * This is a very specialised function determining if a cb is compressed or + * uncompressed. It is assumed that checking for a sparse cb has already been + * performed and that the cb is not sparse. It makes all sorts of other + * assumptions as well and hence it is not useful anywhere other than where it + * is used at the moment. Please, do not make this function available for use + * outside of compress.c as it is bound to confuse people and not do what they + * want. + * + * Return TRUE on errors so that the error will be detected later on in the + * code. Might be a bit confusing to debug but there really should never be + * errors coming from here. + */ +static BOOL ntfs_is_cb_compressed(ntfs_attr *na, runlist_element *rl, + VCN cb_start_vcn, int cb_clusters) +{ + /* + * The simplest case: the run starting at @cb_start_vcn contains + * @cb_clusters clusters which are all not sparse, thus the cb is not + * compressed. + */ +restart: + cb_clusters -= rl->length - (cb_start_vcn - rl->vcn); + while (cb_clusters > 0) { + /* Go to the next run. */ + rl++; + /* Map the next runlist fragment if it is not mapped. */ + if (rl->lcn < LCN_HOLE || !rl->length) { + cb_start_vcn = rl->vcn; + rl = ntfs_attr_find_vcn(na, rl->vcn); + if (!rl || rl->lcn < LCN_HOLE || !rl->length) + return TRUE; + /* + * If the runs were merged need to deal with the + * resulting partial run so simply restart. + */ + if (rl->vcn < cb_start_vcn) + goto restart; + } + /* If the current run is sparse, the cb is compressed. */ + if (rl->lcn == LCN_HOLE) + return TRUE; + /* If the whole cb is not sparse, it is not compressed. */ + if (rl->length >= cb_clusters) + return FALSE; + cb_clusters -= rl->length; + }; + /* All cb_clusters were not sparse thus the cb is not compressed. */ + return FALSE; +} + +/** + * ntfs_compressed_attr_pread - read from a compressed attribute + * @na: ntfs attribute to read from + * @pos: byte position in the attribute to begin reading from + * @count: number of bytes to read + * @b: output data buffer + * + * NOTE: You probably want to be using attrib.c::ntfs_attr_pread() instead. + * + * This function will read @count bytes starting at offset @pos from the + * compressed ntfs attribute @na into the data buffer @b. + * + * On success, return the number of successfully read bytes. If this number + * is lower than @count this means that the read reached end of file or that + * an error was encountered during the read so that the read is partial. + * 0 means end of file or nothing was read (also return 0 when @count is 0). + * + * On error and nothing has been read, return -1 with errno set appropriately + * to the return code of ntfs_pread(), or to EINVAL in case of invalid + * arguments. + */ +s64 ntfs_compressed_attr_pread(ntfs_attr *na, s64 pos, s64 count, void *b) +{ + s64 br, to_read, ofs, total, total2; + u64 cb_size_mask; + VCN start_vcn, vcn, end_vcn; + ntfs_volume *vol; + runlist_element *rl; + u8 *dest, *cb, *cb_pos, *cb_end; + u32 cb_size; + int err; + unsigned int nr_cbs, cb_clusters; + + ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x, pos 0x%llx, count 0x%llx.\n", + (unsigned long long)na->ni->mft_no, na->type, + (long long)pos, (long long)count); + if (!na || !NAttrCompressed(na) || !na->ni || !na->ni->vol || !b || + pos < 0 || count < 0) { + errno = EINVAL; + return -1; + } + /* + * Encrypted attributes are not supported. We return access denied, + * which is what Windows NT4 does, too. + */ + if (NAttrEncrypted(na)) { + errno = EACCES; + return -1; + } + if (!count) + return 0; + /* Truncate reads beyond end of attribute. */ + if (pos + count > na->data_size) { + if (pos >= na->data_size) { + return 0; + } + count = na->data_size - pos; + } + /* If it is a resident attribute, simply use ntfs_attr_pread(). */ + if (!NAttrNonResident(na)) + return ntfs_attr_pread(na, pos, count, b); + total = total2 = 0; + /* Zero out reads beyond initialized size. */ + if (pos + count > na->initialized_size) { + if (pos >= na->initialized_size) { + memset(b, 0, count); + return count; + } + total2 = pos + count - na->initialized_size; + count -= total2; + memset((u8*)b + count, 0, total2); + } + vol = na->ni->vol; + cb_size = na->compression_block_size; + cb_size_mask = cb_size - 1UL; + cb_clusters = na->compression_block_clusters; + + /* Need a temporary buffer for each loaded compression block. */ + cb = ntfs_malloc(cb_size); + if (!cb) + return -1; + + /* Need a temporary buffer for each uncompressed block. */ + dest = ntfs_malloc(cb_size); + if (!dest) { + free(cb); + return -1; + } + /* + * The first vcn in the first compression block (cb) which we need to + * decompress. + */ + start_vcn = (pos & ~cb_size_mask) >> vol->cluster_size_bits; + /* Offset in the uncompressed cb at which to start reading data. */ + ofs = pos & cb_size_mask; + /* + * The first vcn in the cb after the last cb which we need to + * decompress. + */ + end_vcn = ((pos + count + cb_size - 1) & ~cb_size_mask) >> + vol->cluster_size_bits; + /* Number of compression blocks (cbs) in the wanted vcn range. */ + nr_cbs = (end_vcn - start_vcn) << vol->cluster_size_bits >> + na->compression_block_size_bits; + cb_end = cb + cb_size; +do_next_cb: + nr_cbs--; + cb_pos = cb; + vcn = start_vcn; + start_vcn += cb_clusters; + + /* Check whether the compression block is sparse. */ + rl = ntfs_attr_find_vcn(na, vcn); + if (!rl || rl->lcn < LCN_HOLE) { + free(cb); + free(dest); + if (total) + return total; + /* FIXME: Do we want EIO or the error code? (AIA) */ + errno = EIO; + return -1; + } + if (rl->lcn == LCN_HOLE) { + /* Sparse cb, zero out destination range overlapping the cb. */ + ntfs_log_debug("Found sparse compression block.\n"); + to_read = min(count, cb_size - ofs); + memset(b, 0, to_read); + ofs = 0; + total += to_read; + count -= to_read; + b = (u8*)b + to_read; + } else if (!ntfs_is_cb_compressed(na, rl, vcn, cb_clusters)) { + s64 tdata_size, tinitialized_size; + /* + * Uncompressed cb, read it straight into the destination range + * overlapping the cb. + */ + ntfs_log_debug("Found uncompressed compression block.\n"); + /* + * Read the uncompressed data into the destination buffer. + * NOTE: We cheat a little bit here by marking the attribute as + * not compressed in the ntfs_attr structure so that we can + * read the data by simply using ntfs_attr_pread(). (-8 + * NOTE: we have to modify data_size and initialized_size + * temporarily as well... + */ + to_read = min(count, cb_size - ofs); + ofs += vcn << vol->cluster_size_bits; + NAttrClearCompressed(na); + tdata_size = na->data_size; + tinitialized_size = na->initialized_size; + na->data_size = na->initialized_size = na->allocated_size; + do { + br = ntfs_attr_pread(na, ofs, to_read, b); + if (br < 0) { + err = errno; + na->data_size = tdata_size; + na->initialized_size = tinitialized_size; + NAttrSetCompressed(na); + free(cb); + free(dest); + if (total) + return total; + errno = err; + return br; + } + total += br; + count -= br; + b = (u8*)b + br; + to_read -= br; + ofs += br; + } while (to_read > 0); + na->data_size = tdata_size; + na->initialized_size = tinitialized_size; + NAttrSetCompressed(na); + ofs = 0; + } else { + s64 tdata_size, tinitialized_size; + + /* + * Compressed cb, decompress it into the temporary buffer, then + * copy the data to the destination range overlapping the cb. + */ + ntfs_log_debug("Found compressed compression block.\n"); + /* + * Read the compressed data into the temporary buffer. + * NOTE: We cheat a little bit here by marking the attribute as + * not compressed in the ntfs_attr structure so that we can + * read the raw, compressed data by simply using + * ntfs_attr_pread(). (-8 + * NOTE: We have to modify data_size and initialized_size + * temporarily as well... + */ + to_read = cb_size; + NAttrClearCompressed(na); + tdata_size = na->data_size; + tinitialized_size = na->initialized_size; + na->data_size = na->initialized_size = na->allocated_size; + do { + br = ntfs_attr_pread(na, + (vcn << vol->cluster_size_bits) + + (cb_pos - cb), to_read, cb_pos); + if (br < 0) { + err = errno; + na->data_size = tdata_size; + na->initialized_size = tinitialized_size; + NAttrSetCompressed(na); + free(cb); + free(dest); + if (total) + return total; + errno = err; + return br; + } + cb_pos += br; + to_read -= br; + } while (to_read > 0); + na->data_size = tdata_size; + na->initialized_size = tinitialized_size; + NAttrSetCompressed(na); + /* Just a precaution. */ + if (cb_pos + 2 <= cb_end) + *(u16*)cb_pos = 0; + ntfs_log_debug("Successfully read the compression block.\n"); + if (ntfs_decompress(dest, cb_size, cb, cb_size) < 0) { + err = errno; + free(cb); + free(dest); + if (total) + return total; + errno = err; + return -1; + } + to_read = min(count, cb_size - ofs); + memcpy(b, dest + ofs, to_read); + total += to_read; + count -= to_read; + b = (u8*)b + to_read; + ofs = 0; + } + /* Do we have more work to do? */ + if (nr_cbs) + goto do_next_cb; + /* We no longer need the buffers. */ + free(cb); + free(dest); + /* Return number of bytes read. */ + return total + total2; +} diff --git a/libntfs-3g/debug.c b/libntfs-3g/debug.c new file mode 100644 index 00000000..d154fb0e --- /dev/null +++ b/libntfs-3g/debug.c @@ -0,0 +1,73 @@ +/** + * debug.c - Debugging output functions. Originated from the Linux-NTFS project. + * + * Copyright (c) 2002-2004 Anton Altaparmakov + * Copyright (c) 2004-2006 Szabolcs Szakacsits + * + * 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 + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program/include file 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 + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_ERRNO_H +#include +#endif + +#include "types.h" +#include "runlist.h" +#include "debug.h" +#include "logging.h" + +#ifdef DEBUG +/** + * ntfs_debug_runlist_dump - Dump a runlist. + * @rl: + * + * Description... + * + * Returns: + */ +void ntfs_debug_runlist_dump(const runlist_element *rl) +{ + int i = 0; + const char *lcn_str[5] = { "LCN_HOLE ", "LCN_RL_NOT_MAPPED", + "LCN_ENOENT ", "LCN_EINVAL ", + "LCN_unknown " }; + + ntfs_log_debug("NTFS-fs DEBUG: Dumping runlist (values in hex):\n"); + if (!rl) { + ntfs_log_debug("Run list not present.\n"); + return; + } + ntfs_log_debug("VCN LCN Run length\n"); + do { + LCN lcn = (rl + i)->lcn; + + if (lcn < (LCN)0) { + int idx = -lcn - 1; + + if (idx > -LCN_EINVAL - 1) + idx = 4; + ntfs_log_debug("%-16llx %s %-16llx%s\n", rl[i].vcn, lcn_str[idx], rl[i].length, rl[i].length ? "" : " (runlist end)"); + } else + ntfs_log_debug("%-16llx %-16llx %-16llx%s\n", rl[i].vcn, rl[i].lcn, rl[i].length, rl[i].length ? "" : " (runlist end)"); + } while (rl[i++].length); +} + +#endif + diff --git a/libntfs-3g/device.c b/libntfs-3g/device.c new file mode 100644 index 00000000..afdfbcd2 --- /dev/null +++ b/libntfs-3g/device.c @@ -0,0 +1,734 @@ +/** + * device.c - Low level device io functions. Originated from the Linux-NTFS project. + * + * Copyright (c) 2004-2006 Anton Altaparmakov + * Copyright (c) 2004-2006 Szabolcs Szakacsits + * + * 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 + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program/include file 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 + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_UNISTD_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_STDIO_H +#include +#endif +#ifdef HAVE_SYS_TYPES_H +#include +#endif +#ifdef HAVE_SYS_STAT_H +#include +#endif +#ifdef HAVE_FCNTL_H +#include +#endif +#ifdef HAVE_SYS_IOCTL_H +#include +#endif +#ifdef HAVE_SYS_PARAM_H +#include +#endif +#ifdef HAVE_SYS_MOUNT_H +#include +#endif +#ifdef HAVE_LINUX_FD_H +#include +#endif +#ifdef HAVE_LINUX_HDREG_H +#include +#endif + +#include "types.h" +#include "mst.h" +#include "debug.h" +#include "device.h" +#include "logging.h" +#include "misc.h" + +#if defined(linux) && defined(_IO) && !defined(BLKGETSIZE) +#define BLKGETSIZE _IO(0x12,96) /* Get device size in 512-byte blocks. */ +#endif +#if defined(linux) && defined(_IOR) && !defined(BLKGETSIZE64) +#define BLKGETSIZE64 _IOR(0x12,114,size_t) /* Get device size in bytes. */ +#endif +#if defined(linux) && !defined(HDIO_GETGEO) +#define HDIO_GETGEO 0x0301 /* Get device geometry. */ +#endif +#if defined(linux) && defined(_IO) && !defined(BLKSSZGET) +# define BLKSSZGET _IO(0x12,104) /* Get device sector size in bytes. */ +#endif +#if defined(linux) && defined(_IO) && !defined(BLKBSZSET) +# define BLKBSZSET _IOW(0x12,113,size_t) /* Set device block size in bytes. */ +#endif + +/** + * ntfs_device_alloc - allocate an ntfs device structure and pre-initialize it + * @name: name of the device (must be present) + * @state: initial device state (usually zero) + * @dops: ntfs device operations to use with the device (must be present) + * @priv_data: pointer to private data (optional) + * + * Allocate an ntfs device structure and pre-initialize it with the user- + * specified device operations @dops, device state @state, device name @name, + * and optional private data @priv_data. + * + * Note, @name is copied and can hence be freed after this functions returns. + * + * On success return a pointer to the allocated ntfs device structure and on + * error return NULL with errno set to the error code returned by ntfs_malloc(). + */ +struct ntfs_device *ntfs_device_alloc(const char *name, const long state, + struct ntfs_device_operations *dops, void *priv_data) +{ + struct ntfs_device *dev; + + if (!name) { + errno = EINVAL; + return NULL; + } + + dev = ntfs_malloc(sizeof(struct ntfs_device)); + if (dev) { + if (!(dev->d_name = strdup(name))) { + int eo = errno; + free(dev); + errno = eo; + return NULL; + } + dev->d_ops = dops; + dev->d_state = state; + dev->d_private = priv_data; + } + return dev; +} + +/** + * ntfs_device_free - free an ntfs device structure + * @dev: ntfs device structure to free + * + * Free the ntfs device structure @dev. + * + * Return 0 on success or -1 on error with errno set to the error code. The + * following error codes are defined: + * EINVAL Invalid pointer @dev. + * EBUSY Device is still open. Close it before freeing it! + */ +int ntfs_device_free(struct ntfs_device *dev) +{ + if (!dev) { + errno = EINVAL; + return -1; + } + if (NDevOpen(dev)) { + errno = EBUSY; + return -1; + } + free(dev->d_name); + free(dev); + return 0; +} + +/** + * ntfs_pread - positioned read from disk + * @dev: device to read from + * @pos: position in device to read from + * @count: number of bytes to read + * @b: output data buffer + * + * This function will read @count bytes from device @dev at position @pos into + * the data buffer @b. + * + * On success, return the number of successfully read bytes. If this number is + * lower than @count this means that we have either reached end of file or + * encountered an error during the read so that the read is partial. 0 means + * end of file or nothing to read (@count is 0). + * + * On error and nothing has been read, return -1 with errno set appropriately + * to the return code of either seek, read, or set to EINVAL in case of + * invalid arguments. + */ +s64 ntfs_pread(struct ntfs_device *dev, const s64 pos, s64 count, void *b) +{ + s64 br, total; + struct ntfs_device_operations *dops; + + ntfs_log_trace("Entering for pos 0x%llx, count 0x%llx.\n", pos, count); + if (!b || count < 0 || pos < 0) { + errno = EINVAL; + return -1; + } + if (!count) + return 0; + dops = dev->d_ops; + /* Locate to position. */ + if (dops->seek(dev, pos, SEEK_SET) == (off_t)-1) { + ntfs_log_perror("ntfs_pread: device seek to 0x%llx returned error", + pos); + return -1; + } + /* Read the data. */ + for (total = 0; count; count -= br, total += br) { + br = dops->read(dev, (char*)b + total, count); + /* If everything ok, continue. */ + if (br > 0) + continue; + /* If EOF or error return number of bytes read. */ + if (!br || total) + return total; + /* Nothing read and error, return error status. */ + return br; + } + /* Finally, return the number of bytes read. */ + return total; +} + +/** + * ntfs_pwrite - positioned write to disk + * @dev: device to write to + * @pos: position in file descriptor to write to + * @count: number of bytes to write + * @b: data buffer to write to disk + * + * This function will write @count bytes from data buffer @b to the device @dev + * at position @pos. + * + * On success, return the number of successfully written bytes. If this number + * is lower than @count this means that the write has been interrupted in + * flight or that an error was encountered during the write so that the write + * is partial. 0 means nothing was written (also return 0 when @count is 0). + * + * On error and nothing has been written, return -1 with errno set + * appropriately to the return code of either seek, write, or set + * to EINVAL in case of invalid arguments. + */ +s64 ntfs_pwrite(struct ntfs_device *dev, const s64 pos, s64 count, + const void *b) +{ + s64 written, total, ret = -1; + struct ntfs_device_operations *dops; + + ntfs_log_trace("Entering for pos 0x%llx, count 0x%llx.\n", pos, count); + if (!b || count < 0 || pos < 0) { + errno = EINVAL; + goto out; + } + if (!count) + return 0; + if (NDevReadOnly(dev)) { + errno = EROFS; + goto out; + } + dops = dev->d_ops; + /* Locate to position. */ + if (dops->seek(dev, pos, SEEK_SET) == (off_t)-1) { + ntfs_log_perror("ntfs_pwrite: seek to 0x%llx returned error", + pos); + goto out; + } + NDevSetDirty(dev); + for (total = 0; count; count -= written, total += written) { + written = dops->write(dev, (const char*)b + total, count); + /* If everything ok, continue. */ + if (written > 0) + continue; + /* + * If nothing written or error return number of bytes written. + */ + if (!written || total) + break; + /* Nothing written and error, return error status. */ + total = written; + break; + } + ret = total; +out: + return ret; +} + +/** + * ntfs_mst_pread - multi sector transfer (mst) positioned read + * @dev: device to read from + * @pos: position in file descriptor to read from + * @count: number of blocks to read + * @bksize: size of each block that needs mst deprotecting + * @b: output data buffer + * + * Multi sector transfer (mst) positioned read. This function will read @count + * blocks of size @bksize bytes each from device @dev at position @pos into the + * the data buffer @b. + * + * On success, return the number of successfully read blocks. If this number is + * lower than @count this means that we have reached end of file, that the read + * was interrupted, or that an error was encountered during the read so that + * the read is partial. 0 means end of file or nothing was read (also return 0 + * when @count or @bksize are 0). + * + * On error and nothing was read, return -1 with errno set appropriately to the + * return code of either seek, read, or set to EINVAL in case of invalid + * arguments. + * + * NOTE: If an incomplete multi sector transfer has been detected the magic + * will have been changed to magic_BAAD but no error will be returned. Thus it + * is possible that we return count blocks as being read but that any number + * (between zero and count!) of these blocks is actually subject to a multi + * sector transfer error. This should be detected by the caller by checking for + * the magic being "BAAD". + */ +s64 ntfs_mst_pread(struct ntfs_device *dev, const s64 pos, s64 count, + const u32 bksize, void *b) +{ + s64 br, i; + + if (bksize & (bksize - 1) || bksize % NTFS_BLOCK_SIZE) { + errno = EINVAL; + return -1; + } + /* Do the read. */ + br = ntfs_pread(dev, pos, count * bksize, b); + if (br < 0) + return br; + /* + * Apply fixups to successfully read data, disregarding any errors + * returned from the MST fixup function. This is because we want to + * fixup everything possible and we rely on the fact that the "BAAD" + * magic will be detected later on. + */ + count = br / bksize; + for (i = 0; i < count; ++i) + ntfs_mst_post_read_fixup((NTFS_RECORD*) + ((u8*)b + i * bksize), bksize); + /* Finally, return the number of complete blocks read. */ + return count; +} + +/** + * ntfs_mst_pwrite - multi sector transfer (mst) positioned write + * @dev: device to write to + * @pos: position in file descriptor to write to + * @count: number of blocks to write + * @bksize: size of each block that needs mst protecting + * @b: data buffer to write to disk + * + * Multi sector transfer (mst) positioned write. This function will write + * @count blocks of size @bksize bytes each from data buffer @b to the device + * @dev at position @pos. + * + * On success, return the number of successfully written blocks. If this number + * is lower than @count this means that the write has been interrupted or that + * an error was encountered during the write so that the write is partial. 0 + * means nothing was written (also return 0 when @count or @bksize are 0). + * + * On error and nothing has been written, return -1 with errno set + * appropriately to the return code of either seek, write, or set + * to EINVAL in case of invalid arguments. + * + * NOTE: We mst protect the data, write it, then mst deprotect it using a quick + * deprotect algorithm (no checking). This saves us from making a copy before + * the write and at the same time causes the usn to be incremented in the + * buffer. This conceptually fits in better with the idea that cached data is + * always deprotected and protection is performed when the data is actually + * going to hit the disk and the cache is immediately deprotected again + * simulating an mst read on the written data. This way cache coherency is + * achieved. + */ +s64 ntfs_mst_pwrite(struct ntfs_device *dev, const s64 pos, s64 count, + const u32 bksize, void *b) +{ + s64 written, i; + + if (count < 0 || bksize % NTFS_BLOCK_SIZE) { + errno = EINVAL; + return -1; + } + if (!count) + return 0; + /* Prepare data for writing. */ + for (i = 0; i < count; ++i) { + int err; + + err = ntfs_mst_pre_write_fixup((NTFS_RECORD*) + ((u8*)b + i * bksize), bksize); + if (err < 0) { + /* Abort write at this position. */ + if (!i) + return err; + count = i; + break; + } + } + /* Write the prepared data. */ + written = ntfs_pwrite(dev, pos, count * bksize, b); + /* Quickly deprotect the data again. */ + for (i = 0; i < count; ++i) + ntfs_mst_post_write_fixup((NTFS_RECORD*)((u8*)b + i * bksize)); + if (written <= 0) + return written; + /* Finally, return the number of complete blocks written. */ + return written / bksize; +} + +/** + * ntfs_cluster_read - read ntfs clusters + * @vol: volume to read from + * @lcn: starting logical cluster number + * @count: number of clusters to read + * @b: output data buffer + * + * Read @count ntfs clusters starting at logical cluster number @lcn from + * volume @vol into buffer @b. Return number of clusters read or -1 on error, + * with errno set to the error code. + */ +s64 ntfs_cluster_read(const ntfs_volume *vol, const s64 lcn, const s64 count, + void *b) +{ + s64 br; + + if (!vol || lcn < 0 || count < 0) { + errno = EINVAL; + return -1; + } + if (vol->nr_clusters < lcn + count) { + errno = ESPIPE; + ntfs_log_perror("Trying to read outside of volume " + "(%lld < %lld)", vol->nr_clusters, lcn + count); + return -1; + } + br = ntfs_pread(vol->dev, lcn << vol->cluster_size_bits, + count << vol->cluster_size_bits, b); + if (br < 0) { + ntfs_log_perror("Error reading cluster(s)"); + return br; + } + return br >> vol->cluster_size_bits; +} + +/** + * ntfs_cluster_write - write ntfs clusters + * @vol: volume to write to + * @lcn: starting logical cluster number + * @count: number of clusters to write + * @b: data buffer to write to disk + * + * Write @count ntfs clusters starting at logical cluster number @lcn from + * buffer @b to volume @vol. Return the number of clusters written or -1 on + * error, with errno set to the error code. + */ +s64 ntfs_cluster_write(const ntfs_volume *vol, const s64 lcn, + const s64 count, const void *b) +{ + s64 bw; + + if (!vol || lcn < 0 || count < 0) { + errno = EINVAL; + return -1; + } + if (vol->nr_clusters < lcn + count) { + errno = ESPIPE; + ntfs_log_perror("Trying to write outside of volume " + "(%lld < %lld)", vol->nr_clusters, lcn + count); + return -1; + } + if (!NVolReadOnly(vol)) + bw = ntfs_pwrite(vol->dev, lcn << vol->cluster_size_bits, + count << vol->cluster_size_bits, b); + else + bw = count << vol->cluster_size_bits; + if (bw < 0) { + ntfs_log_perror("Error writing cluster(s)"); + return bw; + } + return bw >> vol->cluster_size_bits; +} + +/** + * ntfs_device_offset_valid - test if a device offset is valid + * @dev: open device + * @ofs: offset to test for validity + * + * Test if the offset @ofs is an existing location on the device described + * by the open device structure @dev. + * + * Return 0 if it is valid and -1 if it is not valid. + */ +static int ntfs_device_offset_valid(struct ntfs_device *dev, s64 ofs) +{ + char ch; + + if (dev->d_ops->seek(dev, ofs, SEEK_SET) >= 0 && + dev->d_ops->read(dev, &ch, 1) == 1) + return 0; + return -1; +} + +/** + * ntfs_device_size_get - return the size of a device in blocks + * @dev: open device + * @block_size: block size in bytes in which to return the result + * + * Return the number of @block_size sized blocks in the device described by the + * open device @dev. + * + * Adapted from e2fsutils-1.19, Copyright (C) 1995 Theodore Ts'o. + * + * On error return -1 with errno set to the error code. + */ +s64 ntfs_device_size_get(struct ntfs_device *dev, int block_size) +{ + s64 high, low; + + if (!dev || block_size <= 0 || (block_size - 1) & block_size) { + errno = EINVAL; + return -1; + } +#ifdef BLKGETSIZE64 + { u64 size; + + if (dev->d_ops->ioctl(dev, BLKGETSIZE64, &size) >= 0) { + ntfs_log_debug("BLKGETSIZE64 nr bytes = %llu (0x%llx)\n", + (unsigned long long)size, + (unsigned long long)size); + return (s64)size / block_size; + } + } +#endif +#ifdef BLKGETSIZE + { unsigned long size; + + if (dev->d_ops->ioctl(dev, BLKGETSIZE, &size) >= 0) { + ntfs_log_debug("BLKGETSIZE nr 512 byte blocks = %lu (0x%lx)\n", + size, size); + return (s64)size * 512 / block_size; + } + } +#endif +#ifdef FDGETPRM + { struct floppy_struct this_floppy; + + if (dev->d_ops->ioctl(dev, FDGETPRM, &this_floppy) >= 0) { + ntfs_log_debug("FDGETPRM nr 512 byte blocks = %lu (0x%lx)\n", + (unsigned long)this_floppy.size, + (unsigned long)this_floppy.size); + return (s64)this_floppy.size * 512 / block_size; + } + } +#endif + /* + * We couldn't figure it out by using a specialized ioctl, + * so do binary search to find the size of the device. + */ + low = 0LL; + for (high = 1024LL; !ntfs_device_offset_valid(dev, high); high <<= 1) + low = high; + while (low < high - 1LL) { + const s64 mid = (low + high) / 2; + + if (!ntfs_device_offset_valid(dev, mid)) + low = mid; + else + high = mid; + } + dev->d_ops->seek(dev, 0LL, SEEK_SET); + return (low + 1LL) / block_size; +} + +/** + * ntfs_device_partition_start_sector_get - get starting sector of a partition + * @dev: open device + * + * On success, return the starting sector of the partition @dev in the parent + * block device of @dev. On error return -1 with errno set to the error code. + * + * The following error codes are defined: + * EINVAL Input parameter error + * EOPNOTSUPP System does not support HDIO_GETGEO ioctl + * ENOTTY @dev is a file or a device not supporting HDIO_GETGEO + */ +s64 ntfs_device_partition_start_sector_get(struct ntfs_device *dev) +{ + if (!dev) { + errno = EINVAL; + return -1; + } +#ifdef HDIO_GETGEO + { struct hd_geometry geo; + + if (!dev->d_ops->ioctl(dev, HDIO_GETGEO, &geo)) { + ntfs_log_debug("HDIO_GETGEO start_sect = %lu (0x%lx)\n", + geo.start, geo.start); + return geo.start; + } + } +#else + errno = EOPNOTSUPP; +#endif + return -1; +} + +/** + * ntfs_device_heads_get - get number of heads of device + * @dev: open device + * + * On success, return the number of heads on the device @dev. On error return + * -1 with errno set to the error code. + * + * The following error codes are defined: + * EINVAL Input parameter error + * EOPNOTSUPP System does not support HDIO_GETGEO ioctl + * ENOTTY @dev is a file or a device not supporting HDIO_GETGEO + */ +int ntfs_device_heads_get(struct ntfs_device *dev) +{ + if (!dev) { + errno = EINVAL; + return -1; + } +#ifdef HDIO_GETGEO + { struct hd_geometry geo; + + if (!dev->d_ops->ioctl(dev, HDIO_GETGEO, &geo)) { + ntfs_log_debug("HDIO_GETGEO heads = %u (0x%x)\n", + (unsigned)geo.heads, + (unsigned)geo.heads); + return geo.heads; + } + } +#else + errno = EOPNOTSUPP; +#endif + return -1; +} + +/** + * ntfs_device_sectors_per_track_get - get number of sectors per track of device + * @dev: open device + * + * On success, return the number of sectors per track on the device @dev. On + * error return -1 with errno set to the error code. + * + * The following error codes are defined: + * EINVAL Input parameter error + * EOPNOTSUPP System does not support HDIO_GETGEO ioctl + * ENOTTY @dev is a file or a device not supporting HDIO_GETGEO + */ +int ntfs_device_sectors_per_track_get(struct ntfs_device *dev) +{ + if (!dev) { + errno = EINVAL; + return -1; + } +#ifdef HDIO_GETGEO + { struct hd_geometry geo; + + if (!dev->d_ops->ioctl(dev, HDIO_GETGEO, &geo)) { + ntfs_log_debug("HDIO_GETGEO sectors_per_track = %u (0x%x)\n", + (unsigned)geo.sectors, + (unsigned)geo.sectors); + return geo.sectors; + } + } +#else + errno = EOPNOTSUPP; +#endif + return -1; +} + +/** + * ntfs_device_sector_size_get - get sector size of a device + * @dev: open device + * + * On success, return the sector size in bytes of the device @dev. + * On error return -1 with errno set to the error code. + * + * The following error codes are defined: + * EINVAL Input parameter error + * EOPNOTSUPP System does not support BLKSSZGET ioctl + * ENOTTY @dev is a file or a device not supporting BLKSSZGET + */ +int ntfs_device_sector_size_get(struct ntfs_device *dev) +{ + if (!dev) { + errno = EINVAL; + return -1; + } +#ifdef BLKSSZGET + { + int sect_size = 0; + + if (!dev->d_ops->ioctl(dev, BLKSSZGET, §_size)) { + ntfs_log_debug("BLKSSZGET sector size = %d bytes\n", + sect_size); + return sect_size; + } + } +#else + errno = EOPNOTSUPP; +#endif + return -1; +} + +/** + * ntfs_device_block_size_set - set block size of a device + * @dev: open device + * @block_size: block size to set @dev to + * + * On success, return 0. + * On error return -1 with errno set to the error code. + * + * The following error codes are defined: + * EINVAL Input parameter error + * EOPNOTSUPP System does not support BLKBSZSET ioctl + * ENOTTY @dev is a file or a device not supporting BLKBSZSET + */ +int ntfs_device_block_size_set(struct ntfs_device *dev, + int block_size __attribute__((unused))) +{ + if (!dev) { + errno = EINVAL; + return -1; + } +#ifdef BLKBSZSET + { + size_t s_block_size = block_size; + if (!dev->d_ops->ioctl(dev, BLKBSZSET, &s_block_size)) { + ntfs_log_debug("Used BLKBSZSET to set block size to " + "%d bytes.\n", block_size); + return 0; + } + /* If not a block device, pretend it was successful. */ + if (!NDevBlock(dev)) + return 0; + } +#else + /* If not a block device, pretend it was successful. */ + if (!NDevBlock(dev)) + return 0; + errno = EOPNOTSUPP; +#endif + return -1; +} diff --git a/libntfs-3g/device_io.c b/libntfs-3g/device_io.c new file mode 100644 index 00000000..fc361b53 --- /dev/null +++ b/libntfs-3g/device_io.c @@ -0,0 +1,38 @@ +/* + * device_io.c - Default device io operations. Originated from the Linux-NTFS project. + * + * Copyright (c) 2003 Anton Altaparmakov + * + * 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 + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program/include file 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" + +#ifndef NO_NTFS_DEVICE_DEFAULT_IO_OPS + +#ifndef __CYGWIN32__ + +/* Not on Cygwin; use standard Unix style low level device operations. */ +#include "unix_io.c" + +#else /* __CYGWIN32__ */ + +/* On Cygwin; use Win32 low level device operations. */ +#include "win32_io.c" + +#endif /* __CYGWIN32__ */ + +#endif /* NO_NTFS_DEVICE_DEFAULT_IO_OPS */ diff --git a/libntfs-3g/dir.c b/libntfs-3g/dir.c new file mode 100644 index 00000000..328a7bfb --- /dev/null +++ b/libntfs-3g/dir.c @@ -0,0 +1,1655 @@ +/** + * dir.c - Directory handling code. Originated from the Linux-NTFS project. + * + * Copyright (c) 2002-2005 Anton Altaparmakov + * Copyright (c) 2004-2005 Richard Russon + * Copyright (c) 2004-2006 Szabolcs Szakacsits + * Copyright (c) 2005-2006 Yura Pakhuchiy + * + * 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 + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program/include file 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 + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif +#ifdef HAVE_SYS_STAT_H +#include +#endif + +#ifdef HAVE_SYS_SYSMACROS_H +#include +#endif + +#include "types.h" +#include "debug.h" +#include "attrib.h" +#include "inode.h" +#include "dir.h" +#include "volume.h" +#include "mft.h" +#include "index.h" +#include "ntfstime.h" +#include "lcnalloc.h" +#include "logging.h" +#include "misc.h" +#include "security.h" + +/* + * The little endian Unicode strings "$I30", "$SII", "$SDH", "$O" + * and "$Q" as global constants. + */ +ntfschar NTFS_INDEX_I30[5] = { const_cpu_to_le16('$'), const_cpu_to_le16('I'), + const_cpu_to_le16('3'), const_cpu_to_le16('0'), + const_cpu_to_le16('\0') }; +ntfschar NTFS_INDEX_SII[5] = { const_cpu_to_le16('$'), const_cpu_to_le16('S'), + const_cpu_to_le16('I'), const_cpu_to_le16('I'), + const_cpu_to_le16('\0') }; +ntfschar NTFS_INDEX_SDH[5] = { const_cpu_to_le16('$'), const_cpu_to_le16('S'), + const_cpu_to_le16('D'), const_cpu_to_le16('H'), + const_cpu_to_le16('\0') }; +ntfschar NTFS_INDEX_O[3] = { const_cpu_to_le16('$'), const_cpu_to_le16('O'), + const_cpu_to_le16('\0') }; +ntfschar NTFS_INDEX_Q[3] = { const_cpu_to_le16('$'), const_cpu_to_le16('Q'), + const_cpu_to_le16('\0') }; +ntfschar NTFS_INDEX_R[3] = { const_cpu_to_le16('$'), const_cpu_to_le16('R'), + const_cpu_to_le16('\0') }; + +/** + * ntfs_inode_lookup_by_name - find an inode in a directory given its name + * @dir_ni: ntfs inode of the directory in which to search for the name + * @uname: Unicode name for which to search in the directory + * @uname_len: length of the name @uname in Unicode characters + * + * Look for an inode with name @uname in the directory with inode @dir_ni. + * ntfs_inode_lookup_by_name() walks the contents of the directory looking for + * the Unicode name. If the name is found in the directory, the corresponding + * inode number (>= 0) is returned as a mft reference in cpu format, i.e. it + * is a 64-bit number containing the sequence number. + * + * On error, return -1 with errno set to the error code. If the inode is is not + * found errno is ENOENT. + * + * Note, @uname_len does not include the (optional) terminating NULL character. + * + * Note, we look for a case sensitive match first but we also look for a case + * insensitive match at the same time. If we find a case insensitive match, we + * save that for the case that we don't find an exact match, where we return + * the mft reference of the case insensitive match. + * + * If the volume is mounted with the case sensitive flag set, then we only + * allow exact matches. + */ +u64 ntfs_inode_lookup_by_name(ntfs_inode *dir_ni, const ntfschar *uname, + const int uname_len) +{ + VCN vcn; + u64 mref = 0; + s64 br; + ntfs_volume *vol = dir_ni->vol; + ntfs_attr_search_ctx *ctx; + INDEX_ROOT *ir; + INDEX_ENTRY *ie; + INDEX_ALLOCATION *ia; + u8 *index_end; + ntfs_attr *ia_na; + int eo, rc; + u32 index_block_size, index_vcn_size; + u8 index_vcn_size_bits; + + ntfs_log_trace("Entering\n"); + + if (!dir_ni || !dir_ni->mrec || !uname || uname_len <= 0) { + errno = EINVAL; + return -1; + } + + ctx = ntfs_attr_get_search_ctx(dir_ni, NULL); + if (!ctx) + return -1; + + /* Find the index root attribute in the mft record. */ + if (ntfs_attr_lookup(AT_INDEX_ROOT, NTFS_INDEX_I30, 4, CASE_SENSITIVE, 0, NULL, + 0, ctx)) { + ntfs_log_perror("Index root attribute missing in directory inode " + "0x%llx", (unsigned long long)dir_ni->mft_no); + goto put_err_out; + } + /* Get to the index root value. */ + ir = (INDEX_ROOT*)((u8*)ctx->attr + + le16_to_cpu(ctx->attr->value_offset)); + index_block_size = le32_to_cpu(ir->index_block_size); + if (index_block_size < NTFS_BLOCK_SIZE || + index_block_size & (index_block_size - 1)) { + ntfs_log_debug("Index block size %u is invalid.\n", + (unsigned)index_block_size); + goto put_err_out; + } + index_end = (u8*)&ir->index + le32_to_cpu(ir->index.index_length); + /* The first index entry. */ + ie = (INDEX_ENTRY*)((u8*)&ir->index + + le32_to_cpu(ir->index.entries_offset)); + /* + * Loop until we exceed valid memory (corruption case) or until we + * reach the last entry. + */ + for (;; ie = (INDEX_ENTRY*)((u8*)ie + le16_to_cpu(ie->length))) { + /* Bounds checks. */ + if ((u8*)ie < (u8*)ctx->mrec || (u8*)ie + + sizeof(INDEX_ENTRY_HEADER) > index_end || + (u8*)ie + le16_to_cpu(ie->key_length) > + index_end) + goto put_err_out; + /* + * The last entry cannot contain a name. It can however contain + * a pointer to a child node in the B+tree so we just break out. + */ + if (ie->ie_flags & INDEX_ENTRY_END) + break; + /* + * Not a perfect match, need to do full blown collation so we + * know which way in the B+tree we have to go. + */ + rc = ntfs_names_collate(uname, uname_len, + (ntfschar*)&ie->key.file_name.file_name, + ie->key.file_name.file_name_length, 1, + IGNORE_CASE, vol->upcase, vol->upcase_len); + /* + * If uname collates before the name of the current entry, there + * is definitely no such name in this index but we might need to + * descend into the B+tree so we just break out of the loop. + */ + if (rc == -1) + break; + /* The names are not equal, continue the search. */ + if (rc) + continue; + /* + * Names match with case insensitive comparison, now try the + * case sensitive comparison, which is required for proper + * collation. + */ + rc = ntfs_names_collate(uname, uname_len, + (ntfschar*)&ie->key.file_name.file_name, + ie->key.file_name.file_name_length, 1, + CASE_SENSITIVE, vol->upcase, vol->upcase_len); + if (rc == -1) + break; + if (rc) + continue; + /* + * Perfect match, this will never happen as the + * ntfs_are_names_equal() call will have gotten a match but we + * still treat it correctly. + */ + mref = le64_to_cpu(ie->indexed_file); + ntfs_attr_put_search_ctx(ctx); + return mref; + } + /* + * We have finished with this index without success. Check for the + * presence of a child node and if not present return error code + * ENOENT, unless we have got the mft reference of a matching name + * cached in mref in which case return mref. + */ + if (!(ie->ie_flags & INDEX_ENTRY_NODE)) { + ntfs_attr_put_search_ctx(ctx); + if (mref) + return mref; + ntfs_log_debug("Entry not found.\n"); + errno = ENOENT; + return -1; + } /* Child node present, descend into it. */ + + /* Open the index allocation attribute. */ + ia_na = ntfs_attr_open(dir_ni, AT_INDEX_ALLOCATION, NTFS_INDEX_I30, 4); + if (!ia_na) { + ntfs_log_perror("Failed to open index allocation attribute. Directory " + "inode 0x%llx is corrupt or driver bug", + (unsigned long long)dir_ni->mft_no); + goto put_err_out; + } + + /* Allocate a buffer for the current index block. */ + ia = ntfs_malloc(index_block_size); + if (!ia) { + ntfs_attr_close(ia_na); + goto put_err_out; + } + + /* Determine the size of a vcn in the directory index. */ + if (vol->cluster_size <= index_block_size) { + index_vcn_size = vol->cluster_size; + index_vcn_size_bits = vol->cluster_size_bits; + } else { + index_vcn_size = vol->sector_size; + index_vcn_size_bits = vol->sector_size_bits; + } + + /* Get the starting vcn of the index_block holding the child node. */ + vcn = sle64_to_cpup((u8*)ie + le16_to_cpu(ie->length) - 8); + +descend_into_child_node: + + /* Read the index block starting at vcn. */ + br = ntfs_attr_mst_pread(ia_na, vcn << index_vcn_size_bits, 1, + index_block_size, ia); + if (br != 1) { + if (br != -1) + errno = EIO; + ntfs_log_perror("Failed to read vcn 0x%llx", vcn); + goto close_err_out; + } + + if (sle64_to_cpu(ia->index_block_vcn) != vcn) { + ntfs_log_debug("Actual VCN (0x%llx) of index buffer is different " + "from expected VCN (0x%llx).\n", + (long long)sle64_to_cpu(ia->index_block_vcn), + (long long)vcn); + errno = EIO; + goto close_err_out; + } + if (le32_to_cpu(ia->index.allocated_size) + 0x18 != index_block_size) { + ntfs_log_debug("Index buffer (VCN 0x%llx) of directory inode 0x%llx " + "has a size (%u) differing from the directory " + "specified size (%u).\n", (long long)vcn, + (unsigned long long)dir_ni->mft_no, + (unsigned) le32_to_cpu(ia->index.allocated_size) + 0x18, + (unsigned)index_block_size); + errno = EIO; + goto close_err_out; + } + index_end = (u8*)&ia->index + le32_to_cpu(ia->index.index_length); + if (index_end > (u8*)ia + index_block_size) { + ntfs_log_debug("Size of index buffer (VCN 0x%llx) of directory inode " + "0x%llx exceeds maximum size.\n", + (long long)vcn, (unsigned long long)dir_ni->mft_no); + errno = EIO; + goto close_err_out; + } + + /* The first index entry. */ + ie = (INDEX_ENTRY*)((u8*)&ia->index + + le32_to_cpu(ia->index.entries_offset)); + /* + * Iterate similar to above big loop but applied to index buffer, thus + * loop until we exceed valid memory (corruption case) or until we + * reach the last entry. + */ + for (;; ie = (INDEX_ENTRY*)((u8*)ie + le16_to_cpu(ie->length))) { + /* Bounds check. */ + if ((u8*)ie < (u8*)ia || (u8*)ie + + sizeof(INDEX_ENTRY_HEADER) > index_end || + (u8*)ie + le16_to_cpu(ie->key_length) > + index_end) { + ntfs_log_debug("Index entry out of bounds in directory " + "inode 0x%llx.\n", + (unsigned long long)dir_ni->mft_no); + errno = EIO; + goto close_err_out; + } + /* + * The last entry cannot contain a name. It can however contain + * a pointer to a child node in the B+tree so we just break out. + */ + if (ie->ie_flags & INDEX_ENTRY_END) + break; + /* + * Not a perfect match, need to do full blown collation so we + * know which way in the B+tree we have to go. + */ + rc = ntfs_names_collate(uname, uname_len, + (ntfschar*)&ie->key.file_name.file_name, + ie->key.file_name.file_name_length, 1, + IGNORE_CASE, vol->upcase, vol->upcase_len); + /* + * If uname collates before the name of the current entry, there + * is definitely no such name in this index but we might need to + * descend into the B+tree so we just break out of the loop. + */ + if (rc == -1) + break; + /* The names are not equal, continue the search. */ + if (rc) + continue; + /* + * Names match with case insensitive comparison, now try the + * case sensitive comparison, which is required for proper + * collation. + */ + rc = ntfs_names_collate(uname, uname_len, + (ntfschar*)&ie->key.file_name.file_name, + ie->key.file_name.file_name_length, 1, + CASE_SENSITIVE, vol->upcase, vol->upcase_len); + if (rc == -1) + break; + if (rc) + continue; + + mref = le64_to_cpu(ie->indexed_file); + free(ia); + ntfs_attr_close(ia_na); + ntfs_attr_put_search_ctx(ctx); + return mref; + } + /* + * We have finished with this index buffer without success. Check for + * the presence of a child node. + */ + if (ie->ie_flags & INDEX_ENTRY_NODE) { + if ((ia->index.ih_flags & NODE_MASK) == LEAF_NODE) { + ntfs_log_debug("Index entry with child node found in a leaf " + "node in directory inode 0x%llx.\n", + (unsigned long long)dir_ni->mft_no); + errno = EIO; + goto close_err_out; + } + /* Child node present, descend into it. */ + vcn = sle64_to_cpup((u8*)ie + le16_to_cpu(ie->length) - 8); + if (vcn >= 0) + goto descend_into_child_node; + ntfs_log_debug("Negative child node vcn in directory inode " + "0x%llx.\n", (unsigned long long)dir_ni->mft_no); + errno = EIO; + goto close_err_out; + } + free(ia); + ntfs_attr_close(ia_na); + ntfs_attr_put_search_ctx(ctx); + /* + * No child node present, return error code ENOENT, unless we have got + * the mft reference of a matching name cached in mref in which case + * return mref. + */ + if (mref) + return mref; + ntfs_log_debug("Entry not found.\n"); + errno = ENOENT; + return -1; +put_err_out: + eo = EIO; + ntfs_log_debug("Corrupt directory. Aborting lookup.\n"); +eo_put_err_out: + ntfs_attr_put_search_ctx(ctx); + errno = eo; + return -1; +close_err_out: + eo = errno; + free(ia); + ntfs_attr_close(ia_na); + goto eo_put_err_out; +} + +/** + * ntfs_pathname_to_inode - Find the inode which represents the given pathname + * @vol: An ntfs volume obtained from ntfs_mount + * @parent: A directory inode to begin the search (may be NULL) + * @pathname: Pathname to be located + * + * Take an ASCII pathname and find the inode that represents it. The function + * splits the path and then descends the directory tree. If @parent is NULL, + * then the root directory '.' will be used as the base for the search. + * + * Return: inode Success, the pathname was valid + * NULL Error, the pathname was invalid, or some other error occurred + */ +ntfs_inode *ntfs_pathname_to_inode(ntfs_volume *vol, ntfs_inode *parent, + const char *pathname) +{ + u64 inum; + int len, err = 0; + char *p, *q; + ntfs_inode *ni; + ntfs_inode *result = NULL; + ntfschar *unicode = NULL; + char *ascii = NULL; + + if (!vol || !pathname) { + errno = EINVAL; + return NULL; + } + + ntfs_log_trace("path: '%s'\n", pathname); + + if (parent) { + ni = parent; + } else { + ni = ntfs_inode_open(vol, FILE_root); + if (!ni) { + ntfs_log_debug("Couldn't open the inode of the root " + "directory.\n"); + err = EIO; + goto close; + } + } + + unicode = ntfs_calloc(MAX_PATH); + ascii = strdup(pathname); + if (!unicode || !ascii) { + ntfs_log_debug("Out of memory.\n"); + err = ENOMEM; + goto close; + } + + p = ascii; + /* Remove leading /'s. */ + while (p && *p && *p == PATH_SEP) + p++; + while (p && *p) { + /* Find the end of the first token. */ + q = strchr(p, PATH_SEP); + if (q != NULL) { + *q = '\0'; + q++; + } + + len = ntfs_mbstoucs(p, &unicode, NTFS_MAX_NAME_LEN); + if (len < 0) { + ntfs_log_debug("Couldn't convert name to Unicode: %s.\n", p); + err = errno; + goto close; + } + + inum = ntfs_inode_lookup_by_name(ni, unicode, len); + if (inum == (u64) -1) { + ntfs_log_debug("Couldn't find name '%s' in pathname " + "'%s'.\n", p, pathname); + err = ENOENT; + goto close; + } + + if (ni != parent) + ntfs_inode_close(ni); + + inum = MREF(inum); + ni = ntfs_inode_open(vol, inum); + if (!ni) { + ntfs_log_debug("Cannot open inode %llu: %s.\n", + (unsigned long long)inum, p); + err = EIO; + goto close; + } + + p = q; + while (p && *p && *p == PATH_SEP) + p++; + } + + result = ni; + ni = NULL; +close: + if (ni && (ni != parent)) + ntfs_inode_close(ni); + free(ascii); + free(unicode); + if (err) + errno = err; + return result; +} + +/* + * The little endian Unicode string ".." for ntfs_readdir(). + */ +static const ntfschar dotdot[3] = { const_cpu_to_le16('.'), + const_cpu_to_le16('.'), + const_cpu_to_le16('\0') }; + +/* + * union index_union - + * More helpers for ntfs_readdir(). + */ +typedef union { + INDEX_ROOT *ir; + INDEX_ALLOCATION *ia; +} index_union __attribute__((__transparent_union__)); + +/** + * enum INDEX_TYPE - + * More helpers for ntfs_readdir(). + */ +typedef enum { + INDEX_TYPE_ROOT, /* index root */ + INDEX_TYPE_ALLOCATION, /* index allocation */ +} INDEX_TYPE; + +/** + * ntfs_filldir - ntfs specific filldir method + * @dir_ni: ntfs inode of current directory + * @pos: current position in directory + * @ivcn_bits: log(2) of index vcn size + * @index_type: specifies whether @iu is an index root or an index allocation + * @iu: index root or index block to which @ie belongs + * @ie: current index entry + * @dirent: context for filldir callback supplied by the caller + * @filldir: filldir callback supplied by the caller + * + * Pass information specifying the current directory entry @ie to the @filldir + * callback. + */ +static int ntfs_filldir(ntfs_inode *dir_ni, s64 *pos, u8 ivcn_bits, + const INDEX_TYPE index_type, index_union iu, INDEX_ENTRY *ie, + void *dirent, ntfs_filldir_t filldir) +{ + FILE_NAME_ATTR *fn = &ie->key.file_name; + unsigned dt_type; + + ntfs_log_trace("Entering.\n"); + + /* Advance the position even if going to skip the entry. */ + if (index_type == INDEX_TYPE_ALLOCATION) + *pos = (u8*)ie - (u8*)iu.ia + (sle64_to_cpu( + iu.ia->index_block_vcn) << ivcn_bits) + + dir_ni->vol->mft_record_size; + else /* if (index_type == INDEX_TYPE_ROOT) */ + *pos = (u8*)ie - (u8*)iu.ir; + /* Skip root directory self reference entry. */ + if (MREF_LE(ie->indexed_file) == FILE_root) + return 0; + if (ie->key.file_name.file_attributes & FILE_ATTR_I30_INDEX_PRESENT) + dt_type = NTFS_DT_DIR; + else + dt_type = NTFS_DT_REG; + return filldir(dirent, fn->file_name, fn->file_name_length, + fn->file_name_type, *pos, + le64_to_cpu(ie->indexed_file), dt_type); +} + +/** + * ntfs_mft_get_parent_ref - find mft reference of parent directory of an inode + * @ni: ntfs inode whose parent directory to find + * + * Find the parent directory of the ntfs inode @ni. To do this, find the first + * file name attribute in the mft record of @ni and return the parent mft + * reference from that. + * + * Note this only makes sense for directories, since files can be hard linked + * from multiple directories and there is no way for us to tell which one is + * being looked for. + * + * Technically directories can have hard links, too, but we consider that as + * illegal as Linux/UNIX do not support directory hard links. + * + * Return the mft reference of the parent directory on success or -1 on error + * with errno set to the error code. + */ +static MFT_REF ntfs_mft_get_parent_ref(ntfs_inode *ni) +{ + MFT_REF mref; + ntfs_attr_search_ctx *ctx; + FILE_NAME_ATTR *fn; + int eo; + + ntfs_log_trace("Entering.\n"); + + if (!ni) { + errno = EINVAL; + return ERR_MREF(-1); + } + + ctx = ntfs_attr_get_search_ctx(ni, NULL); + if (!ctx) + return ERR_MREF(-1); + if (ntfs_attr_lookup(AT_FILE_NAME, AT_UNNAMED, 0, 0, 0, NULL, 0, ctx)) { + ntfs_log_debug("No file name found in inode 0x%llx. Corrupt " + "inode.\n", (unsigned long long)ni->mft_no); + goto err_out; + } + if (ctx->attr->non_resident) { + ntfs_log_debug("File name attribute must be resident. Corrupt inode " + "0x%llx.\n", (unsigned long long)ni->mft_no); + goto io_err_out; + } + fn = (FILE_NAME_ATTR*)((u8*)ctx->attr + + le16_to_cpu(ctx->attr->value_offset)); + if ((u8*)fn + le32_to_cpu(ctx->attr->value_length) > + (u8*)ctx->attr + le32_to_cpu(ctx->attr->length)) { + ntfs_log_debug("Corrupt file name attribute in inode 0x%llx.\n", + (unsigned long long)ni->mft_no); + goto io_err_out; + } + mref = le64_to_cpu(fn->parent_directory); + ntfs_attr_put_search_ctx(ctx); + return mref; +io_err_out: + errno = EIO; +err_out: + eo = errno; + ntfs_attr_put_search_ctx(ctx); + errno = eo; + return ERR_MREF(-1); +} + +/** + * ntfs_readdir - read the contents of an ntfs directory + * @dir_ni: ntfs inode of current directory + * @pos: current position in directory + * @dirent: context for filldir callback supplied by the caller + * @filldir: filldir callback supplied by the caller + * + * Parse the index root and the index blocks that are marked in use in the + * index bitmap and hand each found directory entry to the @filldir callback + * supplied by the caller. + * + * Return 0 on success or -1 on error with errno set to the error code. + * + * Note: Index blocks are parsed in ascending vcn order, from which follows + * that the directory entries are not returned sorted. + */ +int ntfs_readdir(ntfs_inode *dir_ni, s64 *pos, + void *dirent, ntfs_filldir_t filldir) +{ + s64 i_size, br, ia_pos, bmp_pos, ia_start; + ntfs_volume *vol; + ntfs_attr *ia_na, *bmp_na = NULL; + ntfs_attr_search_ctx *ctx = NULL; + u8 *index_end, *bmp = NULL; + INDEX_ROOT *ir; + INDEX_ENTRY *ie; + INDEX_ALLOCATION *ia = NULL; + int rc, ir_pos, bmp_buf_size, bmp_buf_pos, eo; + u32 index_block_size, index_vcn_size; + u8 index_block_size_bits, index_vcn_size_bits; + + ntfs_log_trace("Entering.\n"); + + if (!dir_ni || !pos || !filldir) { + errno = EINVAL; + return -1; + } + + if (!(dir_ni->mrec->flags & MFT_RECORD_IS_DIRECTORY)) { + errno = ENOTDIR; + return -1; + } + + vol = dir_ni->vol; + + ntfs_log_trace("Entering for inode 0x%llx, *pos 0x%llx.\n", + (unsigned long long)dir_ni->mft_no, (long long)*pos); + + /* Open the index allocation attribute. */ + ia_na = ntfs_attr_open(dir_ni, AT_INDEX_ALLOCATION, NTFS_INDEX_I30, 4); + if (!ia_na) { + if (errno != ENOENT) { + ntfs_log_perror("Failed to open index allocation attribute. " + "Directory inode 0x%llx is corrupt or bug", + (unsigned long long)dir_ni->mft_no); + return -1; + } + i_size = 0; + } else + i_size = ia_na->data_size; + + rc = 0; + + /* Are we at end of dir yet? */ + if (*pos >= i_size + vol->mft_record_size) + goto done; + + /* Emulate . and .. for all directories. */ + if (!*pos) { + rc = filldir(dirent, dotdot, 1, FILE_NAME_POSIX, *pos, + MK_MREF(dir_ni->mft_no, + le16_to_cpu(dir_ni->mrec->sequence_number)), + NTFS_DT_DIR); + if (rc) + goto done; + ++*pos; + } + if (*pos == 1) { + MFT_REF parent_mref; + + parent_mref = ntfs_mft_get_parent_ref(dir_ni); + if (parent_mref == ERR_MREF(-1)) { + ntfs_log_perror("Parent directory not found"); + goto dir_err_out; + } + + rc = filldir(dirent, dotdot, 2, FILE_NAME_POSIX, *pos, + parent_mref, NTFS_DT_DIR); + if (rc) + goto done; + ++*pos; + } + + ctx = ntfs_attr_get_search_ctx(dir_ni, NULL); + if (!ctx) + goto err_out; + + /* Get the offset into the index root attribute. */ + ir_pos = (int)*pos; + /* Find the index root attribute in the mft record. */ + if (ntfs_attr_lookup(AT_INDEX_ROOT, NTFS_INDEX_I30, 4, CASE_SENSITIVE, 0, NULL, + 0, ctx)) { + ntfs_log_debug("Index root attribute missing in directory inode " + "0x%llx.\n", (unsigned long long)dir_ni->mft_no); + goto dir_err_out; + } + /* Get to the index root value. */ + ir = (INDEX_ROOT*)((u8*)ctx->attr + + le16_to_cpu(ctx->attr->value_offset)); + + /* Determine the size of a vcn in the directory index. */ + index_block_size = le32_to_cpu(ir->index_block_size); + if (index_block_size < NTFS_BLOCK_SIZE || + index_block_size & (index_block_size - 1)) { + ntfs_log_debug("Index block size %u is invalid.\n", + (unsigned)index_block_size); + goto dir_err_out; + } + index_block_size_bits = ffs(index_block_size) - 1; + if (vol->cluster_size <= index_block_size) { + index_vcn_size = vol->cluster_size; + index_vcn_size_bits = vol->cluster_size_bits; + } else { + index_vcn_size = vol->sector_size; + index_vcn_size_bits = vol->sector_size_bits; + } + + /* Are we jumping straight into the index allocation attribute? */ + if (*pos >= vol->mft_record_size) { + ntfs_attr_put_search_ctx(ctx); + ctx = NULL; + goto skip_index_root; + } + + index_end = (u8*)&ir->index + le32_to_cpu(ir->index.index_length); + /* The first index entry. */ + ie = (INDEX_ENTRY*)((u8*)&ir->index + + le32_to_cpu(ir->index.entries_offset)); + /* + * Loop until we exceed valid memory (corruption case) or until we + * reach the last entry or until filldir tells us it has had enough + * or signals an error (both covered by the rc test). + */ + for (;; ie = (INDEX_ENTRY*)((u8*)ie + le16_to_cpu(ie->length))) { + ntfs_log_debug("In index root, offset 0x%x.\n", (u8*)ie - (u8*)ir); + /* Bounds checks. */ + if ((u8*)ie < (u8*)ctx->mrec || (u8*)ie + + sizeof(INDEX_ENTRY_HEADER) > index_end || + (u8*)ie + le16_to_cpu(ie->key_length) > + index_end) + goto dir_err_out; + /* The last entry cannot contain a name. */ + if (ie->ie_flags & INDEX_ENTRY_END) + break; + /* Skip index root entry if continuing previous readdir. */ + if (ir_pos > (u8*)ie - (u8*)ir) + continue; + /* + * Submit the directory entry to ntfs_filldir(), which will + * invoke the filldir() callback as appropriate. + */ + rc = ntfs_filldir(dir_ni, pos, index_vcn_size_bits, + INDEX_TYPE_ROOT, ir, ie, dirent, filldir); + if (rc) { + ntfs_attr_put_search_ctx(ctx); + ctx = NULL; + goto done; + } + } + ntfs_attr_put_search_ctx(ctx); + ctx = NULL; + + /* If there is no index allocation attribute we are finished. */ + if (!ia_na) + goto EOD; + + /* Advance *pos to the beginning of the index allocation. */ + *pos = vol->mft_record_size; + +skip_index_root: + + if (!ia_na) + goto done; + + /* Allocate a buffer for the current index block. */ + ia = ntfs_malloc(index_block_size); + if (!ia) + goto err_out; + + bmp_na = ntfs_attr_open(dir_ni, AT_BITMAP, NTFS_INDEX_I30, 4); + if (!bmp_na) { + ntfs_log_perror("Failed to open index bitmap attribute"); + goto dir_err_out; + } + + /* Get the offset into the index allocation attribute. */ + ia_pos = *pos - vol->mft_record_size; + + bmp_pos = ia_pos >> index_block_size_bits; + if (bmp_pos >> 3 >= bmp_na->data_size) { + ntfs_log_debug("Current index position exceeds index bitmap " + "size.\n"); + goto dir_err_out; + } + + bmp_buf_size = min(bmp_na->data_size - (bmp_pos >> 3), 4096); + bmp = ntfs_malloc(bmp_buf_size); + if (!bmp) + goto err_out; + + br = ntfs_attr_pread(bmp_na, bmp_pos >> 3, bmp_buf_size, bmp); + if (br != bmp_buf_size) { + if (br != -1) + errno = EIO; + ntfs_log_perror("Failed to read from index bitmap attribute"); + goto err_out; + } + + bmp_buf_pos = 0; + /* If the index block is not in use find the next one that is. */ + while (!(bmp[bmp_buf_pos >> 3] & (1 << (bmp_buf_pos & 7)))) { +find_next_index_buffer: + bmp_pos++; + bmp_buf_pos++; + /* If we have reached the end of the bitmap, we are done. */ + if (bmp_pos >> 3 >= bmp_na->data_size) + goto EOD; + ia_pos = bmp_pos << index_block_size_bits; + if (bmp_buf_pos >> 3 < bmp_buf_size) + continue; + /* Read next chunk from the index bitmap. */ + if ((bmp_pos >> 3) + bmp_buf_size > bmp_na->data_size) + bmp_buf_size = bmp_na->data_size - (bmp_pos >> 3); + br = ntfs_attr_pread(bmp_na, bmp_pos >> 3, bmp_buf_size, bmp); + if (br != bmp_buf_size) { + if (br != -1) + errno = EIO; + ntfs_log_perror("Failed to read from index bitmap attribute"); + goto err_out; + } + } + + ntfs_log_debug("Handling index block 0x%llx.\n", (long long)bmp_pos); + + /* Read the index block starting at bmp_pos. */ + br = ntfs_attr_mst_pread(ia_na, bmp_pos << index_block_size_bits, 1, + index_block_size, ia); + if (br != 1) { + if (br != -1) + errno = EIO; + ntfs_log_perror("Failed to read index block"); + goto err_out; + } + + ia_start = ia_pos & ~(s64)(index_block_size - 1); + if (sle64_to_cpu(ia->index_block_vcn) != ia_start >> + index_vcn_size_bits) { + ntfs_log_debug("Actual VCN (0x%llx) of index buffer is different " + "from expected VCN (0x%llx) in inode 0x%llx.\n", + (long long)sle64_to_cpu(ia->index_block_vcn), + (long long)ia_start >> index_vcn_size_bits, + (unsigned long long)dir_ni->mft_no); + goto dir_err_out; + } + if (le32_to_cpu(ia->index.allocated_size) + 0x18 != index_block_size) { + ntfs_log_debug("Index buffer (VCN 0x%llx) of directory inode 0x%llx " + "has a size (%u) differing from the directory " + "specified size (%u).\n", (long long)ia_start >> + index_vcn_size_bits, + (unsigned long long)dir_ni->mft_no, + (unsigned) le32_to_cpu(ia->index.allocated_size) + + 0x18, (unsigned)index_block_size); + goto dir_err_out; + } + index_end = (u8*)&ia->index + le32_to_cpu(ia->index.index_length); + if (index_end > (u8*)ia + index_block_size) { + ntfs_log_debug("Size of index buffer (VCN 0x%llx) of directory inode " + "0x%llx exceeds maximum size.\n", + (long long)ia_start >> index_vcn_size_bits, + (unsigned long long)dir_ni->mft_no); + goto dir_err_out; + } + /* The first index entry. */ + ie = (INDEX_ENTRY*)((u8*)&ia->index + + le32_to_cpu(ia->index.entries_offset)); + /* + * Loop until we exceed valid memory (corruption case) or until we + * reach the last entry or until ntfs_filldir tells us it has had + * enough or signals an error (both covered by the rc test). + */ + for (;; ie = (INDEX_ENTRY*)((u8*)ie + le16_to_cpu(ie->length))) { + ntfs_log_debug("In index allocation, offset 0x%llx.\n", + (long long)ia_start + ((u8*)ie - (u8*)ia)); + /* Bounds checks. */ + if ((u8*)ie < (u8*)ia || (u8*)ie + + sizeof(INDEX_ENTRY_HEADER) > index_end || + (u8*)ie + le16_to_cpu(ie->key_length) > + index_end) { + ntfs_log_debug("Index entry out of bounds in directory inode " + "0x%llx.\n", (unsigned long long)dir_ni->mft_no); + goto dir_err_out; + } + /* The last entry cannot contain a name. */ + if (ie->ie_flags & INDEX_ENTRY_END) + break; + /* Skip index entry if continuing previous readdir. */ + if (ia_pos - ia_start > (u8*)ie - (u8*)ia) + continue; + /* + * Submit the directory entry to ntfs_filldir(), which will + * invoke the filldir() callback as appropriate. + */ + rc = ntfs_filldir(dir_ni, pos, index_vcn_size_bits, + INDEX_TYPE_ALLOCATION, ia, ie, dirent, filldir); + if (rc) + goto done; + } + goto find_next_index_buffer; +EOD: + /* We are finished, set *pos to EOD. */ + *pos = i_size + vol->mft_record_size; +done: + free(ia); + free(bmp); + if (bmp_na) + ntfs_attr_close(bmp_na); + if (ia_na) + ntfs_attr_close(ia_na); +#ifdef DEBUG + if (!rc) + ntfs_log_debug("EOD, *pos 0x%llx, returning 0.\n", (long long)*pos); + else + ntfs_log_debug("filldir returned %i, *pos 0x%llx, returning 0.\n", + rc, (long long)*pos); +#endif + return 0; +dir_err_out: + errno = EIO; +err_out: + eo = errno; + ntfs_log_trace("failed.\n"); + if (ctx) + ntfs_attr_put_search_ctx(ctx); + free(ia); + free(bmp); + if (bmp_na) + ntfs_attr_close(bmp_na); + if (ia_na) + ntfs_attr_close(ia_na); + errno = eo; + return -1; +} + + +/** + * __ntfs_create - create object on ntfs volume + * @dir_ni: ntfs inode for directory in which create new object + * @name: unicode name of new object + * @name_len: length of the name in unicode characters + * @type: type of the object to create + * @dev: major and minor device numbers (obtained from makedev()) + * @target: target in unicode (only for symlinks) + * @target_len: length of target in unicode characters + * + * Internal, use ntfs_create{,_device,_symlink} wrappers instead. + * + * @type can be: + * S_IFREG to create regular file + * S_IFDIR to create directory + * S_IFBLK to create block device + * S_IFCHR to create character device + * S_IFLNK to create symbolic link + * S_IFIFO to create FIFO + * S_IFSOCK to create socket + * other values are invalid. + * + * @dev is used only if @type is S_IFBLK or S_IFCHR, in other cases its value + * ignored. + * + * @target and @target_len are used only if @type is S_IFLNK, in other cases + * their value ignored. + * + * Return opened ntfs inode that describes created object on success or NULL + * on error with errno set to the error code. + */ +static ntfs_inode *__ntfs_create(ntfs_inode *dir_ni, + ntfschar *name, u8 name_len, dev_t type, dev_t dev, + ntfschar *target, u8 target_len) +{ + ntfs_inode *ni; + int rollback_data = 0, rollback_sd = 0; + FILE_NAME_ATTR *fn = NULL; + STANDARD_INFORMATION *si = NULL; + int err, fn_len, si_len; + + ntfs_log_trace("Entering.\n"); + + /* Sanity checks. */ + if (!dir_ni || !name || !name_len) { + ntfs_log_error("Invalid arguments.\n"); + errno = EINVAL; + return NULL; + } + /* Allocate MFT record for new file. */ + ni = ntfs_mft_record_alloc(dir_ni->vol, NULL); + if (!ni) { + err = errno; + ntfs_log_error("Could not allocate new MFT record: %s.\n", + strerror(err)); + errno = err; + return NULL; + } + /* + * Create STANDARD_INFORMATION attribute. Write STANDARD_INFORMATION + * version 1.2, windows will upgrade it to version 3 if needed. + */ + si_len = offsetof(STANDARD_INFORMATION, v1_end); + si = ntfs_calloc(si_len); + if (!si) { + err = errno; + goto err_out; + } + si->creation_time = utc2ntfs(ni->creation_time); + si->last_data_change_time = utc2ntfs(ni->last_data_change_time); + si->last_mft_change_time = utc2ntfs(ni->last_mft_change_time); + si->last_access_time = utc2ntfs(ni->last_access_time); + if (!S_ISREG(type) && !S_ISDIR(type)) { + si->file_attributes = FILE_ATTR_SYSTEM; + ni->flags = FILE_ATTR_SYSTEM; + } + /* Add STANDARD_INFORMATION to inode. */ + if (ntfs_attr_add(ni, AT_STANDARD_INFORMATION, AT_UNNAMED, 0, + (u8*)si, si_len)) { + err = errno; + ntfs_log_error("Failed to add STANDARD_INFORMATION " + "attribute.\n"); + goto err_out; + } + + if (ntfs_sd_add_everyone(ni)) { + err = errno; + goto err_out; + } + rollback_sd = 1; + + if (S_ISDIR(type)) { + INDEX_ROOT *ir = NULL; + INDEX_ENTRY *ie; + int ir_len, index_len; + + /* Create INDEX_ROOT attribute. */ + index_len = sizeof(INDEX_HEADER) + sizeof(INDEX_ENTRY_HEADER); + ir_len = offsetof(INDEX_ROOT, index) + index_len; + ir = ntfs_calloc(ir_len); + if (!ir) { + err = errno; + goto err_out; + } + ir->type = AT_FILE_NAME; + ir->collation_rule = COLLATION_FILE_NAME; + ir->index_block_size = cpu_to_le32(ni->vol->indx_record_size); + if (ni->vol->cluster_size <= ni->vol->indx_record_size) + ir->clusters_per_index_block = + ni->vol->indx_record_size >> + ni->vol->cluster_size_bits; + else + ir->clusters_per_index_block = + -ni->vol->indx_record_size_bits; + ir->index.entries_offset = cpu_to_le32(sizeof(INDEX_HEADER)); + ir->index.index_length = cpu_to_le32(index_len); + ir->index.allocated_size = cpu_to_le32(index_len); + ie = (INDEX_ENTRY*)((u8*)ir + sizeof(INDEX_ROOT)); + ie->length = cpu_to_le16(sizeof(INDEX_ENTRY_HEADER)); + ie->key_length = 0; + ie->ie_flags = INDEX_ENTRY_END; + /* Add INDEX_ROOT attribute to inode. */ + if (ntfs_attr_add(ni, AT_INDEX_ROOT, NTFS_INDEX_I30, 4, + (u8*)ir, ir_len)) { + err = errno; + free(ir); + ntfs_log_error("Failed to add INDEX_ROOT attribute.\n"); + goto err_out; + } + free(ir); + } else { + INTX_FILE *data; + int data_len; + + switch (type) { + case S_IFBLK: + case S_IFCHR: + data_len = offsetof(INTX_FILE, device_end); + data = ntfs_malloc(data_len); + if (!data) { + err = errno; + goto err_out; + } + data->major = cpu_to_le64(major(dev)); + data->minor = cpu_to_le64(minor(dev)); + if (type == S_IFBLK) + data->magic = INTX_BLOCK_DEVICE; + if (type == S_IFCHR) + data->magic = INTX_CHARACTER_DEVICE; + break; + case S_IFLNK: + data_len = sizeof(INTX_FILE_TYPES) + + target_len * sizeof(ntfschar); + data = ntfs_malloc(data_len); + if (!data) { + err = errno; + goto err_out; + } + data->magic = INTX_SYMBOLIC_LINK; + memcpy(data->target, target, + target_len * sizeof(ntfschar)); + break; + case S_IFSOCK: + data = NULL; + data_len = 1; + break; + default: /* FIFO or regular file. */ + data = NULL; + data_len = 0; + break; + } + /* Add DATA attribute to inode. */ + if (ntfs_attr_add(ni, AT_DATA, AT_UNNAMED, 0, (u8*)data, + data_len)) { + err = errno; + ntfs_log_error("Failed to add DATA attribute.\n"); + free(data); + goto err_out; + } + rollback_data = 1; + free(data); + } + /* Create FILE_NAME attribute. */ + fn_len = sizeof(FILE_NAME_ATTR) + name_len * sizeof(ntfschar); + fn = ntfs_calloc(fn_len); + if (!fn) { + err = errno; + goto err_out; + } + fn->parent_directory = MK_LE_MREF(dir_ni->mft_no, + le16_to_cpu(dir_ni->mrec->sequence_number)); + fn->file_name_length = name_len; + fn->file_name_type = FILE_NAME_POSIX; + if (S_ISDIR(type)) + fn->file_attributes = FILE_ATTR_I30_INDEX_PRESENT; + if (!S_ISREG(type) && !S_ISDIR(type)) + fn->file_attributes = FILE_ATTR_SYSTEM; + fn->creation_time = utc2ntfs(ni->creation_time); + fn->last_data_change_time = utc2ntfs(ni->last_data_change_time); + fn->last_mft_change_time = utc2ntfs(ni->last_mft_change_time); + fn->last_access_time = utc2ntfs(ni->last_access_time); + memcpy(fn->file_name, name, name_len * sizeof(ntfschar)); + /* Add FILE_NAME attribute to inode. */ + if (ntfs_attr_add(ni, AT_FILE_NAME, AT_UNNAMED, 0, (u8*)fn, fn_len)) { + err = errno; + ntfs_log_error("Failed to add FILE_NAME attribute.\n"); + goto err_out; + } + /* Add FILE_NAME attribute to index. */ + if (ntfs_index_add_filename(dir_ni, fn, MK_MREF(ni->mft_no, + le16_to_cpu(ni->mrec->sequence_number)))) { + err = errno; + ntfs_log_perror("Failed to add entry to the index"); + goto err_out; + } + /* Set hard links count and directory flag. */ + ni->mrec->link_count = cpu_to_le16(1); + if (S_ISDIR(type)) + ni->mrec->flags |= MFT_RECORD_IS_DIRECTORY; + ntfs_inode_mark_dirty(ni); + /* Done! */ + free(fn); + free(si); + ntfs_log_trace("Done.\n"); + return ni; +err_out: + ntfs_log_trace("Failed.\n"); + if (rollback_sd) { + ntfs_attr *na; + + na = ntfs_attr_open(ni, AT_SECURITY_DESCRIPTOR, AT_UNNAMED, 0); + if (!na) + ntfs_log_perror("Failed to open SD (0x50) attribute of " + " inode 0x%llx. Run chkdsk.\n", + (unsigned long long)ni->mft_no); + else if (ntfs_attr_rm(na)) + ntfs_log_perror("Failed to remove SD (0x50) attribute " + "of inode 0x%llx. Run chkdsk.\n", + (unsigned long long)ni->mft_no); + } + if (rollback_data) { + ntfs_attr *na; + + na = ntfs_attr_open(ni, AT_DATA, AT_UNNAMED, 0); + if (!na) + ntfs_log_perror("Failed to open data attribute of " + " inode 0x%llx. Run chkdsk.\n", + (unsigned long long)ni->mft_no); + else if (ntfs_attr_rm(na)) + ntfs_log_perror("Failed to remove data attribute of " + "inode 0x%llx. Run chkdsk.\n", + (unsigned long long)ni->mft_no); + } + /* + * Free extent MFT records (should not exist any with current + * ntfs_create implementation, but for any case if something will be + * changed in the future). + */ + while (ni->nr_extents) + if (ntfs_mft_record_free(ni->vol, *(ni->extent_nis))) { + err = errno; + ntfs_log_error("Failed to free extent MFT record. " + "Leaving inconsistent metadata.\n"); + } + if (ntfs_mft_record_free(ni->vol, ni)) + ntfs_log_error("Failed to free MFT record. " + "Leaving inconsistent metadata. Run chkdsk.\n"); + free(fn); + free(si); + errno = err; + return NULL; +} + +/** + * Some wrappers around __ntfs_create() ... + */ + +ntfs_inode *ntfs_create(ntfs_inode *dir_ni, ntfschar *name, u8 name_len, + dev_t type) +{ + if (type != S_IFREG && type != S_IFDIR && type != S_IFIFO && + type != S_IFSOCK) { + ntfs_log_error("Invalid arguments.\n"); + return NULL; + } + return __ntfs_create(dir_ni, name, name_len, type, 0, NULL, 0); +} + +ntfs_inode *ntfs_create_device(ntfs_inode *dir_ni, ntfschar *name, u8 name_len, + dev_t type, dev_t dev) +{ + if (type != S_IFCHR && type != S_IFBLK) { + ntfs_log_error("Invalid arguments.\n"); + return NULL; + } + return __ntfs_create(dir_ni, name, name_len, type, dev, NULL, 0); +} + +ntfs_inode *ntfs_create_symlink(ntfs_inode *dir_ni, ntfschar *name, u8 name_len, + ntfschar *target, u8 target_len) +{ + if (!target || !target_len) { + ntfs_log_error("Invalid arguments.\n"); + return NULL; + } + return __ntfs_create(dir_ni, name, name_len, S_IFLNK, 0, + target, target_len); +} + +int ntfs_check_empty_dir(ntfs_inode *ni) +{ + ntfs_attr *na; + int ret = 0; + + if (!(ni->mrec->flags & MFT_RECORD_IS_DIRECTORY)) + return 0; + + na = ntfs_attr_open(ni, AT_INDEX_ROOT, NTFS_INDEX_I30, 4); + if (!na) { + errno = EIO; + ntfs_log_perror("Failed to open directory"); + return -1; + } + + /* Non-empty directory? */ + if ((na->data_size != sizeof(INDEX_ROOT) + sizeof(INDEX_ENTRY_HEADER))){ + /* Both ENOTEMPTY and EEXIST are ok. We use the more common. */ + errno = EEXIST; + ntfs_log_debug("Directory is not empty\n"); + ret = -1; + } + + ntfs_attr_close(na); + return ret; +} + +static int ntfs_check_unlinkable_dir(ntfs_inode *ni, FILE_NAME_ATTR *fn) +{ + int link_count = le16_to_cpu(ni->mrec->link_count); + int ret; + + ret = ntfs_check_empty_dir(ni); + if (!ret || errno != EEXIST) + return ret; + /* + * Directory is non-empty, so we can unlink only if there is more than + * one "real" hard link, i.e. links aren't different DOS and WIN32 names + */ + if ((link_count == 1) || + (link_count == 2 && fn->file_name_type == FILE_NAME_DOS)) { + errno = EEXIST; + ntfs_log_debug("Non-empty directory without hard links\n"); + goto no_hardlink; + } + + ret = 0; +no_hardlink: + return ret; +} + +/** + * ntfs_delete - delete file or directory from ntfs volume + * @ni: ntfs inode for object to delte + * @dir_ni: ntfs inode for directory in which delete object + * @name: unicode name of the object to delete + * @name_len: length of the name in unicode characters + * + * @ni is always closed after the call to this function (even if it failed), + * user does not need to call ntfs_inode_close himself. + * + * Return 0 on success or -1 on error with errno set to the error code. + */ +int ntfs_delete(ntfs_inode *ni, ntfs_inode *dir_ni, ntfschar *name, u8 name_len) +{ + ntfs_attr_search_ctx *actx = NULL; + ntfs_index_context *ictx = NULL; + FILE_NAME_ATTR *fn = NULL; + BOOL looking_for_dos_name = FALSE, looking_for_win32_name = FALSE; + BOOL case_sensitive_match = TRUE; + int err = 0; + + ntfs_log_trace("Entering.\n"); + + if (!ni || !dir_ni || !name || !name_len) { + ntfs_log_error("Invalid arguments.\n"); + errno = EINVAL; + goto err_out; + } + if (ni->nr_extents == -1) + ni = ni->base_ni; + if (dir_ni->nr_extents == -1) + dir_ni = dir_ni->base_ni; + /* + * Search for FILE_NAME attribute with such name. If it's in POSIX or + * WIN32_AND_DOS namespace, then simply remove it from index and inode. + * If filename in DOS or in WIN32 namespace, then remove DOS name first, + * only then remove WIN32 name. + */ + actx = ntfs_attr_get_search_ctx(ni, NULL); + if (!actx) + goto err_out; +search: + while (!ntfs_attr_lookup(AT_FILE_NAME, AT_UNNAMED, 0, CASE_SENSITIVE, + 0, NULL, 0, actx)) { + char *s; + BOOL case_sensitive = IGNORE_CASE; + + errno = 0; + fn = (FILE_NAME_ATTR*)((u8*)actx->attr + + le16_to_cpu(actx->attr->value_offset)); + s = ntfs_attr_name_get(fn->file_name, fn->file_name_length); + ntfs_log_trace("name: '%s' type: %d dos: %d win32: %d " + "case: %d\n", s, fn->file_name_type, + looking_for_dos_name, looking_for_win32_name, + case_sensitive_match); + ntfs_attr_name_free(&s); + if (looking_for_dos_name) { + if (fn->file_name_type == FILE_NAME_DOS) + break; + else + continue; + } + if (looking_for_win32_name) { + if (fn->file_name_type == FILE_NAME_WIN32) + break; + else + continue; + } + + /* Ignore hard links from other directories */ + if (dir_ni->mft_no != MREF_LE(fn->parent_directory)) { + ntfs_log_debug("MFT record numbers don't match " + "(%llu != %llu)\n", dir_ni->mft_no, + MREF_LE(fn->parent_directory)); + continue; + } + + if (fn->file_name_type == FILE_NAME_POSIX || case_sensitive_match) + case_sensitive = CASE_SENSITIVE; + + if (ntfs_names_are_equal(fn->file_name, fn->file_name_length, + name, name_len, case_sensitive, + ni->vol->upcase, ni->vol->upcase_len)){ + + if (fn->file_name_type == FILE_NAME_WIN32) { + looking_for_dos_name = TRUE; + ntfs_attr_reinit_search_ctx(actx); + continue; + } + if (fn->file_name_type == FILE_NAME_DOS) + looking_for_dos_name = TRUE; + break; + } + } + if (errno) { + /* + * If case sensitive search failed, then try once again + * ignoring case. + */ + if (errno == ENOENT && case_sensitive_match) { + case_sensitive_match = FALSE; + ntfs_attr_reinit_search_ctx(actx); + goto search; + } + goto err_out; + } + + if (ntfs_check_unlinkable_dir(ni, fn) < 0) + goto err_out; + + ictx = ntfs_index_ctx_get(dir_ni, NTFS_INDEX_I30, 4); + if (!ictx) + goto err_out; + if (ntfs_index_lookup(fn, le32_to_cpu(actx->attr->value_length), ictx)) + goto err_out; + + if (((FILE_NAME_ATTR*)ictx->data)->file_attributes & + FILE_ATTR_REPARSE_POINT) { + errno = EOPNOTSUPP; + goto err_out; + } + + if (ntfs_index_rm(ictx)) + goto err_out; + + if (ntfs_attr_record_rm(actx)) + goto err_out; + + ni->mrec->link_count = cpu_to_le16(le16_to_cpu( + ni->mrec->link_count) - 1); + + ntfs_inode_mark_dirty(ni); + if (looking_for_dos_name) { + looking_for_dos_name = FALSE; + looking_for_win32_name = TRUE; + ntfs_attr_reinit_search_ctx(actx); + goto search; + } + /* TODO: Update object id, quota and securiry indexes if required. */ + /* + * If hard link count is not equal to zero then we are done. In other + * case there are no reference to this inode left, so we should free all + * non-resident attributes and mark all MFT record as not in use. + */ + if (ni->mrec->link_count) + goto out; + ntfs_attr_reinit_search_ctx(actx); + while (!ntfs_attrs_walk(actx)) { + if (actx->attr->non_resident) { + runlist *rl; + + rl = ntfs_mapping_pairs_decompress(ni->vol, actx->attr, + NULL); + if (!rl) { + err = errno; + ntfs_log_error("Failed to decompress runlist. " + "Leaving inconsistent metadata.\n"); + continue; + } + if (ntfs_cluster_free_from_rl(ni->vol, rl)) { + err = errno; + ntfs_log_error("Failed to free clusters. " + "Leaving inconsistent metadata.\n"); + continue; + } + free(rl); + } + } + if (errno != ENOENT) { + err = errno; + ntfs_log_error("Attribute enumeration failed. " + "Probably leaving inconsistent metadata.\n"); + } + /* All extents should be attached after attribute walk. */ + while (ni->nr_extents) + if (ntfs_mft_record_free(ni->vol, *(ni->extent_nis))) { + err = errno; + ntfs_log_error("Failed to free extent MFT record. " + "Leaving inconsistent metadata.\n"); + } + if (ntfs_mft_record_free(ni->vol, ni)) { + err = errno; + ntfs_log_error("Failed to free base MFT record. " + "Leaving inconsistent metadata.\n"); + } + ni = NULL; +out: + if (actx) + ntfs_attr_put_search_ctx(actx); + if (ictx) + ntfs_index_ctx_put(ictx); + if (ni) + ntfs_inode_close(ni); + if (err) { + errno = err; + ntfs_log_perror("Could not delete file"); + return -1; + } + ntfs_log_trace("Done.\n"); + return 0; +err_out: + err = errno; + goto out; +} + +/** + * ntfs_link - create hard link for file or directory + * @ni: ntfs inode for object to create hard link + * @dir_ni: ntfs inode for directory in which new link should be placed + * @name: unicode name of the new link + * @name_len: length of the name in unicode characters + * + * NOTE: At present we allow creating hardlinks to directories, we use them + * in a temporary state during rename. But it's defenitely bad idea to have + * hard links to directories as a result of operation. + * FIXME: Create internal __ntfs_link that allows hard links to a directories + * and external ntfs_link that do not. Write ntfs_rename that uses __ntfs_link. + * + * Return 0 on success or -1 on error with errno set to the error code. + */ +int ntfs_link(ntfs_inode *ni, ntfs_inode *dir_ni, ntfschar *name, u8 name_len) +{ + FILE_NAME_ATTR *fn = NULL; + int fn_len, err; + + ntfs_log_trace("Entering.\n"); + + if (!ni || !dir_ni || !name || !name_len || + ni->mft_no == dir_ni->mft_no) { + err = EINVAL; + ntfs_log_perror("ntfs_link wrong arguments"); + goto err_out; + } + /* Create FILE_NAME attribute. */ + fn_len = sizeof(FILE_NAME_ATTR) + name_len * sizeof(ntfschar); + fn = ntfs_calloc(fn_len); + if (!fn) { + err = errno; + goto err_out; + } + fn->parent_directory = MK_LE_MREF(dir_ni->mft_no, + le16_to_cpu(dir_ni->mrec->sequence_number)); + fn->file_name_length = name_len; + fn->file_name_type = FILE_NAME_POSIX; + fn->file_attributes = ni->flags; + if (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) + fn->file_attributes |= FILE_ATTR_I30_INDEX_PRESENT; + fn->allocated_size = cpu_to_sle64(ni->allocated_size); + fn->data_size = cpu_to_sle64(ni->data_size); + fn->creation_time = utc2ntfs(ni->creation_time); + fn->last_data_change_time = utc2ntfs(ni->last_data_change_time); + fn->last_mft_change_time = utc2ntfs(ni->last_mft_change_time); + fn->last_access_time = utc2ntfs(ni->last_access_time); + memcpy(fn->file_name, name, name_len * sizeof(ntfschar)); + /* Add FILE_NAME attribute to index. */ + if (ntfs_index_add_filename(dir_ni, fn, MK_MREF(ni->mft_no, + le16_to_cpu(ni->mrec->sequence_number)))) { + err = errno; + ntfs_log_perror("Failed to add filename to the index"); + goto err_out; + } + /* Add FILE_NAME attribute to inode. */ + if (ntfs_attr_add(ni, AT_FILE_NAME, AT_UNNAMED, 0, (u8*)fn, fn_len)) { + ntfs_index_context *ictx; + + err = errno; + ntfs_log_error("Failed to add FILE_NAME attribute.\n"); + /* Try to remove just added attribute from index. */ + ictx = ntfs_index_ctx_get(dir_ni, NTFS_INDEX_I30, 4); + if (!ictx) + goto rollback_failed; + if (ntfs_index_lookup(fn, fn_len, ictx)) { + ntfs_index_ctx_put(ictx); + goto rollback_failed; + } + if (ntfs_index_rm(ictx)) { + ntfs_index_ctx_put(ictx); + goto rollback_failed; + } + goto err_out; + } + /* Increment hard links count. */ + ni->mrec->link_count = cpu_to_le16(le16_to_cpu( + ni->mrec->link_count) + 1); + /* Done! */ + ntfs_inode_mark_dirty(ni); + free(fn); + ntfs_log_trace("Done.\n"); + return 0; +rollback_failed: + ntfs_log_error("Rollback failed. Leaving inconsistent metadata.\n"); +err_out: + free(fn); + errno = err; + ntfs_log_perror("Hard link failed"); + return -1; +} + diff --git a/libntfs-3g/index.c b/libntfs-3g/index.c new file mode 100644 index 00000000..8cb11131 --- /dev/null +++ b/libntfs-3g/index.c @@ -0,0 +1,1836 @@ +/** + * index.c - NTFS index handling. Originated from the Linux-NTFS project. + * + * Copyright (c) 2004-2005 Anton Altaparmakov + * Copyright (c) 2004-2005 Richard Russon + * Copyright (c) 2005-2006 Yura Pakhuchiy + * Copyright (c) 2005-2006 Szabolcs Szakacsits + * + * 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 + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program/include file 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 + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif +#ifdef HAVE_ERRNO_H +#include +#endif + +#include "attrib.h" +#include "collate.h" +#include "debug.h" +#include "index.h" +#include "mst.h" +#include "dir.h" +#include "logging.h" +#include "bitmap.h" +#include "misc.h" + +/** + * ntfs_index_entry_mark_dirty - mark an index entry dirty + * @ictx: ntfs index context describing the index entry + * + * Mark the index entry described by the index entry context @ictx dirty. + * + * If the index entry is in the index root attribute, simply mark the inode + * containing the index root attribute dirty. This ensures the mftrecord, and + * hence the index root attribute, will be written out to disk later. + * + * If the index entry is in an index block belonging to the index allocation + * attribute, set ib_dirty to TRUE, thus index block will be updated during + * ntfs_index_ctx_put. + */ +void ntfs_index_entry_mark_dirty(ntfs_index_context *ictx) +{ + if (ictx->is_in_root) + ntfs_inode_mark_dirty(ictx->actx->ntfs_ino); + else + ictx->ib_dirty = TRUE; +} + +static s64 ntfs_ib_vcn_to_pos(ntfs_index_context *icx, VCN vcn) +{ + return vcn << icx->vcn_size_bits; +} + +static VCN ntfs_ib_pos_to_vcn(ntfs_index_context *icx, s64 pos) +{ + return pos >> icx->vcn_size_bits; +} + +static int ntfs_ib_write(ntfs_index_context *icx, VCN vcn, void *buf) +{ + s64 ret; + + ntfs_log_trace("vcn: %lld\n", vcn); + + ret = ntfs_attr_mst_pwrite(icx->ia_na, ntfs_ib_vcn_to_pos(icx, vcn), + 1, icx->block_size, buf); + if (ret != 1) { + ntfs_log_perror("Failed to write index block %lld of inode " + "%llu", vcn, icx->ni->mft_no); + return STATUS_ERROR; + } + + return STATUS_OK; +} + +static int ntfs_icx_ib_write(ntfs_index_context *icx) +{ + if (ntfs_ib_write(icx, icx->ib_vcn, icx->ib)) + return STATUS_ERROR; + + icx->ib_dirty = FALSE; + + return STATUS_OK; +} + +/** + * ntfs_index_ctx_get - allocate and initialize a new index context + * @ni: ntfs inode with which to initialize the context + * @name: name of the which context describes + * @name_len: length of the index name + * + * Allocate a new index context, initialize it with @ni and return it. + * Return NULL if allocation failed. + */ +ntfs_index_context *ntfs_index_ctx_get(ntfs_inode *ni, + ntfschar *name, u32 name_len) +{ + ntfs_index_context *icx; + + ntfs_log_trace("Entering\n"); + + if (!ni) { + errno = EINVAL; + return NULL; + } + if (ni->nr_extents == -1) + ni = ni->base_ni; + icx = ntfs_calloc(sizeof(ntfs_index_context)); + if (icx) + *icx = (ntfs_index_context) { + .ni = ni, + .name = name, + .name_len = name_len, + }; + return icx; +} + +static void ntfs_index_ctx_free(ntfs_index_context *icx) +{ + ntfs_log_trace("Entering\n"); + + if (!icx->entry) + return; + + if (icx->actx) + ntfs_attr_put_search_ctx(icx->actx); + + if (icx->is_in_root) { + if (icx->ia_na) + ntfs_attr_close(icx->ia_na); + return; + } + + if (icx->ib_dirty) { + /* FIXME: Error handling!!! */ + ntfs_ib_write(icx, icx->ib_vcn, icx->ib); + } + + free(icx->ib); + ntfs_attr_close(icx->ia_na); +} + +/** + * ntfs_index_ctx_put - release an index context + * @icx: index context to free + * + * Release the index context @icx, releasing all associated resources. + */ +void ntfs_index_ctx_put(ntfs_index_context *icx) +{ + ntfs_index_ctx_free(icx); + free(icx); +} + +/** + * ntfs_index_ctx_reinit - reinitialize an index context + * @icx: index context to reinitialize + * + * Reinitialize the index context @icx so it can be used for ntfs_index_lookup. + */ +void ntfs_index_ctx_reinit(ntfs_index_context *icx) +{ + ntfs_log_trace("Entering\n"); + + ntfs_index_ctx_free(icx); + + *icx = (ntfs_index_context) { + .ni = icx->ni, + .name = icx->name, + .name_len = icx->name_len, + }; +} + +static VCN *ntfs_ie_get_vcn_addr(INDEX_ENTRY *ie) +{ + return (VCN *)((u8 *)ie + le16_to_cpu(ie->length) - sizeof(VCN)); +} + +/** + * Get the subnode vcn to which the index entry refers. + */ +VCN ntfs_ie_get_vcn(INDEX_ENTRY *ie) +{ + return sle64_to_cpup(ntfs_ie_get_vcn_addr(ie)); +} + +static INDEX_ENTRY *ntfs_ie_get_first(INDEX_HEADER *ih) +{ + return (INDEX_ENTRY *)((u8 *)ih + le32_to_cpu(ih->entries_offset)); +} + +static INDEX_ENTRY *ntfs_ie_get_next(INDEX_ENTRY *ie) +{ + return (INDEX_ENTRY *)((char *)ie + le16_to_cpu(ie->length)); +} + +static u8 *ntfs_ie_get_end(INDEX_HEADER *ih) +{ + /* FIXME: check if it isn't overflowing the index block size */ + return (u8 *)ih + le32_to_cpu(ih->index_length); +} + +static int ntfs_ie_end(INDEX_ENTRY *ie) +{ + return ie->ie_flags & INDEX_ENTRY_END; +} + +/** + * Find the last entry in the index block + */ +static INDEX_ENTRY *ntfs_ie_get_last(INDEX_ENTRY *ie, char *ies_end) +{ + ntfs_log_trace("Entering\n"); + + while ((char *)ie < ies_end && !ntfs_ie_end(ie)) + ie = ntfs_ie_get_next(ie); + + return ie; +} + +static INDEX_ENTRY *ntfs_ie_get_by_pos(INDEX_HEADER *ih, int pos) +{ + INDEX_ENTRY *ie; + + ntfs_log_trace("pos: %d\n", pos); + + ie = ntfs_ie_get_first(ih); + + while (pos-- > 0) + ie = ntfs_ie_get_next(ie); + + return ie; +} + +static INDEX_ENTRY *ntfs_ie_prev(INDEX_HEADER *ih, INDEX_ENTRY *ie) +{ + INDEX_ENTRY *ie_prev = NULL; + INDEX_ENTRY *tmp; + + ntfs_log_trace("Entering\n"); + + tmp = ntfs_ie_get_first(ih); + + while (tmp != ie) { + ie_prev = tmp; + tmp = ntfs_ie_get_next(tmp); + } + + return ie_prev; +} + +char *ntfs_ie_filename_get(INDEX_ENTRY *ie) +{ + FILE_NAME_ATTR *fn; + + fn = (FILE_NAME_ATTR *)&ie->key; + return ntfs_attr_name_get(fn->file_name, fn->file_name_length); +} + +void ntfs_ie_filename_dump(INDEX_ENTRY *ie) +{ + char *s; + + s = ntfs_ie_filename_get(ie); + ntfs_log_debug("'%s' ", s); + ntfs_attr_name_free(&s); +} + +void ntfs_ih_filename_dump(INDEX_HEADER *ih) +{ + INDEX_ENTRY *ie; + + ntfs_log_trace("Entering\n"); + + ie = ntfs_ie_get_first(ih); + while (!ntfs_ie_end(ie)) { + ntfs_ie_filename_dump(ie); + ie = ntfs_ie_get_next(ie); + } +} + +static int ntfs_ih_numof_entries(INDEX_HEADER *ih) +{ + int n; + INDEX_ENTRY *ie; + + ntfs_log_trace("Entering\n"); + + ie = ntfs_ie_get_first(ih); + for (n = 0; !ntfs_ie_end(ie); n++) + ie = ntfs_ie_get_next(ie); + return n; +} + +static int ntfs_ih_one_entry(INDEX_HEADER *ih) +{ + return (ntfs_ih_numof_entries(ih) == 1); +} + +static int ntfs_ih_zero_entry(INDEX_HEADER *ih) +{ + return (ntfs_ih_numof_entries(ih) == 0); +} + +static void ntfs_ie_delete(INDEX_HEADER *ih, INDEX_ENTRY *ie) +{ + u32 new_size; + + ntfs_log_trace("Entering\n"); + + new_size = le32_to_cpu(ih->index_length) - le16_to_cpu(ie->length); + ih->index_length = cpu_to_le32(new_size); + memmove(ie, (u8 *)ie + le16_to_cpu(ie->length), + new_size - ((u8 *)ie - (u8 *)ih)); +} + +static void ntfs_ie_set_vcn(INDEX_ENTRY *ie, VCN vcn) +{ + *ntfs_ie_get_vcn_addr(ie) = cpu_to_le64(vcn); +} + +/** + * Insert @ie index entry at @pos entry. Used @ih values should be ok already. + */ +static void ntfs_ie_insert(INDEX_HEADER *ih, INDEX_ENTRY *ie, INDEX_ENTRY *pos) +{ + int ie_size = le16_to_cpu(ie->length); + + ntfs_log_trace("Entering\n"); + + ih->index_length = cpu_to_le32(le32_to_cpu(ih->index_length) + ie_size); + memmove((u8 *)pos + ie_size, pos, + le32_to_cpu(ih->index_length) - ((u8 *)pos - (u8 *)ih) - ie_size); + memcpy(pos, ie, ie_size); +} + +static INDEX_ENTRY *ntfs_ie_dup(INDEX_ENTRY *ie) +{ + INDEX_ENTRY *dup; + + ntfs_log_trace("Entering\n"); + + dup = ntfs_malloc(ie->length); + if (dup) + memcpy(dup, ie, ie->length); + + return dup; +} + +static INDEX_ENTRY *ntfs_ie_dup_novcn(INDEX_ENTRY *ie) +{ + INDEX_ENTRY *dup; + int size = ie->length; + + ntfs_log_trace("Entering\n"); + + if (ie->ie_flags & INDEX_ENTRY_NODE) + size -= sizeof(VCN); + + dup = ntfs_malloc(size); + if (dup) + memcpy(dup, ie, size); + + dup->ie_flags &= ~INDEX_ENTRY_NODE; + dup->length = size; + + return dup; +} + +static int ntfs_ia_check(ntfs_index_context *icx, INDEX_BLOCK *ib, VCN vcn) +{ + u32 ib_size = (unsigned)le32_to_cpu(ib->index.allocated_size) + 0x18; + + ntfs_log_trace("Entering\n"); + + if (!ntfs_is_indx_record(ib->magic)) { + + ntfs_log_error("Corrupt index block signature: vcn %lld inode " + "%llu\n", (long long)vcn, icx->ni->mft_no); + return -1; + } + + if (sle64_to_cpu(ib->index_block_vcn) != vcn) { + + ntfs_log_error("Corrupt index block: VCN (%lld) is different " + "from expected VCN (%lld) in inode %llu\n", + (long long) sle64_to_cpu(ib->index_block_vcn), + (long long)vcn, icx->ni->mft_no); + return -1; + } + + if (ib_size != icx->block_size) { + + ntfs_log_error("Corrupt index block : VCN (%lld) of inode %llu " + "has a size (%u) differing from the index " + "specified size (%u)\n", (long long)vcn, + icx->ni->mft_no, ib_size, icx->block_size); + return -1; + } + return 0; +} + +static INDEX_ROOT *ntfs_ir_lookup(ntfs_inode *ni, ntfschar *name, + u32 name_len, ntfs_attr_search_ctx **ctx) +{ + ATTR_RECORD *a; + INDEX_ROOT *ir = NULL; + + ntfs_log_trace("Entering\n"); + + *ctx = ntfs_attr_get_search_ctx(ni, NULL); + if (!*ctx) { + ntfs_log_perror("Failed to get $INDEX_ROOT search context"); + return NULL; + } + + if (ntfs_attr_lookup(AT_INDEX_ROOT, name, name_len, CASE_SENSITIVE, + 0, NULL, 0, *ctx)) { + ntfs_log_perror("Failed to lookup $INDEX_ROOT"); + goto err_out; + } + + a = (*ctx)->attr; + if (a->non_resident) { + errno = EINVAL; + ntfs_log_perror("Non-resident $INDEX_ROOT detected"); + goto err_out; + } + + ir = (INDEX_ROOT *)((char *)a + le16_to_cpu(a->value_offset)); +err_out: + if (!ir) + ntfs_attr_put_search_ctx(*ctx); + return ir; +} + +/** + * Find a key in the index block. + * + * Return values: + * STATUS_OK with errno set to ESUCCESS if we know for sure that the + * entry exists and @ie_out points to this entry. + * STATUS_NOT_FOUND with errno set to ENOENT if we know for sure the + * entry doesn't exist and @ie_out is the insertion point. + * STATUS_KEEP_SEARCHING if we can't answer the above question and + * @vcn will contain the node index block. + * STATUS_ERROR with errno set if on unexpected error during lookup. + */ +static int ntfs_ie_lookup(const void *key, const int key_len, + ntfs_index_context *icx, INDEX_HEADER *ih, + VCN *vcn, INDEX_ENTRY **ie_out) +{ + INDEX_ENTRY *ie; + u8 *index_end; + int rc, item = 0; + + ntfs_log_trace("Entering\n"); + + index_end = ntfs_ie_get_end(ih); + + /* + * Loop until we exceed valid memory (corruption case) or until we + * reach the last entry. + */ + for (ie = ntfs_ie_get_first(ih); ; ie = ntfs_ie_get_next(ie)) { + /* Bounds checks. */ + if ((u8 *)ie + sizeof(INDEX_ENTRY_HEADER) > index_end || + (u8 *)ie + le16_to_cpu(ie->length) > index_end) { + errno = ERANGE; + ntfs_log_error("Index entry out of bounds in inode " + "%llu.\n", icx->ni->mft_no); + return STATUS_ERROR; + } + /* + * The last entry cannot contain a key. It can however contain + * a pointer to a child node in the B+tree so we just break out. + */ + if (ntfs_ie_end(ie)) + break; + /* + * Not a perfect match, need to do full blown collation so we + * know which way in the B+tree we have to go. + */ + rc = ntfs_collate(icx->ni->vol, icx->cr, key, key_len, &ie->key, + le16_to_cpu(ie->key_length)); + if (rc == NTFS_COLLATION_ERROR) { + ntfs_log_error("Collation error. Perhaps a filename " + "contains invalid characters?\n"); + errno = ERANGE; + return STATUS_ERROR; + } + /* + * If @key collates before the key of the current entry, there + * is definitely no such key in this index but we might need to + * descend into the B+tree so we just break out of the loop. + */ + if (rc == -1) + break; + + if (!rc) { + *ie_out = ie; + errno = 0; + icx->parent_pos[icx->pindex] = item; + return STATUS_OK; + } + + item++; + } + /* + * We have finished with this index block without success. Check for the + * presence of a child node and if not present return with errno ENOENT, + * otherwise we will keep searching in another index block. + */ + if (!(ie->ie_flags & INDEX_ENTRY_NODE)) { + ntfs_log_debug("Index entry wasn't found.\n"); + *ie_out = ie; + errno = ENOENT; + return STATUS_NOT_FOUND; + } + + /* Get the starting vcn of the index_block holding the child node. */ + *vcn = ntfs_ie_get_vcn(ie); + if (*vcn < 0) { + errno = EINVAL; + ntfs_log_perror("Negative vcn in inode %llu\n", icx->ni->mft_no); + return STATUS_ERROR; + } + + ntfs_log_trace("Parent entry number %d\n", item); + icx->parent_pos[icx->pindex] = item; + + return STATUS_KEEP_SEARCHING; +} + +static ntfs_attr *ntfs_ia_open(ntfs_index_context *icx, ntfs_inode *ni) +{ + ntfs_attr *na; + + na = ntfs_attr_open(ni, AT_INDEX_ALLOCATION, icx->name, icx->name_len); + if (!na) { + ntfs_log_perror("Failed to open index allocation of inode " + "%llu", ni->mft_no); + return NULL; + } + + return na; +} + +static int ntfs_ib_read(ntfs_index_context *icx, VCN vcn, INDEX_BLOCK *dst) +{ + s64 pos, ret; + + ntfs_log_trace("vcn: %lld\n", vcn); + + pos = ntfs_ib_vcn_to_pos(icx, vcn); + + ret = ntfs_attr_mst_pread(icx->ia_na, pos, 1, icx->block_size, (u8 *)dst); + if (ret != 1) { + if (ret == -1) + ntfs_log_perror("Failed to read index block"); + else + ntfs_log_error("Failed to read full index block at " + "%lld\n", pos); + return -1; + } + + if (ntfs_ia_check(icx, dst, vcn)) + return -1; + + return 0; +} + +static int ntfs_icx_parent_inc(ntfs_index_context *icx) +{ + icx->pindex++; + if (icx->pindex >= MAX_PARENT_VCN) { + errno = EOPNOTSUPP; + ntfs_log_perror("Index is over %d level deep", MAX_PARENT_VCN); + return STATUS_ERROR; + } + return STATUS_OK; +} + +static int ntfs_icx_parent_dec(ntfs_index_context *icx) +{ + icx->pindex--; + if (icx->pindex < 0) { + errno = EINVAL; + ntfs_log_perror("Corrupt index pointer (%d)", icx->pindex); + return STATUS_ERROR; + } + return STATUS_OK; +} + +/** + * ntfs_index_lookup - find a key in an index and return its index entry + * @key: [IN] key for which to search in the index + * @key_len: [IN] length of @key in bytes + * @icx: [IN/OUT] context describing the index and the returned entry + * + * Before calling ntfs_index_lookup(), @icx must have been obtained from a + * call to ntfs_index_ctx_get(). + * + * Look for the @key in the index specified by the index lookup context @icx. + * ntfs_index_lookup() walks the contents of the index looking for the @key. + * + * If the @key is found in the index, 0 is returned and @icx is setup to + * describe the index entry containing the matching @key. @icx->entry is the + * index entry and @icx->data and @icx->data_len are the index entry data and + * its length in bytes, respectively. + * + * If the @key is not found in the index, -1 is returned, errno = ENOENT and + * @icx is setup to describe the index entry whose key collates immediately + * after the search @key, i.e. this is the position in the index at which + * an index entry with a key of @key would need to be inserted. + * + * If an error occurs return -1, set errno to error code and @icx is left + * untouched. + * + * When finished with the entry and its data, call ntfs_index_ctx_put() to free + * the context and other associated resources. + * + * If the index entry was modified, call ntfs_index_entry_mark_dirty() before + * the call to ntfs_index_ctx_put() to ensure that the changes are written + * to disk. + */ +int ntfs_index_lookup(const void *key, const int key_len, + ntfs_index_context *icx) +{ + VCN old_vcn, vcn; + ntfs_inode *ni = icx->ni; + INDEX_ROOT *ir; + INDEX_ENTRY *ie; + INDEX_BLOCK *ib = NULL; + ntfs_attr_search_ctx *actx; + int ret, err = 0; + + ntfs_log_trace("Entering\n"); + + if (!key || key_len <= 0) { + errno = EINVAL; + ntfs_log_perror("key: %p key_len: %d", key, key_len); + return -1; + } + + ir = ntfs_ir_lookup(ni, icx->name, icx->name_len, &actx); + if (!ir) { + if (errno == ENOENT) + errno = EIO; + return -1; + } + + icx->block_size = le32_to_cpu(ir->index_block_size); + if (icx->block_size < NTFS_BLOCK_SIZE) { + errno = EINVAL; + ntfs_log_perror("Index block size (%d) is smaller than the " + "sector size (%d)", icx->block_size, NTFS_BLOCK_SIZE); + return -1; + } + + if (ni->vol->cluster_size <= icx->block_size) + icx->vcn_size_bits = ni->vol->cluster_size_bits; + else + icx->vcn_size_bits = ni->vol->sector_size_bits; + + icx->cr = ir->collation_rule; + if (!ntfs_is_collation_rule_supported(icx->cr)) { + err = errno = EOPNOTSUPP; + ntfs_log_perror("Unknown collation rule 0x%x", + (unsigned)le32_to_cpu(icx->cr)); + goto err_out; + } + + old_vcn = VCN_INDEX_ROOT_PARENT; + /* + * FIXME: check for both ir and ib that the first index entry is + * within the index block. + */ + ret = ntfs_ie_lookup(key, key_len, icx, &ir->index, &vcn, &ie); + if (ret == STATUS_ERROR) { + err = errno; + goto err_out; + } + + icx->actx = actx; + icx->ir = ir; + + if (ret != STATUS_KEEP_SEARCHING) { + /* STATUS_OK or STATUS_NOT_FOUND */ + err = errno; + icx->is_in_root = TRUE; + icx->parent_vcn[icx->pindex] = old_vcn; + goto done; + } + + /* Child node present, descend into it. */ + + icx->ia_na = ntfs_ia_open(icx, ni); + if (!icx->ia_na) + goto err_out; + + ib = ntfs_malloc(icx->block_size); + if (!ib) { + err = errno; + goto err_out; + } + +descend_into_child_node: + + icx->parent_vcn[icx->pindex] = old_vcn; + if (ntfs_icx_parent_inc(icx)) { + err = errno; + goto err_out; + } + old_vcn = vcn; + + ntfs_log_debug("Descend into node with VCN %lld.\n", vcn); + + if (ntfs_ib_read(icx, vcn, ib)) + goto err_out; + + ret = ntfs_ie_lookup(key, key_len, icx, &ib->index, &vcn, &ie); + if (ret != STATUS_KEEP_SEARCHING) { + err = errno; + if (ret == STATUS_ERROR) + goto err_out; + + /* STATUS_OK or STATUS_NOT_FOUND */ + icx->is_in_root = FALSE; + icx->ib = ib; + icx->parent_vcn[icx->pindex] = icx->ib_vcn = vcn; + goto done; + } + + if ((ib->index.ih_flags & NODE_MASK) == LEAF_NODE) { + ntfs_log_error("Index entry with child node found in a leaf " + "node in inode 0x%llx.\n", ni->mft_no); + goto err_out; + } + + goto descend_into_child_node; +err_out: + if (icx->ia_na) + ntfs_attr_close(icx->ia_na); + free(ib); + if (!err) + err = EIO; + if (actx) + ntfs_attr_put_search_ctx(actx); + errno = err; + return -1; +done: + icx->entry = ie; + icx->data = (u8 *)ie + offsetof(INDEX_ENTRY, key); + icx->data_len = le16_to_cpu(ie->key_length); + icx->max_depth = icx->pindex; + ntfs_log_trace("Done.\n"); + if (err) { + errno = err; + return -1; + } + return 0; + +} + +static INDEX_BLOCK *ntfs_ib_alloc(VCN ib_vcn, int ib_size, + INDEX_HEADER_FLAGS node_type) +{ + INDEX_BLOCK *ib; + int ih_size = sizeof(INDEX_HEADER); + + ntfs_log_trace("Entering ib_vcn = %lld ib_size = %d\n", ib_vcn, ib_size); + + ib = ntfs_calloc(ib_size); + if (!ib) + return NULL; + + ib->magic = magic_INDX; + ib->usa_ofs = cpu_to_le16(sizeof(INDEX_BLOCK)); + ib->usa_count = cpu_to_le16(ib_size / NTFS_BLOCK_SIZE + 1); + /* Set USN to 1 */ + *(u16 *)((char *)ib + le16_to_cpu(ib->usa_ofs)) = cpu_to_le16(1); + ib->lsn = cpu_to_le64(0); + + ib->index_block_vcn = cpu_to_sle64(ib_vcn); + + ib->index.entries_offset = cpu_to_le32((ih_size + + le16_to_cpu(ib->usa_count) * 2 + 7) & ~7); + ib->index.index_length = 0; + ib->index.allocated_size = cpu_to_le32(ib_size - + (sizeof(INDEX_BLOCK) - ih_size)); + ib->index.ih_flags = node_type; + + return ib; +} + +/** + * Find the median by going through all the entries + */ +static INDEX_ENTRY *ntfs_ie_get_median(INDEX_HEADER *ih) +{ + INDEX_ENTRY *ie, *ie_start; + u8 *ie_end; + int i = 0, median; + + ntfs_log_trace("Entering\n"); + + ie = ie_start = ntfs_ie_get_first(ih); + ie_end = (u8 *)ntfs_ie_get_end(ih); + + while ((u8 *)ie < ie_end && !ntfs_ie_end(ie)) { + ie = ntfs_ie_get_next(ie); + i++; + } + /* + * NOTE: this could be also the entry at the half of the index block. + */ + median = i / 2 - 1; + + ntfs_log_trace("Entries: %d median: %d\n", i, median); + + for (i = 0, ie = ie_start; i <= median; i++) + ie = ntfs_ie_get_next(ie); + + return ie; +} + +static s64 ntfs_ibm_vcn_to_pos(ntfs_index_context *icx, VCN vcn) +{ + return ntfs_ib_vcn_to_pos(icx, vcn) / icx->block_size; +} + +static s64 ntfs_ibm_pos_to_vcn(ntfs_index_context *icx, s64 pos) +{ + return ntfs_ib_pos_to_vcn(icx, pos * icx->block_size); +} + +static int ntfs_ibm_add(ntfs_index_context *icx) +{ + u8 bmp[8]; + + ntfs_log_trace("Entering\n"); + + if (ntfs_attr_exist(icx->ni, AT_BITMAP, icx->name, icx->name_len)) + return STATUS_OK; + /* + * AT_BITMAP must be at least 8 bytes. + */ + memset(bmp, 0, sizeof(bmp)); + if (ntfs_attr_add(icx->ni, AT_BITMAP, icx->name, icx->name_len, + bmp, sizeof(bmp))) { + ntfs_log_perror("Failed to add AT_BITMAP"); + return STATUS_ERROR; + } + + return STATUS_OK; +} + +static int ntfs_ibm_modify(ntfs_index_context *icx, VCN vcn, int set) +{ + u8 byte; + s64 pos = ntfs_ibm_vcn_to_pos(icx, vcn); + u32 bpos = pos / 8; + u32 bit = 1 << (pos % 8); + ntfs_attr *na; + int ret = STATUS_ERROR; + + ntfs_log_trace("%s vcn: %lld\n", set ? "set" : "clear", vcn); + + na = ntfs_attr_open(icx->ni, AT_BITMAP, icx->name, icx->name_len); + if (!na) { + ntfs_log_perror("Failed to open $BITMAP attribute"); + return -1; + } + + if (set) { + if (na->data_size < bpos + 1) { + if (ntfs_attr_truncate(na, (na->data_size + 8) & ~7)) { + ntfs_log_perror("Failed to truncate AT_BITMAP"); + goto err_na; + } + } + } + + if (ntfs_attr_pread(na, bpos, 1, &byte) != 1) { + ntfs_log_perror("Failed to read $BITMAP"); + goto err_na; + } + + if (set) + byte |= bit; + else + byte &= ~bit; + + if (ntfs_attr_pwrite(na, bpos, 1, &byte) != 1) { + ntfs_log_perror("Failed to write $Bitmap"); + goto err_na; + } + + ret = STATUS_OK; +err_na: + ntfs_attr_close(na); + return ret; +} + + +static int ntfs_ibm_set(ntfs_index_context *icx, VCN vcn) +{ + return ntfs_ibm_modify(icx, vcn, 1); +} + +static int ntfs_ibm_clear(ntfs_index_context *icx, VCN vcn) +{ + return ntfs_ibm_modify(icx, vcn, 0); +} + +static VCN ntfs_ibm_get_free(ntfs_index_context *icx) +{ + u8 *bm; + int bit; + s64 vcn, byte, size; + + ntfs_log_trace("Entering\n"); + + bm = ntfs_attr_readall(icx->ni, AT_BITMAP, icx->name, icx->name_len, + &size); + if (!bm) + return (VCN)-1; + + for (byte = 0; byte < size; byte++) { + + if (bm[byte] == 255) + continue; + + for (bit = 0; bit < 8; bit++) { + if (!(bm[byte] & (1 << bit))) { + vcn = ntfs_ibm_pos_to_vcn(icx, byte * 8 + bit); + goto out; + } + } + } + + vcn = ntfs_ibm_pos_to_vcn(icx, size * 8); +out: + ntfs_log_trace("allocated vcn: %lld\n", vcn); + + if (ntfs_ibm_set(icx, vcn)) + vcn = (VCN)-1; + + free(bm); + return vcn; +} + +static INDEX_BLOCK *ntfs_ir_to_ib(INDEX_ROOT *ir, VCN ib_vcn) +{ + INDEX_BLOCK *ib; + INDEX_ENTRY *ie_last; + char *ies_start, *ies_end; + int i; + + ntfs_log_trace("Entering\n"); + + ib = ntfs_ib_alloc(ib_vcn, ir->index_block_size, LEAF_NODE); + if (!ib) + return NULL; + + ies_start = (char *)ntfs_ie_get_first(&ir->index); + ies_end = (char *)ntfs_ie_get_end(&ir->index); + ie_last = ntfs_ie_get_last((INDEX_ENTRY *)ies_start, ies_end); + /* + * Copy all entries, including the termination entry + * as well, which can never have any data. + */ + i = (char *)ie_last - ies_start + le16_to_cpu(ie_last->length); + memcpy(ntfs_ie_get_first(&ib->index), ies_start, i); + + ib->index.ih_flags = ir->index.ih_flags; + ib->index.index_length = cpu_to_le32(i + + le32_to_cpu(ib->index.entries_offset)); + return ib; +} + +static void ntfs_ir_nill(INDEX_ROOT *ir) +{ + INDEX_ENTRY *ie_last; + char *ies_start, *ies_end; + + ntfs_log_trace("Entering\n"); + /* + * TODO: This function could be much simpler. + */ + ies_start = (char *)ntfs_ie_get_first(&ir->index); + ies_end = (char *)ntfs_ie_get_end(&ir->index); + ie_last = ntfs_ie_get_last((INDEX_ENTRY *)ies_start, ies_end); + /* + * Move the index root termination entry forward + */ + if ((char *)ie_last > ies_start) { + memmove(ies_start, (char *)ie_last, le16_to_cpu(ie_last->length)); + ie_last = (INDEX_ENTRY *)ies_start; + } +} + +static int ntfs_ib_copy_tail(ntfs_index_context *icx, INDEX_BLOCK *src, + INDEX_ENTRY *median, VCN new_vcn) +{ + u8 *ies_end; + INDEX_ENTRY *ie_head; /* first entry after the median */ + int tail_size, ret; + INDEX_BLOCK *dst; + + ntfs_log_trace("Entering\n"); + + dst = ntfs_ib_alloc(new_vcn, icx->block_size, + src->index.ih_flags & NODE_MASK); + if (!dst) + return STATUS_ERROR; + + ie_head = ntfs_ie_get_next(median); + + ies_end = (u8 *)ntfs_ie_get_end(&src->index); + tail_size = ies_end - (u8 *)ie_head; + memcpy(ntfs_ie_get_first(&dst->index), ie_head, tail_size); + + dst->index.index_length = cpu_to_le32(tail_size + + le32_to_cpu(dst->index.entries_offset)); + ret = ntfs_ib_write(icx, new_vcn, dst); + + free(dst); + return ret; +} + +static int ntfs_ib_cut_tail(ntfs_index_context *icx, INDEX_BLOCK *src, + INDEX_ENTRY *ie) +{ + char *ies_start, *ies_end; + INDEX_ENTRY *ie_last; + + ntfs_log_trace("Entering\n"); + + ies_start = (char *)ntfs_ie_get_first(&src->index); + ies_end = (char *)ntfs_ie_get_end(&src->index); + + ie_last = ntfs_ie_get_last((INDEX_ENTRY *)ies_start, ies_end); + if (ie_last->ie_flags & INDEX_ENTRY_NODE) + ntfs_ie_set_vcn(ie_last, ntfs_ie_get_vcn(ie)); + + memcpy(ie, ie_last, ie_last->length); + + src->index.index_length = cpu_to_le32(((char *)ie - ies_start) + + ie->length + le32_to_cpu(src->index.entries_offset)); + + if (ntfs_ib_write(icx, icx->parent_vcn[icx->pindex + 1], src)) + return STATUS_ERROR; + + return STATUS_OK; +} + +static int ntfs_ia_add(ntfs_index_context *icx) +{ + ntfs_log_trace("Entering\n"); + + if (ntfs_ibm_add(icx)) + return -1; + + if (!ntfs_attr_exist(icx->ni, AT_INDEX_ALLOCATION, icx->name, icx->name_len)) { + + if (ntfs_attr_add(icx->ni, AT_INDEX_ALLOCATION, icx->name, + icx->name_len, NULL, 0)) { + ntfs_log_perror("Failed to add AT_INDEX_ALLOCATION"); + return -1; + } + } + + icx->ia_na = ntfs_ia_open(icx, icx->ni); + if (!icx->ia_na) + return -1; + + return 0; +} + +static int ntfs_ir_reparent(ntfs_index_context *icx) +{ + ntfs_attr_search_ctx *ctx; + INDEX_ROOT *ir; + INDEX_ENTRY *ie; + INDEX_BLOCK *ib = NULL; + VCN new_ib_vcn; + int ret = STATUS_ERROR; + + ntfs_log_trace("Entering\n"); + + ir = ntfs_ir_lookup(icx->ni, icx->name, icx->name_len, &ctx); + if (!ir) + return -1; + + if ((ir->index.ih_flags & NODE_MASK) == SMALL_INDEX) + if (ntfs_ia_add(icx)) + goto err_out; + + new_ib_vcn = ntfs_ibm_get_free(icx); + if (new_ib_vcn == -1) + goto err_out; + + ib = ntfs_ir_to_ib(ir, new_ib_vcn); + if (ib == NULL) { + ntfs_log_perror("Failed to move index root to index block"); + goto clear_bmp; + } + + if (ntfs_ib_write(icx, new_ib_vcn, ib)) + goto clear_bmp; + + ntfs_ir_nill(ir); + + ie = ntfs_ie_get_first(&ir->index); + ie->ie_flags |= INDEX_ENTRY_NODE; + ie->length = cpu_to_le16(sizeof(INDEX_ENTRY_HEADER) + sizeof(VCN)); + + ir->index.ih_flags = LARGE_INDEX; + ir->index.index_length = cpu_to_le32(le32_to_cpu(ir->index.entries_offset) + + le16_to_cpu(ie->length)); + ir->index.allocated_size = ir->index.index_length; + + if (ntfs_resident_attr_value_resize(ctx->mrec, ctx->attr, + sizeof(INDEX_ROOT) - sizeof(INDEX_HEADER) + + le32_to_cpu(ir->index.allocated_size))) + /* FIXME: revert bitmap, index root */ + goto err_out; + /* + * FIXME: do it earlier if we have enough space in IR (should always), + * so in error case we wouldn't lose the IB. + */ + ntfs_ie_set_vcn(ie, new_ib_vcn); + + ret = STATUS_OK; +err_out: + free(ib); + ntfs_attr_put_search_ctx(ctx); + return ret; +clear_bmp: + ntfs_ibm_clear(icx, new_ib_vcn); + goto err_out; +} + +/** + * ntfs_ir_truncate - Truncate index root attribute + * + * Returns STATUS_OK, STATUS_RESIDENT_ATTRIBUTE_FILLED_MFT or STATUS_ERROR. + */ +static int ntfs_ir_truncate(ntfs_index_context *icx, int data_size) +{ + ntfs_attr *na; + int ret; + + ntfs_log_trace("Entering\n"); + + na = ntfs_attr_open(icx->ni, AT_INDEX_ROOT, icx->name, icx->name_len); + if (!na) { + ntfs_log_perror("Failed to open INDEX_ROOT"); + return STATUS_ERROR; + } + /* + * INDEX_ROOT must be resident and its entries can be moved to + * INDEX_BLOCK, so ENOSPC isn't a real error. + */ + ret = ntfs_attr_truncate(na, data_size + offsetof(INDEX_ROOT, index)); + if (ret == STATUS_OK) { + + ntfs_attr_search_ctx *ctx; + + icx->ir = ntfs_ir_lookup(icx->ni, icx->name, icx->name_len, &ctx); + if (!icx->ir) + return STATUS_ERROR; + + icx->ir->index.allocated_size = cpu_to_le32(data_size); + + ntfs_attr_put_search_ctx(ctx); + + } else if (errno != ENOSPC) + ntfs_log_trace("Failed to truncate INDEX_ROOT"); + + ntfs_attr_close(na); + return ret; +} + +/** + * ntfs_ir_make_space - Make more space for the index root attribute + * + * On success return STATUS_OK or STATUS_KEEP_SEARCHING. + * On error return STATUS_ERROR. + */ +static int ntfs_ir_make_space(ntfs_index_context *icx, int data_size) +{ + int ret; + + ntfs_log_trace("Entering\n"); + + ret = ntfs_ir_truncate(icx, data_size); + if (ret == STATUS_RESIDENT_ATTRIBUTE_FILLED_MFT) { + + ret = ntfs_ir_reparent(icx); + if (ret == STATUS_OK) + ret = STATUS_KEEP_SEARCHING; + else + ntfs_log_perror("Failed to nodify INDEX_ROOT"); + } + + return ret; +} + +/* + * NOTE: 'ie' must be a copy of a real index entry. + */ +static int ntfs_ie_add_vcn(INDEX_ENTRY **ie) +{ + INDEX_ENTRY *p, *old = *ie; + + old->length += sizeof(VCN); + p = realloc(old, old->length); + if (!p) + return STATUS_ERROR; + + p->ie_flags |= INDEX_ENTRY_NODE; + *ie = p; + + return STATUS_OK; +} + +static int ntfs_ih_insert(INDEX_HEADER *ih, INDEX_ENTRY *orig_ie, VCN new_vcn, + int pos) +{ + INDEX_ENTRY *ie_node, *ie; + int ret = STATUS_ERROR; + VCN old_vcn; + + ntfs_log_trace("Entering\n"); + + ie = ntfs_ie_dup(orig_ie); + if (!ie) + return STATUS_ERROR; + + if (!(ie->ie_flags & INDEX_ENTRY_NODE)) + if (ntfs_ie_add_vcn(&ie)) + goto out; + + ie_node = ntfs_ie_get_by_pos(ih, pos); + old_vcn = ntfs_ie_get_vcn(ie_node); + ntfs_ie_set_vcn(ie_node, new_vcn); + + ntfs_ie_insert(ih, ie, ie_node); + ntfs_ie_set_vcn(ie_node, old_vcn); + ret = STATUS_OK; +out: + free(ie); + + return ret; +} + +static VCN ntfs_icx_parent_vcn(ntfs_index_context *icx) +{ + return icx->parent_vcn[icx->pindex]; +} + +static VCN ntfs_icx_parent_pos(ntfs_index_context *icx) +{ + return icx->parent_pos[icx->pindex]; +} + + +static int ntfs_ir_insert_median(ntfs_index_context *icx, INDEX_ENTRY *median, + VCN new_vcn) +{ + u32 new_size; + int ret; + + ntfs_log_trace("Entering\n"); + + new_size = le32_to_cpu(icx->ir->index.index_length) + median->length; + if (!(median->ie_flags & INDEX_ENTRY_NODE)) + new_size += sizeof(VCN); + + ret = ntfs_ir_make_space(icx, new_size); + if (ret != STATUS_OK) + return ret; + + return ntfs_ih_insert(&icx->ir->index, median, new_vcn, + ntfs_icx_parent_pos(icx)); +} + +static int ntfs_ib_split(ntfs_index_context *icx, INDEX_BLOCK *ib); + +/** + * On success return STATUS_OK or STATUS_KEEP_SEARCHING. + * On error return STATUS_ERROR. + */ +static int ntfs_ib_insert(ntfs_index_context *icx, INDEX_ENTRY *ie, VCN new_vcn) +{ + INDEX_BLOCK *ib; + u32 idx_size, allocated_size; + int err = STATUS_ERROR; + VCN old_vcn; + + ntfs_log_trace("Entering\n"); + + ib = ntfs_malloc(icx->block_size); + if (!ib) + return -1; + + old_vcn = ntfs_icx_parent_vcn(icx); + + if (ntfs_ib_read(icx, old_vcn, ib)) + goto err_out; + + idx_size = le32_to_cpu(ib->index.index_length); + allocated_size = le32_to_cpu(ib->index.allocated_size); + /* FIXME: sizeof(VCN) should be included only if ie has no VCN */ + if (idx_size + ie->length + sizeof(VCN) > allocated_size) { + err = ntfs_ib_split(icx, ib); + if (err == STATUS_OK) + err = STATUS_KEEP_SEARCHING; + goto err_out; + } + + if (ntfs_ih_insert(&ib->index, ie, new_vcn, ntfs_icx_parent_pos(icx))) + goto err_out; + + if (ntfs_ib_write(icx, old_vcn, ib)) + goto err_out; + + err = STATUS_OK; +err_out: + free(ib); + return err; +} + +/** + * ntfs_ib_split - Split index allocation attribute + * + * On success return STATUS_OK or STATUS_KEEP_SEARCHING. + * On error return is STATUS_ERROR. + */ +static int ntfs_ib_split(ntfs_index_context *icx, INDEX_BLOCK *ib) +{ + INDEX_ENTRY *median; + VCN new_vcn; + int ret; + + ntfs_log_trace("Entering\n"); + + if (ntfs_icx_parent_dec(icx)) + return STATUS_ERROR; + + median = ntfs_ie_get_median(&ib->index); + new_vcn = ntfs_ibm_get_free(icx); + if (new_vcn == -1) + return STATUS_ERROR; + + if (ntfs_ib_copy_tail(icx, ib, median, new_vcn)) { + ntfs_ibm_clear(icx, new_vcn); + return STATUS_ERROR; + } + + if (ntfs_icx_parent_vcn(icx) == VCN_INDEX_ROOT_PARENT) + ret = ntfs_ir_insert_median(icx, median, new_vcn); + else + ret = ntfs_ib_insert(icx, median, new_vcn); + + ntfs_inode_mark_dirty(icx->actx->ntfs_ino); + + if (ret != STATUS_OK) { + ntfs_ibm_clear(icx, new_vcn); + return ret; + } + + ret = ntfs_ib_cut_tail(icx, ib, median); + + return ret; +} + + +static int ntfs_ie_add(ntfs_index_context *icx, INDEX_ENTRY *ie) +{ + char *fn; + INDEX_HEADER *ih; + int allocated_size, new_size; + int ret = STATUS_ERROR; + + fn = ntfs_ie_filename_get(ie); + ntfs_log_trace("file: '%s'\n", fn); + + while (1) { + + if (!ntfs_index_lookup(&ie->key, le16_to_cpu(ie->key_length), icx)) { + errno = EEXIST; + ntfs_log_perror("Index already have such entry"); + goto err_out; + } + if (errno != ENOENT) { + ntfs_log_perror("Failed to find place for new entry"); + goto err_out; + } + + if (icx->is_in_root) + ih = &icx->ir->index; + else + ih = &icx->ib->index; + + allocated_size = le32_to_cpu(ih->allocated_size); + new_size = le32_to_cpu(ih->index_length) + le16_to_cpu(ie->length); + + if (new_size <= allocated_size) + break; + + ntfs_log_trace("index block sizes: allocated: %d needed: %d\n", + allocated_size, new_size); + + if (icx->is_in_root) { + if (ntfs_ir_make_space(icx, new_size) == STATUS_ERROR) + goto err_out; + } else { + if (ntfs_ib_split(icx, icx->ib) == STATUS_ERROR) + goto err_out; + } + + ntfs_inode_mark_dirty(icx->actx->ntfs_ino); + ntfs_index_ctx_reinit(icx); + } + + ntfs_ie_insert(ih, ie, icx->entry); + ntfs_index_entry_mark_dirty(icx); + + ret = STATUS_OK; +err_out: + ntfs_attr_name_free(&fn); + ntfs_log_trace("%s\n", ret ? "Failed" : "Done"); + return ret; +} + +/** + * ntfs_index_add_filename - add filename to directory index + * @ni: ntfs inode describing directory to which index add filename + * @fn: FILE_NAME attribute to add + * @mref: reference of the inode which @fn describes + * + * Return 0 on success or -1 on error with errno set to the error code. + */ +int ntfs_index_add_filename(ntfs_inode *ni, FILE_NAME_ATTR *fn, MFT_REF mref) +{ + INDEX_ENTRY *ie; + ntfs_index_context *icx; + int fn_size, ie_size, ret = -1; + + ntfs_log_trace("Entering\n"); + + if (!ni || !fn) { + ntfs_log_error("Invalid arguments.\n"); + errno = EINVAL; + return -1; + } + + fn_size = (fn->file_name_length * sizeof(ntfschar)) + + sizeof(FILE_NAME_ATTR); + ie_size = (sizeof(INDEX_ENTRY_HEADER) + fn_size + 7) & ~7; + + ie = ntfs_calloc(ie_size); + if (!ie) + return -1; + + ie->indexed_file = cpu_to_le64(mref); + ie->length = cpu_to_le16(ie_size); + ie->key_length = cpu_to_le16(fn_size); + memcpy(&ie->key, fn, fn_size); + + icx = ntfs_index_ctx_get(ni, NTFS_INDEX_I30, 4); + if (!icx) + goto out; + + ret = ntfs_ie_add(icx, ie); + + ntfs_index_ctx_put(icx); +out: + free(ie); + return ret; +} + +static int ntfs_ih_takeout(ntfs_index_context *icx, INDEX_HEADER *ih, + INDEX_ENTRY *ie, INDEX_BLOCK *ib) +{ + INDEX_ENTRY *ie_roam; + int ret = STATUS_ERROR; + + ntfs_log_trace("Entering\n"); + + ie_roam = ntfs_ie_dup_novcn(ie); + if (!ie_roam) + return STATUS_ERROR; + + ntfs_ie_delete(ih, ie); + + if (ntfs_icx_parent_vcn(icx) == VCN_INDEX_ROOT_PARENT) + ntfs_inode_mark_dirty(icx->actx->ntfs_ino); + else + if (ntfs_ib_write(icx, ntfs_icx_parent_vcn(icx), ib)) + goto out; + + ntfs_index_ctx_reinit(icx); + + ret = ntfs_ie_add(icx, ie_roam); +out: + free(ie_roam); + return ret; +} + +/** + * Used if an empty index block to be deleted has END entry as the parent + * in the INDEX_ROOT which is the only one there. + */ +static void ntfs_ir_leafify(ntfs_index_context *icx, INDEX_HEADER *ih) +{ + INDEX_ENTRY *ie; + + ntfs_log_trace("Entering\n"); + + ie = ntfs_ie_get_first(ih); + ie->ie_flags &= ~INDEX_ENTRY_NODE; + ie->length -= sizeof(VCN); + + ih->index_length -= sizeof(VCN); + ih->ih_flags &= ~LARGE_INDEX; + + /* Not fatal error */ + ntfs_ir_truncate(icx, le32_to_cpu(ih->index_length)); + + ntfs_inode_mark_dirty(icx->actx->ntfs_ino); + ntfs_index_ctx_reinit(icx); +} + +/** + * Used if an empty index block to be deleted has END entry as the parent + * in the INDEX_ROOT which is not the only one there. + */ +static int ntfs_ih_reparent_end(ntfs_index_context *icx, INDEX_HEADER *ih, + INDEX_BLOCK *ib) +{ + INDEX_ENTRY *ie, *ie_prev; + + ntfs_log_trace("Entering\n"); + + ie = ntfs_ie_get_by_pos(ih, ntfs_icx_parent_pos(icx)); + ie_prev = ntfs_ie_prev(ih, ie); + + ntfs_ie_set_vcn(ie, ntfs_ie_get_vcn(ie_prev)); + + return ntfs_ih_takeout(icx, ih, ie_prev, ib); +} + +static int ntfs_index_rm_leaf(ntfs_index_context *icx) +{ + INDEX_BLOCK *ib = NULL; + INDEX_HEADER *parent_ih; + INDEX_ENTRY *ie; + int ret = STATUS_ERROR; + + ntfs_log_trace("pindex: %d\n", icx->pindex); + + if (ntfs_icx_parent_dec(icx)) + return STATUS_ERROR; + + if (ntfs_ibm_clear(icx, icx->parent_vcn[icx->pindex + 1])) + return STATUS_ERROR; + + if (ntfs_icx_parent_vcn(icx) == VCN_INDEX_ROOT_PARENT) + parent_ih = &icx->ir->index; + else { + ib = ntfs_malloc(icx->block_size); + if (!ib) + return STATUS_ERROR; + + if (ntfs_ib_read(icx, ntfs_icx_parent_vcn(icx), ib)) + goto out; + + parent_ih = &ib->index; + } + + ie = ntfs_ie_get_by_pos(parent_ih, ntfs_icx_parent_pos(icx)); + if (!ntfs_ie_end(ie)) { + ret = ntfs_ih_takeout(icx, parent_ih, ie, ib); + goto out; + } + + if (ntfs_ih_zero_entry(parent_ih)) { + + if (ntfs_icx_parent_vcn(icx) == VCN_INDEX_ROOT_PARENT) { + ntfs_ir_leafify(icx, parent_ih); + goto ok; + } + + ret = ntfs_index_rm_leaf(icx); + goto out; + } + + if (ntfs_ih_reparent_end(icx, parent_ih, ib)) + goto out; +ok: + ret = STATUS_OK; +out: + free(ib); + return ret; +} + +static int ntfs_index_rm_node(ntfs_index_context *icx) +{ + int entry_pos; + VCN vcn; + INDEX_BLOCK *ib = NULL; + INDEX_ENTRY *ie_succ, *ie, *entry = icx->entry; + INDEX_HEADER *ih; + u32 new_size; + int delta, ret = STATUS_ERROR; + + ntfs_log_trace("Entering\n"); + + if (!icx->ia_na) { + icx->ia_na = ntfs_ia_open(icx, icx->ni); + if (!icx->ia_na) + return STATUS_ERROR; + } + + ib = ntfs_malloc(icx->block_size); + if (!ib) + return STATUS_ERROR; + + ie_succ = ntfs_ie_get_next(icx->entry); + entry_pos = icx->parent_pos[icx->pindex]++; +descend: + vcn = ntfs_ie_get_vcn(ie_succ); + if (ntfs_ib_read(icx, vcn, ib)) + goto out; + + ie_succ = ntfs_ie_get_first(&ib->index); + + if (ntfs_icx_parent_inc(icx)) + goto out; + + icx->parent_vcn[icx->pindex] = vcn; + icx->parent_pos[icx->pindex] = 0; + + if ((ib->index.ih_flags & NODE_MASK) == INDEX_NODE) + goto descend; + + if (ntfs_ih_zero_entry(&ib->index)) { + errno = EOPNOTSUPP; + ntfs_log_perror("Failed to find any entry in an index block. " + "Please run chkdsk."); + goto out; + } + + ie = ntfs_ie_dup(ie_succ); + if (!ie) + goto out; + + if (ntfs_ie_add_vcn(&ie)) + goto out2; + + ntfs_ie_set_vcn(ie, ntfs_ie_get_vcn(icx->entry)); + + if (icx->is_in_root) + ih = &icx->ir->index; + else + ih = &icx->ib->index; + + delta = ie->length - icx->entry->length; + new_size = le32_to_cpu(ih->index_length) + delta; + if (delta > 0) { + if (icx->is_in_root) { + if (ntfs_ir_truncate(icx, new_size)) { + errno = EOPNOTSUPP; + ntfs_log_perror("Denied to truncate INDEX ROOT during entry removal"); + goto out2; + } + + ih = &icx->ir->index; + entry = ntfs_ie_get_by_pos(ih, entry_pos); + + } else if (new_size > ih->allocated_size) { + errno = EOPNOTSUPP; + ntfs_log_perror("Denied to split INDEX BLOCK during entry removal"); + goto out2; + } + } + + ntfs_ie_delete(ih, entry); + ntfs_ie_insert(ih, ie, entry); + + if (icx->is_in_root) { + if (ntfs_ir_truncate(icx, new_size)) + goto out2; + ntfs_inode_mark_dirty(icx->actx->ntfs_ino); + } else + if (ntfs_icx_ib_write(icx)) + goto out2; + + ntfs_ie_delete(&ib->index, ie_succ); + + if (ntfs_ih_zero_entry(&ib->index)) { + if (ntfs_index_rm_leaf(icx)) + goto out2; + } else + if (ntfs_ib_write(icx, vcn, ib)) + goto out2; + + ret = STATUS_OK; +out2: + free(ie); +out: + free(ib); + return ret; +} + +/** + * ntfs_index_rm - remove entry from the index + * @icx: index context describing entry to delete + * + * Delete entry described by @icx from the index. Index context is always + * reinitialized after use of this function, so it can be used for index + * lookup once again. + * + * Return 0 on success or -1 on error with errno set to the error code. + */ +int ntfs_index_rm(ntfs_index_context *icx) +{ + INDEX_HEADER *ih; + int err; + + ntfs_log_trace("Entering\n"); + + if (!icx || (!icx->ib && !icx->ir) || ntfs_ie_end(icx->entry)) { + ntfs_log_error("Invalid arguments.\n"); + errno = EINVAL; + goto err_out; + } + if (icx->is_in_root) + ih = &icx->ir->index; + else + ih = &icx->ib->index; + + if (icx->entry->ie_flags & INDEX_ENTRY_NODE) { + + if (ntfs_index_rm_node(icx)) + goto err_out; + + } else if (icx->is_in_root || !ntfs_ih_one_entry(ih)) { + + ntfs_ie_delete(ih, icx->entry); + + if (icx->is_in_root) { + err = ntfs_ir_truncate(icx, le32_to_cpu(ih->index_length)); + if (err != STATUS_OK) + goto err_out; + } else + if (ntfs_icx_ib_write(icx)) + goto err_out; + } else { + if (ntfs_index_rm_leaf(icx)) + goto err_out; + } + + ntfs_index_ctx_reinit(icx); + ntfs_log_trace("Done.\n"); + return 0; +err_out: + err = errno; + ntfs_index_ctx_reinit(icx); + errno = err; + ntfs_log_trace("Failed.\n"); + return -1; +} + +/** + * ntfs_index_root_get - read the index root of an attribute + * @ni: open ntfs inode in which the ntfs attribute resides + * @attr: attribute for which we want its index root + * + * This function will read the related index root an ntfs attribute. + * + * On success a buffer is allocated with the content of the index root + * and which needs to be freed when it's not needed anymore. + * + * On error NULL is returned with errno set to the error code. + */ +INDEX_ROOT *ntfs_index_root_get(ntfs_inode *ni, ATTR_RECORD *attr) +{ + ntfs_attr_search_ctx *ctx; + ntfschar *name; + INDEX_ROOT *root = NULL; + + name = (ntfschar *)((u8 *)attr + le16_to_cpu(attr->name_offset)); + + if (!ntfs_ir_lookup(ni, name, attr->name_length, &ctx)) + return NULL; + + root = ntfs_malloc(sizeof(INDEX_ROOT)); + if (!root) + goto out; + + *root = *((INDEX_ROOT *)((u8 *)ctx->attr + + le16_to_cpu(ctx->attr->value_offset))); +out: + ntfs_attr_put_search_ctx(ctx); + return root; +} + + diff --git a/libntfs-3g/inode.c b/libntfs-3g/inode.c new file mode 100644 index 00000000..7e22f343 --- /dev/null +++ b/libntfs-3g/inode.c @@ -0,0 +1,1157 @@ +/** + * inode.c - Inode handling code. Originated from the Linux-NTFS project. + * + * Copyright (c) 2002-2005 Anton Altaparmakov + * Copyright (c) 2002-2006 Szabolcs Szakacsits + * Copyright (c) 2004-2005 Yura Pakhuchiy + * Copyright (c) 2004-2005 Richard Russon + * + * 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 + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program/include file 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 + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif +#ifdef HAVE_ERRNO_H +#include +#endif + +#include "compat.h" +#include "types.h" +#include "attrib.h" +#include "inode.h" +#include "debug.h" +#include "mft.h" +#include "attrlist.h" +#include "runlist.h" +#include "lcnalloc.h" +#include "index.h" +#include "dir.h" +#include "ntfstime.h" +#include "logging.h" +#include "misc.h" + +/** + * ntfs_inode_mark_dirty - set the inode (and its base inode if it exists) dirty + * @ni: ntfs inode to set dirty + * + * Set the inode @ni dirty so it is written out later (at the latest at + * ntfs_inode_close() time). If @ni is an extent inode, set the base inode + * dirty, too. + * + * This function cannot fail. + */ +void ntfs_inode_mark_dirty(ntfs_inode *ni) +{ + NInoSetDirty(ni); + if (ni->nr_extents == -1) + NInoSetDirty(ni->base_ni); +} + +/** + * __ntfs_inode_allocate - Create and initialise an NTFS inode object + * @vol: + * + * Description... + * + * Returns: + */ +static ntfs_inode *__ntfs_inode_allocate(ntfs_volume *vol) +{ + ntfs_inode *ni; + + ni = (ntfs_inode*)calloc(1, sizeof(ntfs_inode)); + if (ni) + ni->vol = vol; + return ni; +} + +/** + * ntfs_inode_allocate - Create an NTFS inode object + * @vol: + * + * Description... + * + * Returns: + */ +ntfs_inode *ntfs_inode_allocate(ntfs_volume *vol) +{ + return __ntfs_inode_allocate(vol); +} + +/** + * __ntfs_inode_release - Destroy an NTFS inode object + * @ni: + * + * Description... + * + * Returns: + */ +static int __ntfs_inode_release(ntfs_inode *ni) +{ + if (NInoDirty(ni)) + ntfs_log_debug("Eeek. Discarding dirty inode!\n"); + if (NInoAttrList(ni) && ni->attr_list) + free(ni->attr_list); + free(ni->mrec); + free(ni); + return 0; +} + +/** + * ntfs_inode_open - open an inode ready for access + * @vol: volume to get the inode from + * @mref: inode number / mft record number to open + * + * Allocate an ntfs_inode structure and initialize it for the given inode + * specified by @mref. @mref specifies the inode number / mft record to read, + * including the sequence number, which can be 0 if no sequence number checking + * is to be performed. + * + * Then, allocate a buffer for the mft record, read the mft record from the + * volume @vol, and attach it to the ntfs_inode structure (->mrec). The + * mft record is mst deprotected and sanity checked for validity and we abort + * if deprotection or checks fail. + * + * Finally, search for an attribute list attribute in the mft record and if one + * is found, load the attribute list attribute value and attach it to the + * ntfs_inode structure (->attr_list). Also set the NI_AttrList bit to indicate + * this. + * + * Return a pointer to the ntfs_inode structure on success or NULL on error, + * with errno set to the error code. + */ +ntfs_inode *ntfs_inode_open(ntfs_volume *vol, const MFT_REF mref) +{ + s64 l; + ntfs_inode *ni; + ntfs_attr_search_ctx *ctx; + int err = 0; + STANDARD_INFORMATION *std_info; + + ntfs_log_trace("Entering for inode 0x%llx.\n", MREF(mref)); + if (!vol) { + errno = EINVAL; + return NULL; + } + ni = __ntfs_inode_allocate(vol); + if (!ni) + return NULL; + if (ntfs_file_record_read(vol, mref, &ni->mrec, NULL)) + goto err_out; + if (!(ni->mrec->flags & MFT_RECORD_IN_USE)) { + err = ENOENT; + goto err_out; + } + ni->mft_no = MREF(mref); + ctx = ntfs_attr_get_search_ctx(ni, NULL); + if (!ctx) + goto err_out; + /* Receive some basic information about inode. */ + if (ntfs_attr_lookup(AT_STANDARD_INFORMATION, AT_UNNAMED, + 0, CASE_SENSITIVE, 0, NULL, 0, ctx)) { + err = errno; + ntfs_log_trace("Failed to receive STANDARD_INFORMATION " + "attribute.\n"); + goto put_err_out; + } + std_info = (STANDARD_INFORMATION *)((u8 *)ctx->attr + + le16_to_cpu(ctx->attr->value_offset)); + ni->flags = std_info->file_attributes; + ni->creation_time = ntfs2utc(std_info->creation_time); + ni->last_data_change_time = ntfs2utc(std_info->last_data_change_time); + ni->last_mft_change_time = ntfs2utc(std_info->last_mft_change_time); + ni->last_access_time = ntfs2utc(std_info->last_access_time); + /* Set attribute list information. */ + if (ntfs_attr_lookup(AT_ATTRIBUTE_LIST, AT_UNNAMED, 0, 0, 0, NULL, 0, + ctx)) { + if (errno != ENOENT) + goto put_err_out; + /* Attribute list attribute does not present. */ + goto get_size; + } + NInoSetAttrList(ni); + l = ntfs_get_attribute_value_length(ctx->attr); + if (!l) + goto put_err_out; + if (l > 0x40000) { + err = EIO; + goto put_err_out; + } + ni->attr_list_size = l; + ni->attr_list = ntfs_malloc(ni->attr_list_size); + if (!ni->attr_list) + goto put_err_out; + l = ntfs_get_attribute_value(vol, ctx->attr, ni->attr_list); + if (!l) + goto put_err_out; + if (l != ni->attr_list_size) { + err = EIO; + goto put_err_out; + } +get_size: + if (ntfs_attr_lookup(AT_DATA, AT_UNNAMED, 0, 0, 0, NULL, 0, ctx)) { + if (errno != ENOENT) + goto put_err_out; + /* Directory or special file. */ + ni->data_size = ni->allocated_size = 0; + } else { + if (ctx->attr->non_resident) { + ni->data_size = sle64_to_cpu(ctx->attr->data_size); + if (ctx->attr->flags & + (ATTR_IS_COMPRESSED | ATTR_IS_SPARSE)) + ni->allocated_size = sle64_to_cpu( + ctx->attr->compressed_size); + else + ni->allocated_size = sle64_to_cpu( + ctx->attr->allocated_size); + } else { + ni->data_size = le32_to_cpu(ctx->attr->value_length); + ni->allocated_size = (ni->data_size + 7) & ~7; + } + } + ntfs_attr_put_search_ctx(ctx); + return ni; +put_err_out: + if (!err) + err = errno; + ntfs_attr_put_search_ctx(ctx); +err_out: + if (!err) + err = errno; + __ntfs_inode_release(ni); + errno = err; + return NULL; +} + +/** + * ntfs_inode_close - close an ntfs inode and free all associated memory + * @ni: ntfs inode to close + * + * Make sure the ntfs inode @ni is clean. + * + * If the ntfs inode @ni is a base inode, close all associated extent inodes, + * then deallocate all memory attached to it, and finally free the ntfs inode + * structure itself. + * + * If it is an extent inode, we disconnect it from its base inode before we + * destroy it. + * + * Return 0 on success or -1 on error with errno set to the error code. On + * error, @ni has not been freed. The user should attempt to handle the error + * and call ntfs_inode_close() again. The following error codes are defined: + * + * EBUSY @ni and/or its attribute list runlist is/are dirty and the + * attempt to write it/them to disk failed. + * EINVAL @ni is invalid (probably it is an extent inode). + * EIO I/O error while trying to write inode to disk. + */ +int ntfs_inode_close(ntfs_inode *ni) +{ + if (!ni) + return 0; + + ntfs_log_trace("Entering for inode 0x%llx.\n", (long long) ni->mft_no); + + /* If we have dirty metadata, write it out. */ + if (NInoDirty(ni) || NInoAttrListDirty(ni)) { + if (ntfs_inode_sync(ni)) { + if (errno != EIO) + errno = EBUSY; + return -1; + } + } + /* Is this a base inode with mapped extent inodes? */ + if (ni->nr_extents > 0) { + while (ni->nr_extents > 0) { + if (ntfs_inode_close(ni->extent_nis[0])) { + if (errno != EIO) + errno = EBUSY; + return -1; + } + } + } else if (ni->nr_extents == -1) { + ntfs_inode **tmp_nis; + ntfs_inode *base_ni; + s32 i; + + /* + * If the inode is an extent inode, disconnect it from the + * base inode before destroying it. + */ + base_ni = ni->base_ni; + for (i = 0; i < base_ni->nr_extents; ++i) { + tmp_nis = base_ni->extent_nis; + if (tmp_nis[i] != ni) + continue; + /* Found it. Disconnect. */ + memmove(tmp_nis + i, tmp_nis + i + 1, + (base_ni->nr_extents - i - 1) * + sizeof(ntfs_inode *)); + /* Buffer should be for multiple of four extents. */ + if ((--base_ni->nr_extents) & 3) { + i = -1; + break; + } + /* + * ElectricFence is unhappy with realloc(x,0) as free(x) + * thus we explicitly separate these two cases. + */ + if (base_ni->nr_extents) { + /* Resize the memory buffer. */ + tmp_nis = realloc(tmp_nis, base_ni->nr_extents * + sizeof(ntfs_inode *)); + /* Ignore errors, they don't really matter. */ + if (tmp_nis) + base_ni->extent_nis = tmp_nis; + } else if (tmp_nis) + free(tmp_nis); + /* Allow for error checking. */ + i = -1; + break; + } + if (i != -1) + ntfs_log_debug("Extent inode was not attached to base inode! " + "Weird! Continuing regardless.\n"); + } + return __ntfs_inode_release(ni); +} + +/** + * ntfs_extent_inode_open - load an extent inode and attach it to its base + * @base_ni: base ntfs inode + * @mref: mft reference of the extent inode to load (in little endian) + * + * First check if the extent inode @mref is already attached to the base ntfs + * inode @base_ni, and if so, return a pointer to the attached extent inode. + * + * If the extent inode is not already attached to the base inode, allocate an + * ntfs_inode structure and initialize it for the given inode @mref. @mref + * specifies the inode number / mft record to read, including the sequence + * number, which can be 0 if no sequence number checking is to be performed. + * + * Then, allocate a buffer for the mft record, read the mft record from the + * volume @base_ni->vol, and attach it to the ntfs_inode structure (->mrec). + * The mft record is mst deprotected and sanity checked for validity and we + * abort if deprotection or checks fail. + * + * Finally attach the ntfs inode to its base inode @base_ni and return a + * pointer to the ntfs_inode structure on success or NULL on error, with errno + * set to the error code. + * + * Note, extent inodes are never closed directly. They are automatically + * disposed off by the closing of the base inode. + */ +ntfs_inode *ntfs_extent_inode_open(ntfs_inode *base_ni, const MFT_REF mref) +{ + u64 mft_no = MREF_LE(mref); + ntfs_inode *ni; + ntfs_inode **extent_nis; + int i; + + if (!base_ni) { + errno = EINVAL; + return NULL; + } + ntfs_log_trace("Opening extent inode 0x%llx (base mft record 0x%llx).\n", + (unsigned long long)mft_no, + (unsigned long long)base_ni->mft_no); + /* Is the extent inode already open and attached to the base inode? */ + if (base_ni->nr_extents > 0) { + extent_nis = base_ni->extent_nis; + for (i = 0; i < base_ni->nr_extents; i++) { + u16 seq_no; + + ni = extent_nis[i]; + if (mft_no != ni->mft_no) + continue; + /* Verify the sequence number if given. */ + seq_no = MSEQNO_LE(mref); + if (seq_no && seq_no != le16_to_cpu( + ni->mrec->sequence_number)) { + ntfs_log_debug("Found stale extent mft reference! " + "Corrupt file system. Run chkdsk.\n"); + errno = EIO; + return NULL; + } + /* We are done, return the extent inode. */ + return ni; + } + } + /* Wasn't there, we need to load the extent inode. */ + ni = __ntfs_inode_allocate(base_ni->vol); + if (!ni) + return NULL; + if (ntfs_file_record_read(base_ni->vol, le64_to_cpu(mref), &ni->mrec, + NULL)) + goto err_out; + ni->mft_no = mft_no; + ni->nr_extents = -1; + ni->base_ni = base_ni; + /* Attach extent inode to base inode, reallocating memory if needed. */ + if (!(base_ni->nr_extents & 3)) { + i = (base_ni->nr_extents + 4) * sizeof(ntfs_inode *); + + extent_nis = ntfs_malloc(i); + if (!extent_nis) + goto err_out; + if (base_ni->nr_extents) { + memcpy(extent_nis, base_ni->extent_nis, + i - 4 * sizeof(ntfs_inode *)); + free(base_ni->extent_nis); + } + base_ni->extent_nis = extent_nis; + } + base_ni->extent_nis[base_ni->nr_extents++] = ni; + return ni; +err_out: + i = errno; + __ntfs_inode_release(ni); + errno = i; + ntfs_log_perror("Failed to open extent inode"); + return NULL; +} + +/** + * ntfs_inode_attach_all_extents - attach all extents for target inode + * @ni: opened ntfs inode for which perform attach + * + * Return 0 on success and -1 on error with errno set to the error code. + */ +int ntfs_inode_attach_all_extents(ntfs_inode *ni) +{ + ATTR_LIST_ENTRY *ale; + u64 prev_attached = 0; + + if (!ni) { + ntfs_log_trace("Invalid arguments.\n"); + errno = EINVAL; + return -1; + } + + if (ni->nr_extents == -1) + ni = ni->base_ni; + + ntfs_log_trace("Entering for inode 0x%llx.\n", (long long) ni->mft_no); + + /* Inode haven't got attribute list, thus nothing to attach. */ + if (!NInoAttrList(ni)) + return 0; + + if (!ni->attr_list) { + ntfs_log_trace("Corrupt in-memory struct.\n"); + errno = EINVAL; + return -1; + } + + /* Walk through attribute list and attach all extents. */ + errno = 0; + ale = (ATTR_LIST_ENTRY *)ni->attr_list; + while ((u8*)ale < ni->attr_list + ni->attr_list_size) { + if (ni->mft_no != MREF_LE(ale->mft_reference) && + prev_attached != MREF_LE(ale->mft_reference)) { + if (!ntfs_extent_inode_open(ni, + MREF_LE(ale->mft_reference))) { + ntfs_log_trace("Couldn't attach extent inode.\n"); + return -1; + } + prev_attached = MREF_LE(ale->mft_reference); + } + ale = (ATTR_LIST_ENTRY *)((u8*)ale + le16_to_cpu(ale->length)); + } + return 0; +} + +/** + * ntfs_inode_sync_standard_information - update standard information attribute + * @ni: ntfs inode to update standard information + * + * Return 0 on success or -1 on error with errno set to the error code. + */ +static int ntfs_inode_sync_standard_information(ntfs_inode *ni) +{ + ntfs_attr_search_ctx *ctx; + STANDARD_INFORMATION *std_info; + int err; + + ntfs_log_trace("Entering for inode 0x%llx.\n", (long long) ni->mft_no); + + ctx = ntfs_attr_get_search_ctx(ni, NULL); + if (!ctx) + return -1; + if (ntfs_attr_lookup(AT_STANDARD_INFORMATION, AT_UNNAMED, + 0, CASE_SENSITIVE, 0, NULL, 0, ctx)) { + err = errno; + ntfs_log_trace("Failed to receive STANDARD_INFORMATION " + "attribute.\n"); + ntfs_attr_put_search_ctx(ctx); + errno = err; + return -1; + } + std_info = (STANDARD_INFORMATION *)((u8 *)ctx->attr + + le16_to_cpu(ctx->attr->value_offset)); + std_info->file_attributes = ni->flags; + std_info->creation_time = utc2ntfs(ni->creation_time); + std_info->last_data_change_time = utc2ntfs(ni->last_data_change_time); + std_info->last_mft_change_time = utc2ntfs(ni->last_mft_change_time); + std_info->last_access_time = utc2ntfs(ni->last_access_time); + ntfs_inode_mark_dirty(ctx->ntfs_ino); + ntfs_attr_put_search_ctx(ctx); + return 0; +} + +/** + * ntfs_inode_sync_file_name - update FILE_NAME attributes + * @ni: ntfs inode to update FILE_NAME attributes + * + * Update all FILE_NAME attributes for inode @ni in the index. + * + * Return 0 on success or -1 on error with errno set to the error code. + */ +static int ntfs_inode_sync_file_name(ntfs_inode *ni) +{ + ntfs_attr_search_ctx *ctx = NULL; + ntfs_index_context *ictx; + ntfs_inode *index_ni; + FILE_NAME_ATTR *fn; + int err = 0; + + ntfs_log_trace("Entering for inode 0x%llx.\n", (long long) ni->mft_no); + + ctx = ntfs_attr_get_search_ctx(ni, NULL); + if (!ctx) { + err = errno; + ntfs_log_trace("Failed to get attribute search context.\n"); + goto err_out; + } + /* Walk through all FILE_NAME attributes and update them. */ + while (!ntfs_attr_lookup(AT_FILE_NAME, NULL, 0, 0, 0, NULL, 0, ctx)) { + fn = (FILE_NAME_ATTR *)((u8 *)ctx->attr + + le16_to_cpu(ctx->attr->value_offset)); + if (MREF_LE(fn->parent_directory) == ni->mft_no) { + /* + * WARNING: We cheater here and obtain 2 attribute + * search contexts for one inode (first we obtained + * above, second will be obtained inside + * ntfs_index_lookup), it's acceptable for library, + * but will lock kernel. + */ + index_ni = ni; + } else + index_ni = ntfs_inode_open(ni->vol, + le64_to_cpu(fn->parent_directory)); + if (!index_ni) { + if (!err) + err = errno; + ntfs_log_trace("Failed to open inode with index.\n"); + continue; + } + ictx = ntfs_index_ctx_get(index_ni, NTFS_INDEX_I30, 4); + if (!ictx) { + if (!err) + err = errno; + ntfs_log_trace("Failed to get index context.\n"); + ntfs_inode_close(index_ni); + continue; + } + if (ntfs_index_lookup(fn, sizeof(FILE_NAME_ATTR), ictx)) { + if (!err) { + if (errno == ENOENT) + err = EIO; + else + err = errno; + } + ntfs_log_trace("Index lookup failed.\n"); + ntfs_index_ctx_put(ictx); + ntfs_inode_close(index_ni); + continue; + } + /* Update flags and file size. */ + fn = (FILE_NAME_ATTR *)ictx->data; + fn->file_attributes = + (fn->file_attributes & ~FILE_ATTR_VALID_FLAGS) | + (ni->flags & FILE_ATTR_VALID_FLAGS); + fn->allocated_size = cpu_to_sle64(ni->allocated_size); + fn->data_size = cpu_to_sle64(ni->data_size); + fn->creation_time = utc2ntfs(ni->creation_time); + fn->last_data_change_time = utc2ntfs(ni->last_data_change_time); + fn->last_mft_change_time = utc2ntfs(ni->last_mft_change_time); + fn->last_access_time = utc2ntfs(ni->last_access_time); + ntfs_index_entry_mark_dirty(ictx); + ntfs_index_ctx_put(ictx); + if (ni != index_ni) + ntfs_inode_close(index_ni); + } + /* Check for real error occurred. */ + if (errno != ENOENT) { + err = errno; + ntfs_log_trace("Attribute lookup failed.\n"); + goto err_out; + } + ntfs_attr_put_search_ctx(ctx); + if (err) { + errno = err; + return -1; + } + return 0; +err_out: + if (ctx) + ntfs_attr_put_search_ctx(ctx); + errno = err; + return -1; +} + +/** + * ntfs_inode_sync - write the inode (and its dirty extents) to disk + * @ni: ntfs inode to write + * + * Write the inode @ni to disk as well as its dirty extent inodes if such + * exist and @ni is a base inode. If @ni is an extent inode, only @ni is + * written completely disregarding its base inode and any other extent inodes. + * + * For a base inode with dirty extent inodes if any writes fail for whatever + * reason, the failing inode is skipped and the sync process is continued. At + * the end the error condition that brought about the failure is returned. Thus + * the smallest amount of data loss possible occurs. + * + * Return 0 on success or -1 on error with errno set to the error code. + * The following error codes are defined: + * EINVAL - Invalid arguments were passed to the function. + * EBUSY - Inode and/or one of its extents is busy, try again later. + * EIO - I/O error while writing the inode (or one of its extents). + */ +int ntfs_inode_sync(ntfs_inode *ni) +{ + int err = 0; + + if (!ni) { + errno = EINVAL; + return -1; + } + + ntfs_log_trace("Entering for inode 0x%llx.\n", (long long) ni->mft_no); + + /* Update STANDARD_INFORMATION. */ + if ((ni->mrec->flags & MFT_RECORD_IN_USE) && ni->nr_extents != -1 && + ntfs_inode_sync_standard_information(ni)) { + if (!err || errno == EIO) { + err = errno; + if (err != EIO) + err = EBUSY; + } + ntfs_log_trace("Failed to sync standard information.\n"); + } + + /* Update FILE_NAME's in the index. */ + if ((ni->mrec->flags & MFT_RECORD_IN_USE) && ni->nr_extents != -1 && + NInoFileNameTestAndClearDirty(ni) && + ntfs_inode_sync_file_name(ni)) { + if (!err || errno == EIO) { + err = errno; + if (err != EIO) + err = EBUSY; + } + ntfs_log_trace("Failed to sync FILE_NAME attributes.\n"); + NInoFileNameSetDirty(ni); + } + + /* Write out attribute list from cache to disk. */ + if ((ni->mrec->flags & MFT_RECORD_IN_USE) && ni->nr_extents != -1 && + NInoAttrList(ni) && NInoAttrListTestAndClearDirty(ni)) { + ntfs_attr *na; + + na = ntfs_attr_open(ni, AT_ATTRIBUTE_LIST, AT_UNNAMED, 0); + if (!na) { + if (!err || errno == EIO) { + err = errno; + if (err != EIO) + err = EBUSY; + ntfs_log_trace("Attribute list sync failed (open " + "failed).\n"); + } + NInoAttrListSetDirty(ni); + } else { + if (na->data_size == ni->attr_list_size) { + if (ntfs_attr_pwrite(na, 0, ni->attr_list_size, + ni->attr_list) != + ni->attr_list_size) { + if (!err || errno == EIO) { + err = errno; + if (err != EIO) + err = EBUSY; + ntfs_log_trace("Attribute list sync " + "failed (write failed).\n"); + } + NInoAttrListSetDirty(ni); + } + } else { + err = EIO; + ntfs_log_trace("Attribute list sync failed (invalid size).\n"); + NInoAttrListSetDirty(ni); + } + ntfs_attr_close(na); + } + } + + /* Write this inode out to the $MFT (and $MFTMirr if applicable). */ + if (NInoTestAndClearDirty(ni)) { + if (ntfs_mft_record_write(ni->vol, ni->mft_no, ni->mrec)) { + if (!err || errno == EIO) { + err = errno; + if (err != EIO) + err = EBUSY; + } + NInoSetDirty(ni); + ntfs_log_trace("Base MFT record sync failed.\n"); + } + } + + /* If this is a base inode with extents write all dirty extents, too. */ + if (ni->nr_extents > 0) { + s32 i; + + for (i = 0; i < ni->nr_extents; ++i) { + ntfs_inode *eni; + + eni = ni->extent_nis[i]; + if (NInoTestAndClearDirty(eni)) { + if (ntfs_mft_record_write(eni->vol, eni->mft_no, + eni->mrec)) { + if (!err || errno == EIO) { + err = errno; + if (err != EIO) + err = EBUSY; + } + NInoSetDirty(eni); + ntfs_log_trace("Extent MFT record sync " + "failed.\n"); + } + } + } + } + + if (!err) + return 0; + errno = err; + return -1; +} + +/** + * ntfs_inode_add_attrlist - add attribute list to inode and fill it + * @ni: opened ntfs inode to which add attribute list + * + * Return 0 on success or -1 on error with errno set to the error code. + * The following error codes are defined: + * EINVAL - Invalid arguments were passed to the function. + * EEXIST - Attribute list already exist. + * EIO - Input/Ouput error occurred. + * ENOMEM - Not enough memory to perform add. + */ +int ntfs_inode_add_attrlist(ntfs_inode *ni) +{ + int err; + ntfs_attr_search_ctx *ctx; + u8 *al = NULL, *aln; + int al_len = 0; + ATTR_LIST_ENTRY *ale = NULL; + ntfs_attr *na; + + if (!ni) { + ntfs_log_trace("Invalid arguments.\n"); + errno = EINVAL; + return -1; + } + + ntfs_log_trace("Entering for inode 0x%llx.\n", (long long) ni->mft_no); + + if (NInoAttrList(ni) || ni->nr_extents) { + ntfs_log_trace("Inode already has got attribute list.\n"); + errno = EEXIST; + return -1; + } + + /* Form attribute list. */ + ctx = ntfs_attr_get_search_ctx(ni, NULL); + if (!ctx) { + err = errno; + ntfs_log_trace("Couldn't get search context.\n"); + goto err_out; + } + /* Walk through all attributes. */ + while (!ntfs_attr_lookup(AT_UNUSED, NULL, 0, 0, 0, NULL, 0, ctx)) { + + int ale_size; + + if (ctx->attr->type == AT_ATTRIBUTE_LIST) { + err = EIO; + ntfs_log_trace("Eeek! Attribute list already present.\n"); + goto put_err_out; + } + + ale_size = (sizeof(ATTR_LIST_ENTRY) + sizeof(ntfschar) * + ctx->attr->name_length + 7) & ~7; + al_len += ale_size; + + aln = realloc(al, al_len); + if (!aln) { + err = errno; + ntfs_log_perror("Failed to realloc %d bytes", al_len); + goto put_err_out; + } + ale = (ATTR_LIST_ENTRY *)(aln + ((u8 *)ale - al)); + al = aln; + + memset(ale, 0, ale_size); + + /* Add attribute to attribute list. */ + ale->type = ctx->attr->type; + ale->length = cpu_to_le16((sizeof(ATTR_LIST_ENTRY) + + sizeof(ntfschar) * ctx->attr->name_length + 7) & ~7); + ale->name_length = ctx->attr->name_length; + ale->name_offset = (u8 *)ale->name - (u8 *)ale; + if (ctx->attr->non_resident) + ale->lowest_vcn = ctx->attr->lowest_vcn; + else + ale->lowest_vcn = 0; + ale->mft_reference = MK_LE_MREF(ni->mft_no, + le16_to_cpu(ni->mrec->sequence_number)); + ale->instance = ctx->attr->instance; + memcpy(ale->name, (u8 *)ctx->attr + + le16_to_cpu(ctx->attr->name_offset), + ctx->attr->name_length * sizeof(ntfschar)); + ale = (ATTR_LIST_ENTRY *)(al + al_len); + } + /* Check for real error occurred. */ + if (errno != ENOENT) { + err = errno; + ntfs_log_trace("Attribute lookup failed.\n"); + goto put_err_out; + } + + /* Set in-memory attribute list. */ + ni->attr_list = al; + ni->attr_list_size = al_len; + NInoSetAttrList(ni); + NInoAttrListSetDirty(ni); + + /* Free space if there is not enough it for $ATTRIBUTE_LIST. */ + if (le32_to_cpu(ni->mrec->bytes_allocated) - + le32_to_cpu(ni->mrec->bytes_in_use) < + offsetof(ATTR_RECORD, resident_end)) { + if (ntfs_inode_free_space(ni, + offsetof(ATTR_RECORD, resident_end))) { + /* Failed to free space. */ + err = errno; + ntfs_log_trace("Failed to free space for " + "$ATTRIBUTE_LIST.\n"); + goto rollback; + } + } + + /* Add $ATTRIBUTE_LIST to mft record. */ + if (ntfs_resident_attr_record_add(ni, + AT_ATTRIBUTE_LIST, NULL, 0, NULL, 0, 0) < 0) { + err = errno; + ntfs_log_trace("Couldn't add $ATTRIBUTE_LIST to MFT record.\n"); + goto rollback; + } + + /* Resize it. */ + na = ntfs_attr_open(ni, AT_ATTRIBUTE_LIST, AT_UNNAMED, 0); + if (!na) { + err = errno; + ntfs_log_trace("Failed to open just added $ATTRIBUTE_LIST.\n"); + goto remove_attrlist_record; + } + if (ntfs_attr_truncate(na, al_len)) { + err = errno; + ntfs_log_trace("Failed to resize just added $ATTRIBUTE_LIST.\n"); + ntfs_attr_close(na); + goto remove_attrlist_record;; + } + + ntfs_attr_put_search_ctx(ctx); + ntfs_attr_close(na); + return 0; + +remove_attrlist_record: + /* Prevent ntfs_attr_recorm_rm from freeing attribute list. */ + ni->attr_list = NULL; + NInoClearAttrList(ni); + /* Remove $ATTRIBUTE_LIST record. */ + ntfs_attr_reinit_search_ctx(ctx); + if (!ntfs_attr_lookup(AT_ATTRIBUTE_LIST, NULL, 0, + CASE_SENSITIVE, 0, NULL, 0, ctx)) { + if (ntfs_attr_record_rm(ctx)) + ntfs_log_trace("Rollback failed. Failed to remove attribute " + "list record.\n"); + } else + ntfs_log_trace("Rollback failed. Couldn't find attribute list " + "record.\n"); + /* Setup back in-memory runlist. */ + ni->attr_list = al; + ni->attr_list_size = al_len; + NInoSetAttrList(ni); +rollback: + /* + * Scan attribute list for attributes that placed not in the base MFT + * record and move them to it. + */ + ntfs_attr_reinit_search_ctx(ctx); + ale = (ATTR_LIST_ENTRY*)al; + while ((u8*)ale < al + al_len) { + if (MREF_LE(ale->mft_reference) != ni->mft_no) { + if (!ntfs_attr_lookup(ale->type, ale->name, + ale->name_length, + CASE_SENSITIVE, + sle64_to_cpu(ale->lowest_vcn), + NULL, 0, ctx)) { + if (ntfs_attr_record_move_to(ctx, ni)) + ntfs_log_trace("Rollback failed. Couldn't " + "back attribute to base MFT record.\n"); + } else + ntfs_log_trace("Rollback failed. ntfs_attr_lookup " + "failed.\n"); + ntfs_attr_reinit_search_ctx(ctx); + } + ale = (ATTR_LIST_ENTRY*)((u8*)ale + le16_to_cpu(ale->length)); + } + /* Remove in-memory attribute list. */ + ni->attr_list = NULL; + ni->attr_list_size = 0; + NInoClearAttrList(ni); + NInoAttrListClearDirty(ni); +put_err_out: + ntfs_attr_put_search_ctx(ctx); +err_out: + free(al); + errno = err; + return -1; +} + +/** + * ntfs_inode_free_space - free space in the MFT record of inode + * @ni: ntfs inode in which MFT record free space + * @size: amount of space needed to free + * + * Return 0 on success or -1 on error with errno set to the error code. + */ +int ntfs_inode_free_space(ntfs_inode *ni, int size) +{ + ntfs_attr_search_ctx *ctx; + int freed, err; + + if (!ni || size < 0) { + ntfs_log_trace("Invalid arguments.\n"); + errno = EINVAL; + return -1; + } + + ntfs_log_trace("Entering for inode 0x%llx, size %d.\n", + (long long) ni->mft_no, size); + + freed = (le32_to_cpu(ni->mrec->bytes_allocated) - + le32_to_cpu(ni->mrec->bytes_in_use)); + + if (size <= freed) + return 0; + + ctx = ntfs_attr_get_search_ctx(ni, NULL); + if (!ctx) { + err = errno; + ntfs_log_trace("Failed to get attribute search context.\n"); + errno = err; + return -1; + } + + /* + * Chkdsk complain if $STANDARD_INFORMATION is not in the base MFT + * record. FIXME: I'm not sure in this, need to recheck. For now simply + * do not move $STANDARD_INFORMATION at all. + * + * Also we can't move $ATTRIBUTE_LIST from base MFT_RECORD, so position + * search context on first attribute after $STANDARD_INFORMATION and + * $ATTRIBUTE_LIST. + * + * Why we reposition instead of simply skip this attributes during + * enumeration? Because in case we have got only in-memory attribute + * list ntfs_attr_lookup will fail when it will try to find + * $ATTRIBUTE_LIST. + */ + if (ntfs_attr_lookup(AT_FILE_NAME, NULL, 0, CASE_SENSITIVE, 0, NULL, + 0, ctx)) { + if (errno != ENOENT) { + err = errno; + ntfs_log_trace("Attribute lookup failed.\n"); + goto put_err_out; + } + if (ctx->attr->type == AT_END) { + err = ENOSPC; + goto put_err_out; + } + } + + while (1) { + int record_size; + + /* + * Check whether attribute is from different MFT record. If so, + * find next, because we don't need such. + */ + while (ctx->ntfs_ino->mft_no != ni->mft_no) { + if (ntfs_attr_lookup(AT_UNUSED, NULL, 0, CASE_SENSITIVE, + 0, NULL, 0, ctx)) { + err = errno; + if (errno != ENOENT) { + ntfs_log_trace("Attribute lookup failed.\n"); + } else + err = ENOSPC; + goto put_err_out; + } + } + + record_size = le32_to_cpu(ctx->attr->length); + + /* Move away attribute. */ + if (ntfs_attr_record_move_away(ctx, 0)) { + err = errno; + ntfs_log_trace("Failed to move out attribute.\n"); + break; + } + freed += record_size; + + /* Check whether we done. */ + if (size <= freed) { + ntfs_attr_put_search_ctx(ctx); + return 0; + } + + /* + * Reposition to first attribute after $STANDARD_INFORMATION and + * $ATTRIBUTE_LIST (see comments upwards). + */ + ntfs_attr_reinit_search_ctx(ctx); + if (ntfs_attr_lookup(AT_FILE_NAME, NULL, 0, CASE_SENSITIVE, 0, + NULL, 0, ctx)) { + if (errno != ENOENT) { + err = errno; + ntfs_log_trace("Attribute lookup failed.\n"); + break; + } + if (ctx->attr->type == AT_END) { + err = ENOSPC; + break; + } + } + } +put_err_out: + ntfs_attr_put_search_ctx(ctx); + if (err == ENOSPC) + ntfs_log_trace("No attributes left that can be moved out.\n"); + errno = err; + return -1; +} + +/** + * ntfs_inode_update_atime - update access time for ntfs inode + * @ni: ntfs inode for which update access time + * + * This function usually get called when user read not metadata from inode. + * Do not update time for system files. + */ +void ntfs_inode_update_atime(ntfs_inode *ni) +{ + if (!NVolReadOnly(ni->vol) && !NVolNoATime(ni->vol) && (ni->mft_no >= + FILE_first_user || ni->mft_no == FILE_root)) { + ni->last_access_time = time(NULL); + NInoFileNameSetDirty(ni); + NInoSetDirty(ni); + } +} + +/** + * ntfs_inode_update_time - update all times for ntfs inode + * @ni: ntfs inode for which update times + * + * This function updates last access, mft and data change times. Usually + * get called when user write not metadata to inode. Do not update time for + * system files. + */ +void ntfs_inode_update_time(ntfs_inode *ni) +{ + if (!NVolReadOnly(ni->vol) && !NVolNoATime(ni->vol) && (ni->mft_no >= + FILE_first_user || ni->mft_no == FILE_root)) { + time_t now; + + now = time(NULL); + ni->last_access_time = now; + ni->last_data_change_time = now; + ni->last_mft_change_time = now; + NInoFileNameSetDirty(ni); + NInoSetDirty(ni); + } +} + +/** + * ntfs_inode_badclus_bad - check for $Badclus:$Bad data attribute + * @mft_no: mft record number where @attr is present + * @attr: attribute record used to check for the $Bad attribute + * + * Check if the mft record given by @mft_no and @attr contains the bad sector + * list. Please note that mft record numbers describing $Badclus extent inodes + * will not match the current $Badclus:$Bad check. + * + * On success return 1 if the file is $Badclus:$Bad, otherwise return 0. + * On error return -1 with errno set to the error code. + */ +int ntfs_inode_badclus_bad(u64 mft_no, ATTR_RECORD *attr) +{ + int len, ret = 0; + ntfschar *ustr; + + if (!attr) { + ntfs_log_error("Invalid argument.\n"); + errno = EINVAL; + return -1; + } + + if (mft_no != FILE_BadClus) + return 0; + + if (attr->type != AT_DATA) + return 0; + + if ((ustr = ntfs_str2ucs("$Bad", &len)) == NULL) { + ntfs_log_perror("Couldn't convert '$Bad' to Unicode"); + return -1; + } + + if (ustr && ntfs_names_are_equal(ustr, len, + (ntfschar *)((u8 *)attr + le16_to_cpu(attr->name_offset)), + attr->name_length, 0, NULL, 0)) + ret = 1; + + ntfs_ucsfree(ustr); + + return ret; +} diff --git a/libntfs-3g/lcnalloc.c b/libntfs-3g/lcnalloc.c new file mode 100644 index 00000000..0da647c3 --- /dev/null +++ b/libntfs-3g/lcnalloc.c @@ -0,0 +1,858 @@ +/** + * lcnalloc.c - Cluster (de)allocation code. Originated from the Linux-NTFS project. + * + * Copyright (c) 2002-2004 Anton Altaparmakov + * Copyright (c) 2004-2006 Szabolcs Szakacsits + * Copyright (c) 2004 Yura Pakhuchiy + * + * 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 + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program/include file 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 + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STDIO_H +#include +#endif +#ifdef HAVE_ERRNO_H +#include +#endif + +#include "types.h" +#include "attrib.h" +#include "bitmap.h" +#include "debug.h" +#include "runlist.h" +#include "volume.h" +#include "lcnalloc.h" +#include "logging.h" +#include "misc.h" + +/** + * ntfs_cluster_alloc - allocate clusters on an ntfs volume + * @vol: mounted ntfs volume on which to allocate the clusters + * @start_vcn: vcn to use for the first allocated cluster + * @count: number of clusters to allocate + * @start_lcn: starting lcn at which to allocate the clusters (or -1 if none) + * @zone: zone from which to allocate the clusters + * + * Allocate @count clusters preferably starting at cluster @start_lcn or at the + * current allocator position if @start_lcn is -1, on the mounted ntfs volume + * @vol. @zone is either DATA_ZONE for allocation of normal clusters and + * MFT_ZONE for allocation of clusters for the master file table, i.e. the + * $MFT/$DATA attribute. + * + * On success return a runlist describing the allocated cluster(s). + * + * On error return NULL with errno set to the error code. + * + * Notes on the allocation algorithm + * ================================= + * + * There are two data zones. First is the area between the end of the mft zone + * and the end of the volume, and second is the area between the start of the + * volume and the start of the mft zone. On unmodified/standard NTFS 1.x + * volumes, the second data zone doesn't exist due to the mft zone being + * expanded to cover the start of the volume in order to reserve space for the + * mft bitmap attribute. + * + * This is not the prettiest function but the complexity stems from the need of + * implementing the mft vs data zoned approach and from the fact that we have + * access to the lcn bitmap in portions of up to 8192 bytes at a time, so we + * need to cope with crossing over boundaries of two buffers. Further, the fact + * that the allocator allows for caller supplied hints as to the location of + * where allocation should begin and the fact that the allocator keeps track of + * where in the data zones the next natural allocation should occur, contribute + * to the complexity of the function. But it should all be worthwhile, because + * this allocator should: 1) be a full implementation of the MFT zone approach + * used by Windows, 2) cause reduction in fragmentation as much as possible, + * and 3) be speedy in allocations (the code is not optimized for speed, but + * the algorithm is, so further speed improvements are probably possible). + * + * FIXME: We should be monitoring cluster allocation and increment the MFT zone + * size dynamically but this is something for the future. We will just cause + * heavier fragmentation by not doing it and I am not even sure Windows would + * grow the MFT zone dynamically, so it might even be correct not to do this. + * The overhead in doing dynamic MFT zone expansion would be very large and + * unlikely worth the effort. (AIA) + * + * TODO: I have added in double the required zone position pointer wrap around + * logic which can be optimized to having only one of the two logic sets. + * However, having the double logic will work fine, but if we have only one of + * the sets and we get it wrong somewhere, then we get into trouble, so + * removing the duplicate logic requires _very_ careful consideration of _all_ + * possible code paths. So at least for now, I am leaving the double logic - + * better safe than sorry... (AIA) + */ +runlist *ntfs_cluster_alloc(ntfs_volume *vol, VCN start_vcn, s64 count, + LCN start_lcn, const NTFS_CLUSTER_ALLOCATION_ZONES zone) +{ + LCN zone_start, zone_end, bmp_pos, bmp_initial_pos, last_read_pos, lcn; + LCN prev_lcn = 0, prev_run_len = 0, mft_zone_size; + s64 clusters, br; + runlist *rl = NULL, *trl; + u8 *buf, *byte; + int err = 0, rlpos, rlsize, buf_size; + u8 pass, done_zones, search_zone, need_writeback, bit; + + ntfs_log_trace("Entering with count = 0x%llx, start_lcn = 0x%llx, zone = " + "%s_ZONE.\n", (long long)count, (long long)start_lcn, + zone == MFT_ZONE ? "MFT" : "DATA"); + if (!vol || count < 0 || start_lcn < -1 || !vol->lcnbmp_na || + (s8)zone < FIRST_ZONE || zone > LAST_ZONE) { + ntfs_log_trace("Invalid arguments!\n"); + errno = EINVAL; + return NULL; + } + + /* Return empty runlist if @count == 0 */ + if (!count) { + rl = ntfs_malloc(0x1000); + if (!rl) + return NULL; + rl[0].vcn = start_vcn; + rl[0].lcn = LCN_RL_NOT_MAPPED; + rl[0].length = 0; + return rl; + } + + /* Allocate memory. */ + buf = ntfs_malloc(8192); + if (!buf) + return NULL; + /* + * If no specific @start_lcn was requested, use the current data zone + * position, otherwise use the requested @start_lcn but make sure it + * lies outside the mft zone. Also set done_zones to 0 (no zones done) + * and pass depending on whether we are starting inside a zone (1) or + * at the beginning of a zone (2). If requesting from the MFT_ZONE, + * we either start at the current position within the mft zone or at + * the specified position. If the latter is out of bounds then we start + * at the beginning of the MFT_ZONE. + */ + done_zones = 0; + pass = 1; + /* + * zone_start and zone_end are the current search range. search_zone + * is 1 for mft zone, 2 for data zone 1 (end of mft zone till end of + * volume) and 4 for data zone 2 (start of volume till start of mft + * zone). + */ + zone_start = start_lcn; + if (zone_start < 0) { + if (zone == DATA_ZONE) + zone_start = vol->data1_zone_pos; + else + zone_start = vol->mft_zone_pos; + if (!zone_start) { + /* + * Zone starts at beginning of volume which means a + * single pass is sufficient. + */ + pass = 2; + } + } else if (zone == DATA_ZONE && zone_start >= vol->mft_zone_start && + zone_start < vol->mft_zone_end) { + zone_start = vol->mft_zone_end; + /* + * Starting at beginning of data1_zone which means a single + * pass in this zone is sufficient. + */ + pass = 2; + } else if (zone == MFT_ZONE && (zone_start < vol->mft_zone_start || + zone_start >= vol->mft_zone_end)) { + zone_start = vol->mft_lcn; + if (!vol->mft_zone_end) + zone_start = 0; + /* + * Starting at beginning of volume which means a single pass + * is sufficient. + */ + pass = 2; + } + if (zone == MFT_ZONE) { + zone_end = vol->mft_zone_end; + search_zone = 1; + } else /* if (zone == DATA_ZONE) */ { + /* Skip searching the mft zone. */ + done_zones |= 1; + if (zone_start >= vol->mft_zone_end) { + zone_end = vol->nr_clusters; + search_zone = 2; + } else { + zone_end = vol->mft_zone_start; + search_zone = 4; + } + } + /* + * bmp_pos is the current bit position inside the bitmap. We use + * bmp_initial_pos to determine whether or not to do a zone switch. + */ + bmp_pos = bmp_initial_pos = zone_start; + + /* Loop until all clusters are allocated, i.e. clusters == 0. */ + clusters = count; + rlpos = rlsize = 0; + while (1) { + ntfs_log_trace("Start of outer while loop: done_zones = 0x%x, " + "search_zone = %i, pass = %i, zone_start = " + "0x%llx, zone_end = 0x%llx, bmp_initial_pos = " + "0x%llx, bmp_pos = 0x%llx, rlpos = %i, rlsize = " + "%i.\n", done_zones, search_zone, pass, + (long long)zone_start, (long long)zone_end, + (long long)bmp_initial_pos, (long long)bmp_pos, + rlpos, rlsize); + /* Loop until we run out of free clusters. */ + last_read_pos = bmp_pos >> 3; + ntfs_log_trace("last_read_pos = 0x%llx.\n", (long long)last_read_pos); + br = ntfs_attr_pread(vol->lcnbmp_na, last_read_pos, 8192, buf); + if (br <= 0) { + if (!br) { + /* Reached end of attribute. */ + ntfs_log_trace("End of attribute reached. Skipping " + "to zone_pass_done.\n"); + goto zone_pass_done; + } + err = errno; + ntfs_log_trace("ntfs_attr_pread() failed. Aborting.\n"); + goto err_ret; + } + /* + * We might have read less than 8192 bytes if we are close to + * the end of the attribute. + */ + buf_size = (int)br << 3; + lcn = bmp_pos & 7; + bmp_pos &= ~7; + need_writeback = 0; + ntfs_log_trace("Before inner while loop: buf_size = %i, lcn = " + "0x%llx, bmp_pos = 0x%llx, need_writeback = %i.\n", + buf_size, (long long)lcn, (long long)bmp_pos, + need_writeback); + while (lcn < buf_size && lcn + bmp_pos < zone_end) { + byte = buf + (lcn >> 3); + ntfs_log_trace("In inner while loop: buf_size = %i, lcn = " + "0x%llx, bmp_pos = 0x%llx, " + "need_writeback = %i, byte ofs = 0x%x, " + "*byte = 0x%x.\n", buf_size, + (long long)lcn, (long long)bmp_pos, + need_writeback, (unsigned int)(lcn >> 3), + (unsigned int)*byte); + /* Skip full bytes. */ + if (*byte == 0xff) { + lcn = (lcn + 8) & ~7; + ntfs_log_trace("continuing while loop 1.\n"); + continue; + } + bit = 1 << (lcn & 7); + ntfs_log_trace("bit = %i.\n", bit); + /* If the bit is already set, go onto the next one. */ + if (*byte & bit) { + lcn++; + ntfs_log_trace("continuing while loop 2.\n"); + continue; + } + /* Reallocate memory if necessary. */ + if ((rlpos + 2) * (int)sizeof(runlist) >= rlsize) { + ntfs_log_trace("Reallocating space.\n"); + if (!rl) + ntfs_log_trace("First free bit is at LCN = " + "0x%llx.\n", (long long)(lcn + bmp_pos)); + rlsize += 4096; + trl = (runlist*)realloc(rl, rlsize); + if (!trl) { + err = ENOMEM; + ntfs_log_trace("Failed to allocate memory, " + "going to wb_err_ret.\n"); + goto wb_err_ret; + } + rl = trl; + ntfs_log_trace("Reallocated memory, rlsize = " + "0x%x.\n", rlsize); + } + /* Allocate the bitmap bit. */ + *byte |= bit; + /* We need to write this bitmap buffer back to disk! */ + need_writeback = 1; + ntfs_log_trace("*byte = 0x%x, need_writeback is set.\n", + (unsigned int)*byte); + /* + * Coalesce with previous run if adjacent LCNs. + * Otherwise, append a new run. + */ + ntfs_log_trace("Adding run (lcn 0x%llx, len 0x%llx), " + "prev_lcn = 0x%llx, lcn = 0x%llx, " + "bmp_pos = 0x%llx, prev_run_len = " + "0x%llx, rlpos = %i.\n", + (long long)(lcn + bmp_pos), 1LL, + (long long)prev_lcn, (long long)lcn, + (long long)bmp_pos, + (long long)prev_run_len, rlpos); + if (prev_lcn == lcn + bmp_pos - prev_run_len && rlpos) { + ntfs_log_trace("Coalescing to run (lcn 0x%llx, len " + "0x%llx).\n", + (long long)rl[rlpos - 1].lcn, + (long long) rl[rlpos - 1].length); + rl[rlpos - 1].length = ++prev_run_len; + ntfs_log_trace("Run now (lcn 0x%llx, len 0x%llx), " + "prev_run_len = 0x%llx.\n", + (long long)rl[rlpos - 1].lcn, + (long long)rl[rlpos - 1].length, + (long long)prev_run_len); + } else { + if (rlpos) { + ntfs_log_trace("Adding new run, (previous " + "run lcn 0x%llx, len 0x%llx).\n", + (long long) rl[rlpos - 1].lcn, + (long long) rl[rlpos - 1].length); + rl[rlpos].vcn = rl[rlpos - 1].vcn + + prev_run_len; + } else { + ntfs_log_trace("Adding new run, is first run.\n"); + rl[rlpos].vcn = start_vcn; + } + rl[rlpos].lcn = prev_lcn = lcn + bmp_pos; + rl[rlpos].length = prev_run_len = 1; + rlpos++; + } + /* Done? */ + if (!--clusters) { + LCN tc; + /* + * Update the current zone position. Positions + * of already scanned zones have been updated + * during the respective zone switches. + */ + tc = lcn + bmp_pos + 1; + ntfs_log_trace("Done. Updating current zone " + "position, tc = 0x%llx, search_zone = %i.\n", + (long long)tc, search_zone); + switch (search_zone) { + case 1: + ntfs_log_trace("Before checks, vol->mft_zone_pos = 0x%llx.\n", + (long long) vol->mft_zone_pos); + if (tc >= vol->mft_zone_end) { + vol->mft_zone_pos = + vol->mft_lcn; + if (!vol->mft_zone_end) + vol->mft_zone_pos = 0; + } else if ((bmp_initial_pos >= + vol->mft_zone_pos || + tc > vol->mft_zone_pos) + && tc >= vol->mft_lcn) + vol->mft_zone_pos = tc; + ntfs_log_trace("After checks, vol->mft_zone_pos = 0x%llx.\n", + (long long) vol->mft_zone_pos); + break; + case 2: + ntfs_log_trace("Before checks, vol->data1_zone_pos = 0x%llx.\n", + (long long) vol->data1_zone_pos); + if (tc >= vol->nr_clusters) + vol->data1_zone_pos = + vol->mft_zone_end; + else if ((bmp_initial_pos >= + vol->data1_zone_pos || + tc > vol->data1_zone_pos) + && tc >= vol->mft_zone_end) + vol->data1_zone_pos = tc; + ntfs_log_trace("After checks, vol->data1_zone_pos = 0x%llx.\n", + (long long) vol->data1_zone_pos); + break; + case 4: + ntfs_log_trace("Before checks, vol->data2_zone_pos = 0x%llx.\n", + (long long) vol->data2_zone_pos); + if (tc >= vol->mft_zone_start) + vol->data2_zone_pos = 0; + else if (bmp_initial_pos >= + vol->data2_zone_pos || + tc > vol->data2_zone_pos) + vol->data2_zone_pos = tc; + ntfs_log_trace("After checks, vol->data2_zone_pos = 0x%llx.\n", + (long long) vol->data2_zone_pos); + break; + default: + free(rl); + free(buf); + NTFS_BUG("switch (search_zone) 1"); + return NULL; + } + ntfs_log_trace("Going to done_ret.\n"); + goto done_ret; + } + lcn++; + } + bmp_pos += buf_size; + ntfs_log_trace("After inner while loop: buf_size = 0x%x, lcn = " + "0x%llx, bmp_pos = 0x%llx, need_writeback = %i.\n", + buf_size, (long long)lcn, + (long long)bmp_pos, need_writeback); + if (need_writeback) { + s64 bw; + ntfs_log_trace("Writing back.\n"); + need_writeback = 0; + bw = ntfs_attr_pwrite(vol->lcnbmp_na, last_read_pos, + br, buf); + if (bw != br) { + if (bw == -1) + err = errno; + else + err = EIO; + ntfs_log_trace("Bitmap writeback failed in read next " + "buffer code path with error code %i.\n", err); + goto err_ret; + } + } + if (bmp_pos < zone_end) { + ntfs_log_trace("Continuing outer while loop, bmp_pos = " + "0x%llx, zone_end = 0x%llx.\n", + (long long)bmp_pos, + (long long)zone_end); + continue; + } +zone_pass_done: /* Finished with the current zone pass. */ + ntfs_log_trace("At zone_pass_done, pass = %i.\n", pass); + if (pass == 1) { + /* + * Now do pass 2, scanning the first part of the zone + * we omitted in pass 1. + */ + pass = 2; + zone_end = zone_start; + switch (search_zone) { + case 1: /* mft_zone */ + zone_start = vol->mft_zone_start; + break; + case 2: /* data1_zone */ + zone_start = vol->mft_zone_end; + break; + case 4: /* data2_zone */ + zone_start = 0; + break; + default: + NTFS_BUG("switch (search_zone) 2"); + } + /* Sanity check. */ + if (zone_end < zone_start) + zone_end = zone_start; + bmp_pos = zone_start; + ntfs_log_trace("Continuing outer while loop, pass = 2, " + "zone_start = 0x%llx, zone_end = " + "0x%llx, bmp_pos = 0x%llx.\n", + zone_start, zone_end, bmp_pos); + continue; + } /* pass == 2 */ +done_zones_check: + ntfs_log_trace("At done_zones_check, search_zone = %i, done_zones " + "before = 0x%x, done_zones after = 0x%x.\n", + search_zone, done_zones, done_zones | search_zone); + done_zones |= search_zone; + if (done_zones < 7) { + ntfs_log_trace("Switching zone.\n"); + /* Now switch to the next zone we haven't done yet. */ + pass = 1; + switch (search_zone) { + case 1: + ntfs_log_trace("Switching from mft zone to data1 " + "zone.\n"); + /* Update mft zone position. */ + if (rlpos) { + LCN tc; + ntfs_log_trace("Before checks, vol->mft_zone_pos = 0x%llx.\n", + (long long) vol->mft_zone_pos); + tc = rl[rlpos - 1].lcn + + rl[rlpos - 1].length; + if (tc >= vol->mft_zone_end) { + vol->mft_zone_pos = + vol->mft_lcn; + if (!vol->mft_zone_end) + vol->mft_zone_pos = 0; + } else if ((bmp_initial_pos >= + vol->mft_zone_pos || + tc > vol->mft_zone_pos) + && tc >= vol->mft_lcn) + vol->mft_zone_pos = tc; + ntfs_log_trace("After checks, vol->mft_zone_pos = 0x%llx.\n", + (long long) vol->mft_zone_pos); + } + /* Switch from mft zone to data1 zone. */ +switch_to_data1_zone: search_zone = 2; + zone_start = bmp_initial_pos = + vol->data1_zone_pos; + zone_end = vol->nr_clusters; + if (zone_start == vol->mft_zone_end) + pass = 2; + if (zone_start >= zone_end) { + vol->data1_zone_pos = zone_start = + vol->mft_zone_end; + pass = 2; + } + break; + case 2: + ntfs_log_trace("Switching from data1 zone to data2 " + "zone.\n"); + /* Update data1 zone position. */ + if (rlpos) { + LCN tc; + ntfs_log_trace("Before checks, vol->data1_zone_pos = 0x%llx.\n", + (long long) vol->data1_zone_pos); + tc = rl[rlpos - 1].lcn + + rl[rlpos - 1].length; + if (tc >= vol->nr_clusters) + vol->data1_zone_pos = + vol->mft_zone_end; + else if ((bmp_initial_pos >= + vol->data1_zone_pos || + tc > vol->data1_zone_pos) + && tc >= vol->mft_zone_end) + vol->data1_zone_pos = tc; + ntfs_log_trace("After checks, vol->data1_zone_pos = 0x%llx.\n", + (long long) vol->data1_zone_pos); + } + /* Switch from data1 zone to data2 zone. */ + search_zone = 4; + zone_start = bmp_initial_pos = + vol->data2_zone_pos; + zone_end = vol->mft_zone_start; + if (!zone_start) + pass = 2; + if (zone_start >= zone_end) { + vol->data2_zone_pos = zone_start = + bmp_initial_pos = 0; + pass = 2; + } + break; + case 4: + ntfs_log_debug("Switching from data2 zone to data1 " + "zone.\n"); + /* Update data2 zone position. */ + if (rlpos) { + LCN tc; + ntfs_log_trace("Before checks, vol->data2_zone_pos = 0x%llx.\n", + (long long) vol->data2_zone_pos); + tc = rl[rlpos - 1].lcn + + rl[rlpos - 1].length; + if (tc >= vol->mft_zone_start) + vol->data2_zone_pos = 0; + else if (bmp_initial_pos >= + vol->data2_zone_pos || + tc > vol->data2_zone_pos) + vol->data2_zone_pos = tc; + ntfs_log_trace("After checks, vol->data2_zone_pos = 0x%llx.\n", + (long long) vol->data2_zone_pos); + } + /* Switch from data2 zone to data1 zone. */ + goto switch_to_data1_zone; /* See above. */ + default: + NTFS_BUG("switch (search_zone) 3"); + } + ntfs_log_trace("After zone switch, search_zone = %i, pass = " + "%i, bmp_initial_pos = 0x%llx, " + "zone_start = 0x%llx, zone_end = " + "0x%llx.\n", search_zone, pass, + (long long)bmp_initial_pos, + (long long)zone_start, + (long long)zone_end); + bmp_pos = zone_start; + if (zone_start == zone_end) { + ntfs_log_trace("Empty zone, going to " + "done_zones_check.\n"); + /* Empty zone. Don't bother searching it. */ + goto done_zones_check; + } + ntfs_log_trace("Continuing outer while loop.\n"); + continue; + } /* done_zones == 7 */ + ntfs_log_trace("All zones are finished.\n"); + /* + * All zones are finished! If DATA_ZONE, shrink mft zone. If + * MFT_ZONE, we have really run out of space. + */ + mft_zone_size = vol->mft_zone_end - vol->mft_zone_start; + ntfs_log_trace("vol->mft_zone_start = 0x%llx, vol->mft_zone_end = " + "0x%llx, mft_zone_size = 0x%llx.\n", + (long long)vol->mft_zone_start, + (long long)vol->mft_zone_end, + (long long)mft_zone_size); + if (zone == MFT_ZONE || mft_zone_size <= 0) { + ntfs_log_trace("No free clusters left, going to err_ret.\n"); + /* Really no more space left on device. */ + err = ENOSPC; + goto err_ret; + } /* zone == DATA_ZONE && mft_zone_size > 0 */ + ntfs_log_trace("Shrinking mft zone.\n"); + zone_end = vol->mft_zone_end; + mft_zone_size >>= 1; + if (mft_zone_size > 0) + vol->mft_zone_end = vol->mft_zone_start + mft_zone_size; + else /* mft zone and data2 zone no longer exist. */ + vol->data2_zone_pos = vol->mft_zone_start = + vol->mft_zone_end = 0; + if (vol->mft_zone_pos >= vol->mft_zone_end) { + vol->mft_zone_pos = vol->mft_lcn; + if (!vol->mft_zone_end) + vol->mft_zone_pos = 0; + } + bmp_pos = zone_start = bmp_initial_pos = + vol->data1_zone_pos = vol->mft_zone_end; + search_zone = 2; + pass = 2; + done_zones &= ~2; + ntfs_log_trace("After shrinking mft zone, mft_zone_size = 0x%llx, " + "vol->mft_zone_start = 0x%llx, " + "vol->mft_zone_end = 0x%llx, vol->mft_zone_pos " + "= 0x%llx, search_zone = 2, pass = 2, " + "dones_zones = 0x%x, zone_start = 0x%llx, " + "zone_end = 0x%llx, vol->data1_zone_pos = " + "0x%llx, continuing outer while loop.\n", + (long long)mft_zone_size, + (long long)vol->mft_zone_start, + (long long)vol->mft_zone_end, + (long long)vol->mft_zone_pos, + done_zones, + (long long)zone_start, + (long long)zone_end, + (long long)vol->data1_zone_pos); + } + ntfs_log_debug("After outer while loop.\n"); +done_ret: + ntfs_log_debug("At done_ret.\n"); + /* Add runlist terminator element. */ + rl[rlpos].vcn = rl[rlpos - 1].vcn + rl[rlpos - 1].length; + rl[rlpos].lcn = LCN_RL_NOT_MAPPED; + rl[rlpos].length = 0; + if (need_writeback) { + s64 bw; + ntfs_log_trace("Writing back.\n"); + need_writeback = 0; + bw = ntfs_attr_pwrite(vol->lcnbmp_na, last_read_pos, br, buf); + if (bw != br) { + if (bw < 0) + err = errno; + else + err = EIO; + ntfs_log_trace("Bitmap writeback failed in done code path " + "with error code %i.\n", err); + goto err_ret; + } + } +done_err_ret: + ntfs_log_debug("At done_err_ret (follows done_ret).\n"); + free(buf); + /* Done! */ + if (!err) + return rl; + ntfs_log_trace("Failed to allocate clusters. Returning with error code " + "%i.\n", err); + errno = err; + return NULL; +wb_err_ret: + ntfs_log_trace("At wb_err_ret.\n"); + if (need_writeback) { + s64 bw; + ntfs_log_trace("Writing back.\n"); + need_writeback = 0; + bw = ntfs_attr_pwrite(vol->lcnbmp_na, last_read_pos, br, buf); + if (bw != br) { + if (bw < 0) + err = errno; + else + err = EIO; + ntfs_log_trace("Bitmap writeback failed in error code path " + "with error code %i.\n", err); + } + } +err_ret: + ntfs_log_trace("At err_ret.\n"); + if (rl) { + if (err == ENOSPC) { + ntfs_log_trace("err = ENOSPC, first free lcn = 0x%llx, could " + "allocate up to = 0x%llx clusters.\n", + (long long)rl[0].lcn, + (long long)count - clusters); + } + /* Add runlist terminator element. */ + rl[rlpos].vcn = rl[rlpos - 1].vcn + rl[rlpos - 1].length; + rl[rlpos].lcn = LCN_RL_NOT_MAPPED; + rl[rlpos].length = 0; + /* Deallocate all allocated clusters. */ + ntfs_log_trace("Deallocating allocated clusters.\n"); + ntfs_cluster_free_from_rl(vol, rl); + /* Free the runlist. */ + free(rl); + rl = NULL; + } else { + if (err == ENOSPC) { + ntfs_log_trace("No space left at all, err = ENOSPC, first " + "free lcn = 0x%llx.\n", + (long long)vol->data1_zone_pos); + } + } + ntfs_log_trace("rl = NULL, going to done_err_ret.\n"); + goto done_err_ret; +} + +/** + * ntfs_cluster_free_from_rl - free clusters from runlist + * @vol: mounted ntfs volume on which to free the clusters + * @rl: runlist from which deallocate clusters + * + * On success return 0 and on error return -1 with errno set to the error code. + */ +int ntfs_cluster_free_from_rl(ntfs_volume *vol, runlist *rl) +{ + ntfs_log_trace("Entering.\n"); + + for (; rl->length; rl++) { + + ntfs_log_trace("Dealloc lcn 0x%llx, len 0x%llx.\n", + (long long)rl->lcn, (long long)rl->length); + + if (rl->lcn >= 0 && ntfs_bitmap_clear_run(vol->lcnbmp_na, + rl->lcn, rl->length)) { + int eo = errno; + ntfs_log_trace("Eeek! Deallocation of clusters failed.\n"); + errno = eo; + return -1; + } + } + return 0; +} + +/** + * ntfs_cluster_free - free clusters on an ntfs volume + * @vol: mounted ntfs volume on which to free the clusters + * @na: attribute whose runlist describes the clusters to free + * @start_vcn: vcn in @rl at which to start freeing clusters + * @count: number of clusters to free or -1 for all clusters + * + * Free @count clusters starting at the cluster @start_vcn in the runlist + * described by the attribute @na from the mounted ntfs volume @vol. + * + * If @count is -1, all clusters from @start_vcn to the end of the runlist + * are deallocated. + * + * On success return the number of deallocated clusters (not counting sparse + * clusters) and on error return -1 with errno set to the error code. + */ +int ntfs_cluster_free(ntfs_volume *vol, ntfs_attr *na, VCN start_vcn, s64 count) +{ + runlist *rl; + s64 nr_freed, delta, to_free; + + if (!vol || !vol->lcnbmp_na || !na || start_vcn < 0 || + (count < 0 && count != -1)) { + ntfs_log_trace("Invalid arguments!\n"); + errno = EINVAL; + return -1; + } + ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x, count 0x%llx, " + "vcn 0x%llx.\n", (unsigned long long)na->ni->mft_no, + na->type, (long long)count, (long long)start_vcn); + + rl = ntfs_attr_find_vcn(na, start_vcn); + if (!rl) { + if (errno == ENOENT) + return 0; + else + return -1; + } + + if (rl->lcn < 0 && rl->lcn != LCN_HOLE) { + errno = EIO; + return -1; + } + + /* Find the starting cluster inside the run that needs freeing. */ + delta = start_vcn - rl->vcn; + + /* The number of clusters in this run that need freeing. */ + to_free = rl->length - delta; + if (count >= 0 && to_free > count) + to_free = count; + + if (rl->lcn != LCN_HOLE) { + /* Do the actual freeing of the clusters in this run. */ + if (ntfs_bitmap_clear_run(vol->lcnbmp_na, rl->lcn + delta, + to_free)) + return -1; + /* We have freed @to_free real clusters. */ + nr_freed = to_free; + } else { + /* No real clusters were freed. */ + nr_freed = 0; + } + + /* Go to the next run and adjust the number of clusters left to free. */ + ++rl; + if (count >= 0) + count -= to_free; + + /* + * Loop over the remaining runs, using @count as a capping value, and + * free them. + */ + for (; rl->length && count != 0; ++rl) { + // FIXME: Need to try ntfs_attr_map_runlist() for attribute + // list support! (AIA) + if (rl->lcn < 0 && rl->lcn != LCN_HOLE) { + // FIXME: Eeek! We need rollback! (AIA) + ntfs_log_trace("Eeek! invalid lcn (= %lli). Should attempt " + "to map runlist! Leaving inconsistent " + "metadata!\n", (long long)rl->lcn); + errno = EIO; + return -1; + } + + /* The number of clusters in this run that need freeing. */ + to_free = rl->length; + if (count >= 0 && to_free > count) + to_free = count; + + if (rl->lcn != LCN_HOLE) { + /* Do the actual freeing of the clusters in the run. */ + if (ntfs_bitmap_clear_run(vol->lcnbmp_na, rl->lcn, + to_free)) { + int eo = errno; + + // FIXME: Eeek! We need rollback! (AIA) + ntfs_log_trace("Eeek! bitmap clear run failed. " + "Leaving inconsistent metadata!\n"); + errno = eo; + return -1; + } + /* We have freed @to_free real clusters. */ + nr_freed += to_free; + } + + if (count >= 0) + count -= to_free; + } + + if (count != -1 && count != 0) { + // FIXME: Eeek! BUG() + ntfs_log_trace("Eeek! count still not zero (= %lli). Leaving " + "inconsistent metadata!\n", (long long)count); + errno = EIO; + return -1; + } + + /* Done. Return the number of actual clusters that were freed. */ + return nr_freed; +} diff --git a/libntfs-3g/logfile.c b/libntfs-3g/logfile.c new file mode 100644 index 00000000..c41a762b --- /dev/null +++ b/libntfs-3g/logfile.c @@ -0,0 +1,762 @@ +/** + * logfile.c - NTFS journal handling. Originated from the Linux-NTFS project. + * + * Copyright (c) 2002-2005 Anton Altaparmakov + * Copyright (c) 2005 Yura Pakhuchiy + * Copyright (c) 2005-2006 Szabolcs Szakacsits + * + * 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 + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program/include file 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 + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif +#ifdef HAVE_ERRNO_H +#include +#endif + +#include "attrib.h" +#include "debug.h" +#include "logfile.h" +#include "volume.h" +#include "mst.h" +#include "logging.h" +#include "misc.h" + +/** + * ntfs_check_restart_page_header - check the page header for consistency + * @rp: restart page header to check + * @pos: position in logfile at which the restart page header resides + * + * Check the restart page header @rp for consistency and return TRUE if it is + * consistent and FALSE otherwise. + * + * This function only needs NTFS_BLOCK_SIZE bytes in @rp, i.e. it does not + * require the full restart page. + */ +static BOOL ntfs_check_restart_page_header(RESTART_PAGE_HEADER *rp, s64 pos) +{ + u32 logfile_system_page_size, logfile_log_page_size; + u16 ra_ofs, usa_count, usa_ofs, usa_end = 0; + BOOL have_usa = TRUE; + + ntfs_log_trace("Entering.\n"); + /* + * If the system or log page sizes are smaller than the ntfs block size + * or either is not a power of 2 we cannot handle this log file. + */ + logfile_system_page_size = le32_to_cpu(rp->system_page_size); + logfile_log_page_size = le32_to_cpu(rp->log_page_size); + if (logfile_system_page_size < NTFS_BLOCK_SIZE || + logfile_log_page_size < NTFS_BLOCK_SIZE || + logfile_system_page_size & + (logfile_system_page_size - 1) || + logfile_log_page_size & (logfile_log_page_size - 1)) { + ntfs_log_error("$LogFile uses unsupported page size.\n"); + return FALSE; + } + /* + * We must be either at !pos (1st restart page) or at pos = system page + * size (2nd restart page). + */ + if (pos && pos != logfile_system_page_size) { + ntfs_log_error("Found restart area in incorrect " + "position in $LogFile.\n"); + return FALSE; + } + /* We only know how to handle version 1.1. */ + if (sle16_to_cpu(rp->major_ver) != 1 || + sle16_to_cpu(rp->minor_ver) != 1) { + ntfs_log_error("$LogFile version %i.%i is not " + "supported. (This driver supports version " + "1.1 only.)\n", (int)sle16_to_cpu(rp->major_ver), + (int)sle16_to_cpu(rp->minor_ver)); + return FALSE; + } + /* + * If chkdsk has been run the restart page may not be protected by an + * update sequence array. + */ + if (ntfs_is_chkd_record(rp->magic) && !le16_to_cpu(rp->usa_count)) { + have_usa = FALSE; + goto skip_usa_checks; + } + /* Verify the size of the update sequence array. */ + usa_count = 1 + (logfile_system_page_size >> NTFS_BLOCK_SIZE_BITS); + if (usa_count != le16_to_cpu(rp->usa_count)) { + ntfs_log_error("$LogFile restart page specifies " + "inconsistent update sequence array count.\n"); + return FALSE; + } + /* Verify the position of the update sequence array. */ + usa_ofs = le16_to_cpu(rp->usa_ofs); + usa_end = usa_ofs + usa_count * sizeof(u16); + if (usa_ofs < sizeof(RESTART_PAGE_HEADER) || + usa_end > NTFS_BLOCK_SIZE - sizeof(u16)) { + ntfs_log_error("$LogFile restart page specifies " + "inconsistent update sequence array offset.\n"); + return FALSE; + } +skip_usa_checks: + /* + * Verify the position of the restart area. It must be: + * - aligned to 8-byte boundary, + * - after the update sequence array, and + * - within the system page size. + */ + ra_ofs = le16_to_cpu(rp->restart_area_offset); + if (ra_ofs & 7 || (have_usa ? ra_ofs < usa_end : + ra_ofs < sizeof(RESTART_PAGE_HEADER)) || + ra_ofs > logfile_system_page_size) { + ntfs_log_error("$LogFile restart page specifies " + "inconsistent restart area offset.\n"); + return FALSE; + } + /* + * Only restart pages modified by chkdsk are allowed to have chkdsk_lsn + * set. + */ + if (!ntfs_is_chkd_record(rp->magic) && sle64_to_cpu(rp->chkdsk_lsn)) { + ntfs_log_error("$LogFile restart page is not modified " + "by chkdsk but a chkdsk LSN is specified.\n"); + return FALSE; + } + ntfs_log_trace("Done.\n"); + return TRUE; +} + +/** + * ntfs_check_restart_area - check the restart area for consistency + * @rp: restart page whose restart area to check + * + * Check the restart area of the restart page @rp for consistency and return + * TRUE if it is consistent and FALSE otherwise. + * + * This function assumes that the restart page header has already been + * consistency checked. + * + * This function only needs NTFS_BLOCK_SIZE bytes in @rp, i.e. it does not + * require the full restart page. + */ +static BOOL ntfs_check_restart_area(RESTART_PAGE_HEADER *rp) +{ + u64 file_size; + RESTART_AREA *ra; + u16 ra_ofs, ra_len, ca_ofs; + u8 fs_bits; + + ntfs_log_trace("Entering.\n"); + ra_ofs = le16_to_cpu(rp->restart_area_offset); + ra = (RESTART_AREA*)((u8*)rp + ra_ofs); + /* + * Everything before ra->file_size must be before the first word + * protected by an update sequence number. This ensures that it is + * safe to access ra->client_array_offset. + */ + if (ra_ofs + offsetof(RESTART_AREA, file_size) > + NTFS_BLOCK_SIZE - sizeof(u16)) { + ntfs_log_error("$LogFile restart area specifies " + "inconsistent file offset.\n"); + return FALSE; + } + /* + * Now that we can access ra->client_array_offset, make sure everything + * up to the log client array is before the first word protected by an + * update sequence number. This ensures we can access all of the + * restart area elements safely. Also, the client array offset must be + * aligned to an 8-byte boundary. + */ + ca_ofs = le16_to_cpu(ra->client_array_offset); + if (((ca_ofs + 7) & ~7) != ca_ofs || + ra_ofs + ca_ofs > (u16)(NTFS_BLOCK_SIZE - + sizeof(u16))) { + ntfs_log_error("$LogFile restart area specifies " + "inconsistent client array offset.\n"); + return FALSE; + } + /* + * The restart area must end within the system page size both when + * calculated manually and as specified by ra->restart_area_length. + * Also, the calculated length must not exceed the specified length. + */ + ra_len = ca_ofs + le16_to_cpu(ra->log_clients) * + sizeof(LOG_CLIENT_RECORD); + if ((u32)(ra_ofs + ra_len) > le32_to_cpu(rp->system_page_size) || + (u32)(ra_ofs + le16_to_cpu(ra->restart_area_length)) > + le32_to_cpu(rp->system_page_size) || + ra_len > le16_to_cpu(ra->restart_area_length)) { + ntfs_log_error("$LogFile restart area is out of bounds " + "of the system page size specified by the " + "restart page header and/or the specified " + "restart area length is inconsistent.\n"); + return FALSE; + } + /* + * The ra->client_free_list and ra->client_in_use_list must be either + * LOGFILE_NO_CLIENT or less than ra->log_clients or they are + * overflowing the client array. + */ + if ((ra->client_free_list != LOGFILE_NO_CLIENT && + le16_to_cpu(ra->client_free_list) >= + le16_to_cpu(ra->log_clients)) || + (ra->client_in_use_list != LOGFILE_NO_CLIENT && + le16_to_cpu(ra->client_in_use_list) >= + le16_to_cpu(ra->log_clients))) { + ntfs_log_error("$LogFile restart area specifies " + "overflowing client free and/or in use lists.\n"); + return FALSE; + } + /* + * Check ra->seq_number_bits against ra->file_size for consistency. + * We cannot just use ffs() because the file size is not a power of 2. + */ + file_size = (u64)sle64_to_cpu(ra->file_size); + fs_bits = 0; + while (file_size) { + file_size >>= 1; + fs_bits++; + } + if (le32_to_cpu(ra->seq_number_bits) != (u32)(67 - fs_bits)) { + ntfs_log_error("$LogFile restart area specifies " + "inconsistent sequence number bits.\n"); + return FALSE; + } + /* The log record header length must be a multiple of 8. */ + if (((le16_to_cpu(ra->log_record_header_length) + 7) & ~7) != + le16_to_cpu(ra->log_record_header_length)) { + ntfs_log_error("$LogFile restart area specifies " + "inconsistent log record header length.\n"); + return FALSE; + } + /* Ditto for the log page data offset. */ + if (((le16_to_cpu(ra->log_page_data_offset) + 7) & ~7) != + le16_to_cpu(ra->log_page_data_offset)) { + ntfs_log_error("$LogFile restart area specifies " + "inconsistent log page data offset.\n"); + return FALSE; + } + ntfs_log_trace("Done.\n"); + return TRUE; +} + +/** + * ntfs_check_log_client_array - check the log client array for consistency + * @rp: restart page whose log client array to check + * + * Check the log client array of the restart page @rp for consistency and + * return TRUE if it is consistent and FALSE otherwise. + * + * This function assumes that the restart page header and the restart area have + * already been consistency checked. + * + * Unlike ntfs_check_restart_page_header() and ntfs_check_restart_area(), this + * function needs @rp->system_page_size bytes in @rp, i.e. it requires the full + * restart page and the page must be multi sector transfer deprotected. + */ +static BOOL ntfs_check_log_client_array(RESTART_PAGE_HEADER *rp) +{ + RESTART_AREA *ra; + LOG_CLIENT_RECORD *ca, *cr; + u16 nr_clients, idx; + BOOL in_free_list, idx_is_first; + + ntfs_log_trace("Entering.\n"); + ra = (RESTART_AREA*)((u8*)rp + le16_to_cpu(rp->restart_area_offset)); + ca = (LOG_CLIENT_RECORD*)((u8*)ra + + le16_to_cpu(ra->client_array_offset)); + /* + * Check the ra->client_free_list first and then check the + * ra->client_in_use_list. Check each of the log client records in + * each of the lists and check that the array does not overflow the + * ra->log_clients value. Also keep track of the number of records + * visited as there cannot be more than ra->log_clients records and + * that way we detect eventual loops in within a list. + */ + nr_clients = le16_to_cpu(ra->log_clients); + idx = le16_to_cpu(ra->client_free_list); + in_free_list = TRUE; +check_list: + for (idx_is_first = TRUE; idx != LOGFILE_NO_CLIENT_CPU; nr_clients--, + idx = le16_to_cpu(cr->next_client)) { + if (!nr_clients || idx >= le16_to_cpu(ra->log_clients)) + goto err_out; + /* Set @cr to the current log client record. */ + cr = ca + idx; + /* The first log client record must not have a prev_client. */ + if (idx_is_first) { + if (cr->prev_client != LOGFILE_NO_CLIENT) + goto err_out; + idx_is_first = FALSE; + } + } + /* Switch to and check the in use list if we just did the free list. */ + if (in_free_list) { + in_free_list = FALSE; + idx = le16_to_cpu(ra->client_in_use_list); + goto check_list; + } + ntfs_log_trace("Done.\n"); + return TRUE; +err_out: + ntfs_log_error("$LogFile log client array is corrupt.\n"); + return FALSE; +} + +/** + * ntfs_check_and_load_restart_page - check the restart page for consistency + * @log_na: opened ntfs attribute for journal $LogFile + * @rp: restart page to check + * @pos: position in @log_na at which the restart page resides + * @wrp: [OUT] copy of the multi sector transfer deprotected restart page + * @lsn: [OUT] set to the current logfile lsn on success + * + * Check the restart page @rp for consistency and return 0 if it is consistent + * and errno otherwise. The restart page may have been modified by chkdsk in + * which case its magic is CHKD instead of RSTR. + * + * This function only needs NTFS_BLOCK_SIZE bytes in @rp, i.e. it does not + * require the full restart page. + * + * If @wrp is not NULL, on success, *@wrp will point to a buffer containing a + * copy of the complete multi sector transfer deprotected page. On failure, + * *@wrp is undefined. + * + * Similarly, if @lsn is not NULL, on success *@lsn will be set to the current + * logfile lsn according to this restart page. On failure, *@lsn is undefined. + * + * The following error codes are defined: + * EINVAL - The restart page is inconsistent. + * ENOMEM - Not enough memory to load the restart page. + * EIO - Failed to reading from $LogFile. + */ +static int ntfs_check_and_load_restart_page(ntfs_attr *log_na, + RESTART_PAGE_HEADER *rp, s64 pos, RESTART_PAGE_HEADER **wrp, + LSN *lsn) +{ + RESTART_AREA *ra; + RESTART_PAGE_HEADER *trp; + int err; + + ntfs_log_trace("Entering.\n"); + /* Check the restart page header for consistency. */ + if (!ntfs_check_restart_page_header(rp, pos)) { + /* Error output already done inside the function. */ + return EINVAL; + } + /* Check the restart area for consistency. */ + if (!ntfs_check_restart_area(rp)) { + /* Error output already done inside the function. */ + return EINVAL; + } + ra = (RESTART_AREA*)((u8*)rp + le16_to_cpu(rp->restart_area_offset)); + /* + * Allocate a buffer to store the whole restart page so we can multi + * sector transfer deprotect it. + */ + trp = ntfs_malloc(le32_to_cpu(rp->system_page_size)); + if (!trp) + return errno; + /* + * Read the whole of the restart page into the buffer. If it fits + * completely inside @rp, just copy it from there. Otherwise read it + * from disk. + */ + if (le32_to_cpu(rp->system_page_size) <= NTFS_BLOCK_SIZE) + memcpy(trp, rp, le32_to_cpu(rp->system_page_size)); + else if (ntfs_attr_pread(log_na, pos, + le32_to_cpu(rp->system_page_size), trp) != + le32_to_cpu(rp->system_page_size)) { + err = errno; + ntfs_log_error("Failed to read whole restart page into the " + "buffer.\n"); + if (err != ENOMEM) + err = EIO; + goto err_out; + } + /* + * Perform the multi sector transfer deprotection on the buffer if the + * restart page is protected. + */ + if ((!ntfs_is_chkd_record(trp->magic) || le16_to_cpu(trp->usa_count)) + && ntfs_mst_post_read_fixup((NTFS_RECORD*)trp, + le32_to_cpu(rp->system_page_size))) { + /* + * A multi sector tranfer error was detected. We only need to + * abort if the restart page contents exceed the multi sector + * transfer fixup of the first sector. + */ + if (le16_to_cpu(rp->restart_area_offset) + + le16_to_cpu(ra->restart_area_length) > + NTFS_BLOCK_SIZE - (int)sizeof(u16)) { + ntfs_log_error("Multi sector transfer error " + "detected in $LogFile restart page.\n"); + err = EINVAL; + goto err_out; + } + } + /* + * If the restart page is modified by chkdsk or there are no active + * logfile clients, the logfile is consistent. Otherwise, need to + * check the log client records for consistency, too. + */ + err = 0; + if (ntfs_is_rstr_record(rp->magic) && + ra->client_in_use_list != LOGFILE_NO_CLIENT) { + if (!ntfs_check_log_client_array(trp)) { + err = EINVAL; + goto err_out; + } + } + if (lsn) { + if (ntfs_is_rstr_record(rp->magic)) + *lsn = sle64_to_cpu(ra->current_lsn); + else /* if (ntfs_is_chkd_record(rp->magic)) */ + *lsn = sle64_to_cpu(rp->chkdsk_lsn); + } + ntfs_log_trace("Done.\n"); + if (wrp) + *wrp = trp; + else { +err_out: + free(trp); + } + return err; +} + +/** + * ntfs_check_logfile - check in the journal if the volume is consistent + * @log_na: ntfs attribute of loaded journal $LogFile to check + * @rp: [OUT] on success this is a copy of the current restart page + * + * Check the $LogFile journal for consistency and return TRUE if it is + * consistent and FALSE if not. On success, the current restart page is + * returned in *@rp. Caller must call ntfs_free(*@rp) when finished with it. + * + * At present we only check the two restart pages and ignore the log record + * pages. + * + * Note that the MstProtected flag is not set on the $LogFile inode and hence + * when reading pages they are not deprotected. This is because we do not know + * if the $LogFile was created on a system with a different page size to ours + * yet and mst deprotection would fail if our page size is smaller. + */ +BOOL ntfs_check_logfile(ntfs_attr *log_na, RESTART_PAGE_HEADER **rp) +{ + s64 size, pos; + LSN rstr1_lsn, rstr2_lsn; + ntfs_volume *vol = log_na->ni->vol; + u8 *kaddr = NULL; + RESTART_PAGE_HEADER *rstr1_ph = NULL; + RESTART_PAGE_HEADER *rstr2_ph = NULL; + int log_page_size, log_page_mask, err; + BOOL logfile_is_empty = TRUE; + u8 log_page_bits; + + ntfs_log_trace("Entering.\n"); + /* An empty $LogFile must have been clean before it got emptied. */ + if (NVolLogFileEmpty(vol)) + goto is_empty; + size = log_na->data_size; + /* Make sure the file doesn't exceed the maximum allowed size. */ + if (size > (s64)MaxLogFileSize) + size = MaxLogFileSize; + log_page_size = DefaultLogPageSize; + log_page_mask = log_page_size - 1; + /* + * Use generic_ffs() instead of ffs() to enable the compiler to + * optimize log_page_size and log_page_bits into constants. + */ + log_page_bits = ffs(log_page_size) - 1; + size &= ~(log_page_size - 1); + + /* + * Ensure the log file is big enough to store at least the two restart + * pages and the minimum number of log record pages. + */ + if (size < log_page_size * 2 || (size - log_page_size * 2) >> + log_page_bits < MinLogRecordPages) { + ntfs_log_error("$LogFile is too small.\n"); + return FALSE; + } + /* Allocate memory for restart page. */ + kaddr = ntfs_malloc(NTFS_BLOCK_SIZE); + if (!kaddr) + return FALSE; + /* + * Read through the file looking for a restart page. Since the restart + * page header is at the beginning of a page we only need to search at + * what could be the beginning of a page (for each page size) rather + * than scanning the whole file byte by byte. If all potential places + * contain empty and uninitialized records, the log file can be assumed + * to be empty. + */ + for (pos = 0; pos < size; pos <<= 1) { + /* + * Read first NTFS_BLOCK_SIZE bytes of potential restart page. + */ + if (ntfs_attr_pread(log_na, pos, NTFS_BLOCK_SIZE, kaddr) != + NTFS_BLOCK_SIZE) { + ntfs_log_error("Failed to read first NTFS_BLOCK_SIZE " + "bytes of potential restart page.\n"); + goto err_out; + } + + /* + * A non-empty block means the logfile is not empty while an + * empty block after a non-empty block has been encountered + * means we are done. + */ + if (!ntfs_is_empty_recordp((le32*)kaddr)) + logfile_is_empty = FALSE; + else if (!logfile_is_empty) + break; + /* + * A log record page means there cannot be a restart page after + * this so no need to continue searching. + */ + if (ntfs_is_rcrd_recordp((le32*)kaddr)) + break; + /* If not a (modified by chkdsk) restart page, continue. */ + if (!ntfs_is_rstr_recordp((le32*)kaddr) && + !ntfs_is_chkd_recordp((le32*)kaddr)) { + if (!pos) + pos = NTFS_BLOCK_SIZE >> 1; + continue; + } + /* + * Check the (modified by chkdsk) restart page for consistency + * and get a copy of the complete multi sector transfer + * deprotected restart page. + */ + err = ntfs_check_and_load_restart_page(log_na, + (RESTART_PAGE_HEADER*)kaddr, pos, + !rstr1_ph ? &rstr1_ph : &rstr2_ph, + !rstr1_ph ? &rstr1_lsn : &rstr2_lsn); + if (!err) { + /* + * If we have now found the first (modified by chkdsk) + * restart page, continue looking for the second one. + */ + if (!pos) { + pos = NTFS_BLOCK_SIZE >> 1; + continue; + } + /* + * We have now found the second (modified by chkdsk) + * restart page, so we can stop looking. + */ + break; + } + /* + * Error output already done inside the function. Note, we do + * not abort if the restart page was invalid as we might still + * find a valid one further in the file. + */ + if (err != EINVAL) + goto err_out; + /* Continue looking. */ + if (!pos) + pos = NTFS_BLOCK_SIZE >> 1; + } + if (kaddr) { + free(kaddr); + kaddr = NULL; + } + if (logfile_is_empty) { + NVolSetLogFileEmpty(vol); +is_empty: + ntfs_log_trace("Done. ($LogFile is empty.)\n"); + return TRUE; + } + if (!rstr1_ph) { + if (rstr2_ph) + ntfs_log_error("BUG: rstr2_ph isn't NULL!\n"); + ntfs_log_error("Did not find any restart pages in " + "$LogFile and it was not empty.\n"); + return FALSE; + } + /* If both restart pages were found, use the more recent one. */ + if (rstr2_ph) { + /* + * If the second restart area is more recent, switch to it. + * Otherwise just throw it away. + */ + if (rstr2_lsn > rstr1_lsn) { + ntfs_log_debug("Using second restart page as it is more " + "recent.\n"); + free(rstr1_ph); + rstr1_ph = rstr2_ph; + /* rstr1_lsn = rstr2_lsn; */ + } else { + ntfs_log_debug("Using first restart page as it is more " + "recent.\n"); + free(rstr2_ph); + } + rstr2_ph = NULL; + } + /* All consistency checks passed. */ + if (rp) + *rp = rstr1_ph; + else + free(rstr1_ph); + ntfs_log_trace("Done.\n"); + return TRUE; +err_out: + free(kaddr); + free(rstr1_ph); + free(rstr2_ph); + return FALSE; +} + +/** + * ntfs_is_logfile_clean - check in the journal if the volume is clean + * @log_na: ntfs attribute of loaded journal $LogFile to check + * @rp: copy of the current restart page + * + * Analyze the $LogFile journal and return TRUE if it indicates the volume was + * shutdown cleanly and FALSE if not. + * + * At present we only look at the two restart pages and ignore the log record + * pages. This is a little bit crude in that there will be a very small number + * of cases where we think that a volume is dirty when in fact it is clean. + * This should only affect volumes that have not been shutdown cleanly but did + * not have any pending, non-check-pointed i/o, i.e. they were completely idle + * at least for the five seconds preceding the unclean shutdown. + * + * This function assumes that the $LogFile journal has already been consistency + * checked by a call to ntfs_check_logfile() and in particular if the $LogFile + * is empty this function requires that NVolLogFileEmpty() is true otherwise an + * empty volume will be reported as dirty. + */ +BOOL ntfs_is_logfile_clean(ntfs_attr *log_na, RESTART_PAGE_HEADER *rp) +{ + RESTART_AREA *ra; + + ntfs_log_trace("Entering.\n"); + /* An empty $LogFile must have been clean before it got emptied. */ + if (NVolLogFileEmpty(log_na->ni->vol)) { + ntfs_log_trace("Done. ($LogFile is empty.)\n"); + return TRUE; + } + if (!rp) { + ntfs_log_error("Restart page header is NULL.\n"); + return FALSE; + } + if (!ntfs_is_rstr_record(rp->magic) && + !ntfs_is_chkd_record(rp->magic)) { + ntfs_log_error("Restart page buffer is invalid. This is " + "probably a bug in that the $LogFile should " + "have been consistency checked before calling " + "this function.\n"); + return FALSE; + } + + ra = (RESTART_AREA*)((u8*)rp + le16_to_cpu(rp->restart_area_offset)); + /* + * If the $LogFile has active clients, i.e. it is open, and we do not + * have the RESTART_VOLUME_IS_CLEAN bit set in the restart area flags, + * we assume there was an unclean shutdown. + */ + if (ra->client_in_use_list != LOGFILE_NO_CLIENT && + !(ra->flags & RESTART_VOLUME_IS_CLEAN)) { + ntfs_log_debug("Done. $LogFile indicates a dirty shutdown.\n"); + return FALSE; + } + /* $LogFile indicates a clean shutdown. */ + ntfs_log_trace("Done. $LogFile indicates a clean shutdown.\n"); + return TRUE; +} + +/** + * ntfs_empty_logfile - empty the contents of the $LogFile journal + * @na: ntfs attribute of journal $LogFile to empty + * + * Empty the contents of the $LogFile journal @na and return 0 on success and + * -1 on error. + * + * This function assumes that the $LogFile journal has already been consistency + * checked by a call to ntfs_check_logfile() and that ntfs_is_logfile_clean() + * has been used to ensure that the $LogFile is clean. + */ +int ntfs_empty_logfile(ntfs_attr *na) +{ + s64 len, pos, count; + char buf[NTFS_BUF_SIZE]; + + ntfs_log_trace("Entering.\n"); + if (NVolLogFileEmpty(na->ni->vol)) + return 0; + + /* The $DATA attribute of the $LogFile has to be non-resident. */ + if (!NAttrNonResident(na)) { + errno = EIO; + ntfs_log_perror("$LogFile $DATA attribute is resident!?!\n"); + return -1; + } + + /* Get length of $LogFile contents. */ + len = na->data_size; + if (!len) { + ntfs_log_debug("$LogFile has zero length, no disk write " + "needed.\n"); + return 0; + } + + /* Read $LogFile until its end. We do this as a check for correct + length thus making sure we are decompressing the mapping pairs + array correctly and hence writing below is safe as well. */ + pos = 0; + while ((count = ntfs_attr_pread(na, pos, NTFS_BUF_SIZE, buf)) > 0) + pos += count; + + if (count == -1 || pos != len) { + ntfs_log_error("Amount of $LogFile data read does not " + "correspond to expected length!\n"); + if (count != -1) + errno = EIO; + return -1; + } + + /* Fill the buffer with 0xff's. */ + memset(buf, -1, NTFS_BUF_SIZE); + + /* Set the $DATA attribute. */ + pos = 0; + while ((count = len - pos) > 0) { + if (count > NTFS_BUF_SIZE) + count = NTFS_BUF_SIZE; + + if ((count = ntfs_attr_pwrite(na, pos, count, buf)) <= 0) { + ntfs_log_perror("Failed to set the $LogFile attribute " + "value.\n"); + if (count != -1) + errno = EIO; + return -1; + } + pos += count; + } + + /* Set the flag so we do not have to do it again on remount. */ + NVolSetLogFileEmpty(na->ni->vol); + return 0; +} diff --git a/libntfs-3g/logging.c b/libntfs-3g/logging.c new file mode 100644 index 00000000..647fe3d4 --- /dev/null +++ b/libntfs-3g/logging.c @@ -0,0 +1,634 @@ +/** + * logging.c - Centralised logging. Originated from the Linux-NTFS project. + * + * Copyright (c) 2005 Richard Russon + * Copyright (c) 2005-2006 Szabolcs Szakacsits + * + * 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 + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program/include file 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 + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_STDIO_H +#include +#endif +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_STDARG_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_SYSLOG_H +#include +#endif + +#include "logging.h" +#include "misc.h" + +#ifndef PATH_SEP +#define PATH_SEP '/' +#endif + +/* Colour prefixes and a suffix */ +static const char *col_green = "\e[32m"; +static const char *col_cyan = "\e[36m"; +static const char *col_yellow = "\e[01;33m"; +static const char *col_red = "\e[01;31m"; +static const char *col_redinv = "\e[01;07;31m"; +static const char *col_end = "\e[0m"; + +/** + * struct ntfs_logging - Control info for the logging system + * @levels: Bitfield of logging levels + * @flags: Flags which affect the output style + * @handler: Function to perform the actual logging + */ +struct ntfs_logging { + u32 levels; + u32 flags; + ntfs_log_handler *handler; +}; + +/** + * ntfs_log + * This struct controls all the logging within the library and tools. + */ +static struct ntfs_logging ntfs_log = { +#ifdef DEBUG + NTFS_LOG_LEVEL_DEBUG | NTFS_LOG_LEVEL_TRACE | +#endif + NTFS_LOG_LEVEL_INFO | NTFS_LOG_LEVEL_QUIET | NTFS_LOG_LEVEL_WARNING | + NTFS_LOG_LEVEL_ERROR | NTFS_LOG_LEVEL_PERROR | NTFS_LOG_LEVEL_CRITICAL | + NTFS_LOG_LEVEL_PROGRESS, + NTFS_LOG_FLAG_ONLYNAME, +#ifdef DEBUG + ntfs_log_handler_outerr +#else + ntfs_log_handler_null +#endif +}; + + +/** + * ntfs_log_get_levels - Get a list of the current logging levels + * + * Find out which logging levels are enabled. + * + * Returns: Log levels in a 32-bit field + */ +u32 ntfs_log_get_levels(void) +{ + return ntfs_log.levels; +} + +/** + * ntfs_log_set_levels - Enable extra logging levels + * @levels: 32-bit field of log levels to set + * + * Enable one or more logging levels. + * The logging levels are named: NTFS_LOG_LEVEL_*. + * + * Returns: Log levels that were enabled before the call + */ +u32 ntfs_log_set_levels(u32 levels) +{ + u32 old; + old = ntfs_log.levels; + ntfs_log.levels |= levels; + return old; +} + +/** + * ntfs_log_clear_levels - Disable some logging levels + * @levels: 32-bit field of log levels to clear + * + * Disable one or more logging levels. + * The logging levels are named: NTFS_LOG_LEVEL_*. + * + * Returns: Log levels that were enabled before the call + */ +u32 ntfs_log_clear_levels(u32 levels) +{ + u32 old; + old = ntfs_log.levels; + ntfs_log.levels &= (~levels); + return old; +} + + +/** + * ntfs_log_get_flags - Get a list of logging style flags + * + * Find out which logging flags are enabled. + * + * Returns: Logging flags in a 32-bit field + */ +u32 ntfs_log_get_flags(void) +{ + return ntfs_log.flags; +} + +/** + * ntfs_log_set_flags - Enable extra logging style flags + * @flags: 32-bit field of logging flags to set + * + * Enable one or more logging flags. + * The log flags are named: NTFS_LOG_LEVEL_*. + * + * Returns: Logging flags that were enabled before the call + */ +u32 ntfs_log_set_flags(u32 flags) +{ + u32 old; + old = ntfs_log.flags; + ntfs_log.flags |= flags; + return old; +} + +/** + * ntfs_log_clear_flags - Disable some logging styles + * @flags: 32-bit field of logging flags to clear + * + * Disable one or more logging flags. + * The log flags are named: NTFS_LOG_LEVEL_*. + * + * Returns: Logging flags that were enabled before the call + */ +u32 ntfs_log_clear_flags(u32 flags) +{ + u32 old; + old = ntfs_log.flags; + ntfs_log.flags &= (~flags); + return old; +} + + +/** + * ntfs_log_get_stream - Default output streams for logging levels + * @level: Log level + * + * By default, urgent messages are sent to "stderr". + * Other messages are sent to "stdout". + * + * Returns: "string" Prefix to be used + */ +static FILE * ntfs_log_get_stream(u32 level) +{ + FILE *stream; + + switch (level) { + case NTFS_LOG_LEVEL_INFO: + case NTFS_LOG_LEVEL_QUIET: + case NTFS_LOG_LEVEL_PROGRESS: + case NTFS_LOG_LEVEL_VERBOSE: + stream = stdout; + break; + + case NTFS_LOG_LEVEL_DEBUG: + case NTFS_LOG_LEVEL_TRACE: + case NTFS_LOG_LEVEL_WARNING: + case NTFS_LOG_LEVEL_ERROR: + case NTFS_LOG_LEVEL_CRITICAL: + case NTFS_LOG_LEVEL_PERROR: + default: + stream = stderr; + break; + } + + return stream; +} + +/** + * ntfs_log_get_prefix - Default prefixes for logging levels + * @level: Log level to be prefixed + * + * Prefixing the logging output can make it easier to parse. + * + * Returns: "string" Prefix to be used + */ +static const char * ntfs_log_get_prefix(u32 level) +{ + const char *prefix; + + switch (level) { + case NTFS_LOG_LEVEL_DEBUG: + prefix = "DEBUG: "; + break; + case NTFS_LOG_LEVEL_TRACE: + prefix = "TRACE: "; + break; + case NTFS_LOG_LEVEL_QUIET: + prefix = "QUIET: "; + break; + case NTFS_LOG_LEVEL_INFO: + prefix = "INFO: "; + break; + case NTFS_LOG_LEVEL_VERBOSE: + prefix = "VERBOSE: "; + break; + case NTFS_LOG_LEVEL_PROGRESS: + prefix = "PROGRESS: "; + break; + case NTFS_LOG_LEVEL_WARNING: + prefix = "WARNING: "; + break; + case NTFS_LOG_LEVEL_ERROR: + prefix = "ERROR: "; + break; + case NTFS_LOG_LEVEL_PERROR: + prefix = "ERROR: "; + break; + case NTFS_LOG_LEVEL_CRITICAL: + prefix = "CRITICAL: "; + break; + default: + prefix = ""; + break; + } + + return prefix; +} + + +/** + * ntfs_log_set_handler - Provide an alternate logging handler + * @handler: function to perform the logging + * + * This alternate handler will be called for all future logging requests. + * If no @handler is specified, logging will revert to the default handler. + */ +void ntfs_log_set_handler(ntfs_log_handler *handler) +{ + if (handler) { + ntfs_log.handler = handler; +#ifdef HAVE_SYSLOG_H + if (handler == ntfs_log_handler_syslog) + openlog("libntfs", LOG_PID, LOG_USER); +#endif + } else + ntfs_log.handler = ntfs_log_handler_null; +} + +/** + * ntfs_log_redirect - Pass on the request to the real handler + * @function: Function in which the log line occurred + * @file: File in which the log line occurred + * @line: Line number on which the log line occurred + * @level: Level at which the line is logged + * @data: User specified data, possibly specific to a handler + * @format: printf-style formatting string + * @...: Arguments to be formatted + * + * This is just a redirector function. The arguments are simply passed to the + * main logging handler (as defined in the global logging struct @ntfs_log). + * + * Returns: -1 Error occurred + * 0 Message wasn't logged + * num Number of output characters + */ +int ntfs_log_redirect(const char *function, const char *file, + int line, u32 level, void *data, const char *format, ...) +{ + int olderr = errno; + int ret; + va_list args; + + if (!(ntfs_log.levels & level)) /* Don't log this message */ + return 0; + + va_start(args, format); + errno = olderr; + ret = ntfs_log.handler(function, file, line, level, data, format, args); + va_end(args); + + errno = olderr; + return ret; +} + + +/** + * ntfs_log_handler_syslog - syslog logging handler + * @function: Function in which the log line occurred + * @file: File in which the log line occurred + * @line: Line number on which the log line occurred + * @level: Level at which the line is logged + * @data: User specified data, possibly specific to a handler + * @format: printf-style formatting string + * @args: Arguments to be formatted + * + * A simple syslog logging handler. Ignores colors. + * + * Returns: -1 Error occurred + * 0 Message wasn't logged + * num Number of output characters + */ + +#ifdef HAVE_SYSLOG_H +int ntfs_log_handler_syslog(const char *function __attribute__((unused)), + const char *file, __attribute__((unused)) int line, u32 level, + void *data __attribute__((unused)), const char *format, va_list args) +{ + int ret = 0; + int olderr = errno; + + if ((ntfs_log.flags & NTFS_LOG_FLAG_ONLYNAME) && + (strchr(file, PATH_SEP))) /* Abbreviate the filename */ + file = strrchr(file, PATH_SEP) + 1; +#if 0 /* FIXME: Implement this all. */ + if (ntfs_log.flags & NTFS_LOG_FLAG_PREFIX) /* Prefix the output */ + ret += fprintf(stream, "%s", ntfs_log_get_prefix(level)); + + if (ntfs_log.flags & NTFS_LOG_FLAG_FILENAME) /* Source filename */ + ret += fprintf(stream, "%s ", file); + + if (ntfs_log.flags & NTFS_LOG_FLAG_LINE) /* Source line number */ + ret += fprintf(stream, "(%d) ", line); + + if ((ntfs_log.flags & NTFS_LOG_FLAG_FUNCTION) || /* Source function */ + (level & NTFS_LOG_LEVEL_TRACE)) + ret += fprintf(stream, "%s(): ", function); + + ret += vfprintf(stream, format, args); + + if (level & NTFS_LOG_LEVEL_PERROR) { + if (reason) + ret += fprintf(stream, ": %s\n", reason); + else + ret += fprintf(stream, ": %s\n", strerror(olderr)); + } +#endif + vsyslog(LOG_NOTICE, format, args); + ret = 1; /* FIXME: caclulate how many bytes had been written. */ + + errno = olderr; + return ret; +} +#endif + +/** + * ntfs_log_handler_fprintf - Basic logging handler + * @function: Function in which the log line occurred + * @file: File in which the log line occurred + * @line: Line number on which the log line occurred + * @level: Level at which the line is logged + * @data: User specified data, possibly specific to a handler + * @format: printf-style formatting string + * @args: Arguments to be formatted + * + * A simple logging handler. This is where the log line is finally displayed. + * It is more likely that you will want to set the handler to either + * ntfs_log_handler_outerr or ntfs_log_handler_stderr. + * + * Note: For this handler, @data is a pointer to a FILE output stream. + * If @data is NULL, nothing will be displayed. + * + * Returns: -1 Error occurred + * 0 Message wasn't logged + * num Number of output characters + */ +int ntfs_log_handler_fprintf(const char *function, const char *file, + int line, u32 level, void *data, const char *format, va_list args) +{ + int ret = 0; + int olderr = errno; + FILE *stream; + const char *col_prefix = NULL; + const char *col_suffix = NULL; + + if (!data) /* Interpret data as a FILE stream. */ + return 0; /* If it's NULL, we can't do anything. */ + stream = (FILE*)data; + + if (ntfs_log.flags & NTFS_LOG_FLAG_COLOUR) { + /* Pick a colour determined by the log level */ + switch (level) { + case NTFS_LOG_LEVEL_DEBUG: + col_prefix = col_green; + col_suffix = col_end; + break; + case NTFS_LOG_LEVEL_TRACE: + col_prefix = col_cyan; + col_suffix = col_end; + break; + case NTFS_LOG_LEVEL_WARNING: + col_prefix = col_yellow; + col_suffix = col_end; + break; + case NTFS_LOG_LEVEL_ERROR: + case NTFS_LOG_LEVEL_PERROR: + col_prefix = col_red; + col_suffix = col_end; + break; + case NTFS_LOG_LEVEL_CRITICAL: + col_prefix = col_redinv; + col_suffix = col_end; + break; + } + } + + if (col_prefix) + ret += fprintf(stream, col_prefix); + + if ((ntfs_log.flags & NTFS_LOG_FLAG_ONLYNAME) && + (strchr(file, PATH_SEP))) /* Abbreviate the filename */ + file = strrchr(file, PATH_SEP) + 1; + + if (ntfs_log.flags & NTFS_LOG_FLAG_PREFIX) /* Prefix the output */ + ret += fprintf(stream, "%s", ntfs_log_get_prefix(level)); + + if (ntfs_log.flags & NTFS_LOG_FLAG_FILENAME) /* Source filename */ + ret += fprintf(stream, "%s ", file); + + if (ntfs_log.flags & NTFS_LOG_FLAG_LINE) /* Source line number */ + ret += fprintf(stream, "(%d) ", line); + + if ((ntfs_log.flags & NTFS_LOG_FLAG_FUNCTION) || /* Source function */ + (level & NTFS_LOG_LEVEL_TRACE)) + ret += fprintf(stream, "%s(): ", function); + + ret += vfprintf(stream, format, args); + + if (level & NTFS_LOG_LEVEL_PERROR) + ret += fprintf(stream, ": %s\n", strerror(olderr)); + + if (col_suffix) + ret += fprintf(stream, col_suffix); + + + fflush(stream); + errno = olderr; + return ret; +} + +/** + * ntfs_log_handler_null - Null logging handler (no output) + * @function: Function in which the log line occurred + * @file: File in which the log line occurred + * @line: Line number on which the log line occurred + * @level: Level at which the line is logged + * @data: User specified data, possibly specific to a handler + * @format: printf-style formatting string + * @args: Arguments to be formatted + * + * This handler produces no output. It provides a way to temporarily disable + * logging, without having to change the levels and flags. + * + * Returns: 0 Message wasn't logged + */ +int ntfs_log_handler_null(const char *function __attribute__((unused)), const char *file __attribute__((unused)), + int line __attribute__((unused)), u32 level __attribute__((unused)), void *data __attribute__((unused)), + const char *format __attribute__((unused)), va_list args __attribute__((unused))) +{ + return 0; +} + +/** + * ntfs_log_handler_stdout - All logs go to stdout + * @function: Function in which the log line occurred + * @file: File in which the log line occurred + * @line: Line number on which the log line occurred + * @level: Level at which the line is logged + * @data: User specified data, possibly specific to a handler + * @format: printf-style formatting string + * @args: Arguments to be formatted + * + * Display a log message to stdout. + * + * Note: For this handler, @data is a pointer to a FILE output stream. + * If @data is NULL, then stdout will be used. + * + * Note: This function calls ntfs_log_handler_fprintf to do the main work. + * + * Returns: -1 Error occurred + * 0 Message wasn't logged + * num Number of output characters + */ +int ntfs_log_handler_stdout(const char *function, const char *file, + int line, u32 level, void *data, const char *format, va_list args) +{ + if (!data) + data = stdout; + + return ntfs_log_handler_fprintf(function, file, line, level, data, format, args); +} + +/** + * ntfs_log_handler_outerr - Logs go to stdout/stderr depending on level + * @function: Function in which the log line occurred + * @file: File in which the log line occurred + * @line: Line number on which the log line occurred + * @level: Level at which the line is logged + * @data: User specified data, possibly specific to a handler + * @format: printf-style formatting string + * @args: Arguments to be formatted + * + * Display a log message. The output stream will be determined by the log + * level. + * + * Note: For this handler, @data is a pointer to a FILE output stream. + * If @data is NULL, the function ntfs_log_get_stream will be called + * + * Note: This function calls ntfs_log_handler_fprintf to do the main work. + * + * Returns: -1 Error occurred + * 0 Message wasn't logged + * num Number of output characters + */ +int ntfs_log_handler_outerr(const char *function, const char *file, + int line, u32 level, void *data, const char *format, va_list args) +{ + if (!data) + data = ntfs_log_get_stream(level); + + return ntfs_log_handler_fprintf(function, file, line, level, data, format, args); +} + +/** + * ntfs_log_handler_stderr - All logs go to stderr + * @function: Function in which the log line occurred + * @file: File in which the log line occurred + * @line: Line number on which the log line occurred + * @level: Level at which the line is logged + * @data: User specified data, possibly specific to a handler + * @format: printf-style formatting string + * @args: Arguments to be formatted + * + * Display a log message to stderr. + * + * Note: For this handler, @data is a pointer to a FILE output stream. + * If @data is NULL, then stdout will be used. + * + * Note: This function calls ntfs_log_handler_fprintf to do the main work. + * + * Returns: -1 Error occurred + * 0 Message wasn't logged + * num Number of output characters + */ +int ntfs_log_handler_stderr(const char *function, const char *file, + int line, u32 level, void *data, const char *format, va_list args) +{ + if (!data) + data = stderr; + + return ntfs_log_handler_fprintf(function, file, line, level, data, format, args); +} + + +/** + * ntfs_log_parse_option - Act upon command line options + * @option: Option flag + * + * Delegate some of the work of parsing the command line. All the options begin + * with "--log-". Options cause log levels to be enabled in @ntfs_log (the + * global logging structure). + * + * Note: The "colour" option changes the logging handler. + * + * Returns: TRUE Option understood + * FALSE Invalid log option + */ +BOOL ntfs_log_parse_option(const char *option) +{ + if (strcmp(option, "--log-debug") == 0) { + ntfs_log_set_levels(NTFS_LOG_LEVEL_DEBUG); + return TRUE; + } else if (strcmp(option, "--log-verbose") == 0) { + ntfs_log_set_levels(NTFS_LOG_LEVEL_VERBOSE); + return TRUE; + } else if (strcmp(option, "--log-quiet") == 0) { + ntfs_log_clear_levels(NTFS_LOG_LEVEL_QUIET); + return TRUE; + } else if (strcmp(option, "--log-trace") == 0) { + ntfs_log_set_levels(NTFS_LOG_LEVEL_TRACE); + return TRUE; + } else if ((strcmp(option, "--log-colour") == 0) || + (strcmp(option, "--log-color") == 0)) { + ntfs_log_set_flags(NTFS_LOG_FLAG_COLOUR); + return TRUE; + } + + ntfs_log_debug("Unknown logging option '%s'\n", option); + return FALSE; +} + diff --git a/libntfs-3g/mft.c b/libntfs-3g/mft.c new file mode 100644 index 00000000..2ef1b4ea --- /dev/null +++ b/libntfs-3g/mft.c @@ -0,0 +1,1576 @@ +/** + * mft.c - Mft record handling code. Originated from the Linux-NTFS project. + * + * Copyright (c) 2000-2004 Anton Altaparmakov + * Copyright (c) 2004-2005 Richard Russon + * Copyright (c) 2004-2006 Szabolcs Szakacsits + * Copyright (c) 2005 Yura Pakhuchiy + * + * 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 + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program/include file 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 + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STDIO_H +#include +#endif +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif +#include + +#include "compat.h" +#include "types.h" +#include "device.h" +#include "debug.h" +#include "bitmap.h" +#include "attrib.h" +#include "inode.h" +#include "volume.h" +#include "layout.h" +#include "lcnalloc.h" +#include "mft.h" +#include "logging.h" +#include "misc.h" + +/** + * ntfs_mft_records_read - read records from the mft from disk + * @vol: volume to read from + * @mref: starting mft record number to read + * @count: number of mft records to read + * @b: output data buffer + * + * Read @count mft records starting at @mref from volume @vol into buffer + * @b. Return 0 on success or -1 on error, with errno set to the error + * code. + * + * If any of the records exceed the initialized size of the $MFT/$DATA + * attribute, i.e. they cannot possibly be allocated mft records, assume this + * is a bug and return error code ESPIPE. + * + * The read mft records are mst deprotected and are hence ready to use. The + * caller should check each record with is_baad_record() in case mst + * deprotection failed. + * + * NOTE: @b has to be at least of size @count * vol->mft_record_size. + */ +int ntfs_mft_records_read(const ntfs_volume *vol, const MFT_REF mref, + const s64 count, MFT_RECORD *b) +{ + s64 br; + VCN m; + + ntfs_log_trace("Entering for inode 0x%llx.\n", MREF(mref)); + if (!vol || !vol->mft_na || !b || count < 0) { + errno = EINVAL; + return -1; + } + m = MREF(mref); + /* Refuse to read non-allocated mft records. */ + if (m + count > vol->mft_na->initialized_size >> + vol->mft_record_size_bits) { + errno = ESPIPE; + ntfs_log_perror("Trying to read non-allocated mft records " + "(%lld > %lld)", m + count, + vol->mft_na->initialized_size >> + vol->mft_record_size_bits); + return -1; + } + br = ntfs_attr_mst_pread(vol->mft_na, m << vol->mft_record_size_bits, + count, vol->mft_record_size, b); + if (br != count) { + if (br != -1) + errno = EIO; + if (br >= 0) + ntfs_log_debug("Error: partition is smaller than it should " + "be!\n"); + else + ntfs_log_perror("Error reading $Mft record(s)"); + return -1; + } + return 0; +} + +/** + * ntfs_mft_records_write - write mft records to disk + * @vol: volume to write to + * @mref: starting mft record number to write + * @count: number of mft records to write + * @b: data buffer containing the mft records to write + * + * Write @count mft records starting at @mref from data buffer @b to volume + * @vol. Return 0 on success or -1 on error, with errno set to the error code. + * + * If any of the records exceed the initialized size of the $MFT/$DATA + * attribute, i.e. they cannot possibly be allocated mft records, assume this + * is a bug and return error code ESPIPE. + * + * Before the mft records are written, they are mst protected. After the write, + * they are deprotected again, thus resulting in an increase in the update + * sequence number inside the data buffer @b. + * + * If any mft records are written which are also represented in the mft mirror + * $MFTMirr, we make a copy of the relevant parts of the data buffer @b into a + * temporary buffer before we do the actual write. Then if at least one mft + * record was successfully written, we write the appropriate mft records from + * the copied buffer to the mft mirror, too. + */ +int ntfs_mft_records_write(const ntfs_volume *vol, const MFT_REF mref, + const s64 count, MFT_RECORD *b) +{ + s64 bw; + VCN m; + void *bmirr = NULL; + int cnt = 0, res = 0; + + ntfs_log_trace("Entering for inode 0x%llx.\n", MREF(mref)); + if (!vol || !vol->mft_na || vol->mftmirr_size <= 0 || !b || count < 0) { + errno = EINVAL; + return -1; + } + m = MREF(mref); + /* Refuse to write non-allocated mft records. */ + if (m + count > vol->mft_na->initialized_size >> + vol->mft_record_size_bits) { + errno = ESPIPE; + ntfs_log_perror("Trying to write non-allocated mft records " + "(%lld > %lld)", m + count, + vol->mft_na->initialized_size >> + vol->mft_record_size_bits); + return -1; + } + if (m < vol->mftmirr_size) { + if (!vol->mftmirr_na) { + errno = EINVAL; + return -1; + } + cnt = vol->mftmirr_size - m; + if (cnt > count) + cnt = count; + bmirr = ntfs_malloc(cnt * vol->mft_record_size); + if (!bmirr) + return -1; + memcpy(bmirr, b, cnt * vol->mft_record_size); + } + bw = ntfs_attr_mst_pwrite(vol->mft_na, m << vol->mft_record_size_bits, + count, vol->mft_record_size, b); + if (bw != count) { + if (bw != -1) + errno = EIO; + if (bw >= 0) + ntfs_log_debug("Error: partial write while writing $Mft " + "record(s)!\n"); + else + ntfs_log_perror("Error writing $Mft record(s)"); + res = errno; + } + if (bmirr && bw > 0) { + if (bw < cnt) + cnt = bw; + bw = ntfs_attr_mst_pwrite(vol->mftmirr_na, + m << vol->mft_record_size_bits, cnt, + vol->mft_record_size, bmirr); + if (bw != cnt) { + if (bw != -1) + errno = EIO; + ntfs_log_debug("Error: failed to sync $MFTMirr! Run " + "chkdsk.\n"); + res = errno; + } + } + free(bmirr); + if (!res) + return res; + errno = res; + return -1; +} + +/** + * ntfs_file_record_read - read a FILE record from the mft from disk + * @vol: volume to read from + * @mref: mft reference specifying mft record to read + * @mrec: address of pointer in which to return the mft record + * @attr: address of pointer in which to return the first attribute + * + * Read a FILE record from the mft of @vol from the storage medium. @mref + * specifies the mft record to read, including the sequence number, which can + * be 0 if no sequence number checking is to be performed. + * + * The function allocates a buffer large enough to hold the mft record and + * reads the record into the buffer (mst deprotecting it in the process). + * *@mrec is then set to point to the buffer. + * + * If @attr is not NULL, *@attr is set to point to the first attribute in the + * mft record, i.e. *@attr is a pointer into *@mrec. + * + * Return 0 on success, or -1 on error, with errno set to the error code. + * + * The read mft record is checked for having the magic FILE, + * and for having a matching sequence number (if MSEQNO(*@mref) != 0). + * If either of these fails, -1 is returned and errno is set to EIO. If you get + * this, but you still want to read the mft record (e.g. in order to correct + * it), use ntfs_mft_record_read() directly. + * + * Note: Caller has to free *@mrec when finished. + * + * Note: We do not check if the mft record is flagged in use. The caller can + * check if desired. + */ +int ntfs_file_record_read(const ntfs_volume *vol, const MFT_REF mref, + MFT_RECORD **mrec, ATTR_RECORD **attr) +{ + MFT_RECORD *m; + ATTR_RECORD *a; + int err; + + if (!vol || !mrec) { + errno = EINVAL; + return -1; + } + m = *mrec; + if (!m) { + m = ntfs_malloc(vol->mft_record_size); + if (!m) + return -1; + } + if (ntfs_mft_record_read(vol, mref, m)) { + err = errno; + goto read_failed; + } + if (!ntfs_is_file_record(m->magic)) + goto file_corrupt; + if (MSEQNO(mref) && MSEQNO(mref) != le16_to_cpu(m->sequence_number)) + goto file_corrupt; + a = (ATTR_RECORD*)((char*)m + le16_to_cpu(m->attrs_offset)); + if (p2n(a) < p2n(m) || (char*)a > (char*)m + vol->mft_record_size) + goto file_corrupt; + *mrec = m; + if (attr) + *attr = a; + return 0; +file_corrupt: + ntfs_log_debug("ntfs_file_record_read(): file is corrupt.\n"); + err = EIO; +read_failed: + if (m != *mrec) + free(m); + errno = err; + return -1; +} + +/** + * ntfs_mft_record_layout - layout an mft record into a memory buffer + * @vol: volume to which the mft record will belong + * @mref: mft reference specifying the mft record number + * @mrec: destination buffer of size >= @vol->mft_record_size bytes + * + * Layout an empty, unused mft record with the mft reference @mref into the + * buffer @m. The volume @vol is needed because the mft record structure was + * modified in NTFS 3.1 so we need to know which volume version this mft record + * will be used on. + * + * On success return 0 and on error return -1 with errno set to the error code. + */ +int ntfs_mft_record_layout(const ntfs_volume *vol, const MFT_REF mref, + MFT_RECORD *mrec) +{ + ATTR_RECORD *a; + + if (!vol || !mrec) { + errno = EINVAL; + return -1; + } + /* Aligned to 2-byte boundary. */ + if (vol->major_ver < 3 || (vol->major_ver == 3 && !vol->minor_ver)) + mrec->usa_ofs = cpu_to_le16((sizeof(MFT_RECORD_OLD) + 1) & ~1); + else { + /* Abort if mref is > 32 bits. */ + if (MREF(mref) & 0x0000ffff00000000ull) { + ntfs_log_debug("Mft reference exceeds 32 bits!\n"); + errno = ERANGE; + return -1; + } + mrec->usa_ofs = cpu_to_le16((sizeof(MFT_RECORD) + 1) & ~1); + /* + * Set the NTFS 3.1+ specific fields while we know that the + * volume version is 3.1+. + */ + mrec->reserved = cpu_to_le16(0); + mrec->mft_record_number = cpu_to_le32(MREF(mref)); + } + mrec->magic = magic_FILE; + if (vol->mft_record_size >= NTFS_BLOCK_SIZE) + mrec->usa_count = cpu_to_le16(vol->mft_record_size / + NTFS_BLOCK_SIZE + 1); + else { + mrec->usa_count = cpu_to_le16(1); + ntfs_log_error("Sector size is bigger than MFT record size. " + "Setting usa_count to 1. If Windows chkdsk " + "reports this as corruption, please email %s " + "stating that you saw this message and that " + "the file system created was corrupt. " + "Thank you.\n", NTFS_DEV_LIST); + } + /* Set the update sequence number to 1. */ + *(u16*)((u8*)mrec + le16_to_cpu(mrec->usa_ofs)) = cpu_to_le16(1); + mrec->lsn = cpu_to_le64(0ull); + mrec->sequence_number = cpu_to_le16(1); + mrec->link_count = cpu_to_le16(0); + /* Aligned to 8-byte boundary. */ + mrec->attrs_offset = cpu_to_le16((le16_to_cpu(mrec->usa_ofs) + + (le16_to_cpu(mrec->usa_count) << 1) + 7) & ~7); + mrec->flags = cpu_to_le16(0); + /* + * Using attrs_offset plus eight bytes (for the termination attribute), + * aligned to 8-byte boundary. + */ + mrec->bytes_in_use = cpu_to_le32((le16_to_cpu(mrec->attrs_offset) + 8 + + 7) & ~7); + mrec->bytes_allocated = cpu_to_le32(vol->mft_record_size); + mrec->base_mft_record = cpu_to_le64((MFT_REF)0); + mrec->next_attr_instance = cpu_to_le16(0); + a = (ATTR_RECORD*)((u8*)mrec + le16_to_cpu(mrec->attrs_offset)); + a->type = AT_END; + a->length = cpu_to_le32(0); + /* Finally, clear the unused part of the mft record. */ + memset((u8*)a + 8, 0, vol->mft_record_size - ((u8*)a + 8 - (u8*)mrec)); + return 0; +} + +/** + * ntfs_mft_record_format - format an mft record on an ntfs volume + * @vol: volume on which to format the mft record + * @mref: mft reference specifying mft record to format + * + * Format the mft record with the mft reference @mref in $MFT/$DATA, i.e. lay + * out an empty, unused mft record in memory and write it to the volume @vol. + * + * On success return 0 and on error return -1 with errno set to the error code. + */ +int ntfs_mft_record_format(const ntfs_volume *vol, const MFT_REF mref) +{ + MFT_RECORD *m; + int err; + + if (!vol || !vol->mft_na) { + errno = EINVAL; + return -1; + } + m = ntfs_calloc(vol->mft_record_size); + if (!m) + return -1; + if (ntfs_mft_record_layout(vol, mref, m)) { + err = errno; + free(m); + errno = err; + return -1; + } + if (ntfs_mft_record_write(vol, mref, m)) { + err = errno; + free(m); + errno = err; + return -1; + } + free(m); + return 0; +} + +static const char *es = " Leaving inconsistent metadata. Run chkdsk."; + +/** + * ntfs_ffz - Find the first unset (zero) bit in a word + * @word: + * + * Description... + * + * Returns: + */ +static inline unsigned int ntfs_ffz(unsigned int word) +{ + return ffs(~word) - 1; +} + +#ifndef PAGE_SIZE +#define PAGE_SIZE 4096 +#endif + +/** + * ntfs_mft_bitmap_find_free_rec - find a free mft record in the mft bitmap + * @vol: volume on which to search for a free mft record + * @base_ni: open base inode if allocating an extent mft record or NULL + * + * Search for a free mft record in the mft bitmap attribute on the ntfs volume + * @vol. + * + * If @base_ni is NULL start the search at the default allocator position. + * + * If @base_ni is not NULL start the search at the mft record after the base + * mft record @base_ni. + * + * Return the free mft record on success and -1 on error with errno set to the + * error code. An error code of ENOSPC means that there are no free mft + * records in the currently initialized mft bitmap. + */ +static int ntfs_mft_bitmap_find_free_rec(ntfs_volume *vol, ntfs_inode *base_ni) +{ + s64 pass_end, ll, data_pos, pass_start, ofs, bit; + ntfs_attr *mftbmp_na; + u8 *buf, *byte; + unsigned int size; + u8 pass, b; + + mftbmp_na = vol->mftbmp_na; + /* + * Set the end of the pass making sure we do not overflow the mft + * bitmap. + */ + size = PAGE_SIZE; + pass_end = vol->mft_na->allocated_size >> vol->mft_record_size_bits; + ll = mftbmp_na->initialized_size << 3; + if (pass_end > ll) + pass_end = ll; + pass = 1; + if (!base_ni) + data_pos = vol->mft_data_pos; + else + data_pos = base_ni->mft_no + 1; + if (data_pos < 24) + data_pos = 24; + if (data_pos >= pass_end) { + data_pos = 24; + pass = 2; + /* This happens on a freshly formatted volume. */ + if (data_pos >= pass_end) { + errno = ENOSPC; + return -1; + } + } + pass_start = data_pos; + buf = ntfs_malloc(PAGE_SIZE); + if (!buf) + return -1; + + ntfs_log_debug("Starting bitmap search: pass %u, pass_start 0x%llx, " + "pass_end 0x%llx, data_pos 0x%llx.\n", pass, + (long long)pass_start, (long long)pass_end, + (long long)data_pos); +#ifdef DEBUG + byte = NULL; + b = 0; +#endif + /* Loop until a free mft record is found. */ + for (; pass <= 2; size = PAGE_SIZE) { + /* Cap size to pass_end. */ + ofs = data_pos >> 3; + ll = ((pass_end + 7) >> 3) - ofs; + if (size > ll) + size = ll; + ll = ntfs_attr_pread(mftbmp_na, ofs, size, buf); + if (ll < 0) { + ntfs_log_error("Failed to read mft bitmap " + "attribute, aborting.\n"); + free(buf); + return -1; + } + ntfs_log_debug("Read 0x%llx bytes.\n", (long long)ll); + /* If we read at least one byte, search @buf for a zero bit. */ + if (ll) { + size = ll << 3; + bit = data_pos & 7; + data_pos &= ~7ull; + ntfs_log_debug("Before inner for loop: size 0x%x, " + "data_pos 0x%llx, bit 0x%llx, " + "*byte 0x%hhx, b %u.\n", size, + (long long)data_pos, (long long)bit, + byte ? *byte : -1, b); + for (; bit < size && data_pos + bit < pass_end; + bit &= ~7ull, bit += 8) { + byte = buf + (bit >> 3); + if (*byte == 0xff) + continue; + /* Note: ffz() result must be zero based. */ + b = ntfs_ffz((unsigned long)*byte); + if (b < 8 && b >= (bit & 7)) { + free(buf); + return data_pos + (bit & ~7ull) + b; + } + } + ntfs_log_debug("After inner for loop: size 0x%x, " + "data_pos 0x%llx, bit 0x%llx, " + "*byte 0x%hhx, b %u.\n", size, + (long long)data_pos, (long long)bit, + byte ? *byte : -1, b); + data_pos += size; + /* + * If the end of the pass has not been reached yet, + * continue searching the mft bitmap for a zero bit. + */ + if (data_pos < pass_end) + continue; + } + /* Do the next pass. */ + pass++; + if (pass == 2) { + /* + * Starting the second pass, in which we scan the first + * part of the zone which we omitted earlier. + */ + pass_end = pass_start; + data_pos = pass_start = 24; + ntfs_log_debug("pass %i, pass_start 0x%llx, pass_end " + "0x%llx.\n", pass, (long long)pass_start, + (long long)pass_end); + if (data_pos >= pass_end) + break; + } + } + /* No free mft records in currently initialized mft bitmap. */ + free(buf); + errno = ENOSPC; + return -1; +} + +/** + * ntfs_mft_bitmap_extend_allocation - extend mft bitmap attribute by a cluster + * @vol: volume on which to extend the mft bitmap attribute + * + * Extend the mft bitmap attribute on the ntfs volume @vol by one cluster. + * + * Note: Only changes allocated_size, i.e. does not touch initialized_size or + * data_size. + * + * Return 0 on success and -1 on error with errno set to the error code. + */ +static int ntfs_mft_bitmap_extend_allocation(ntfs_volume *vol) +{ + LCN lcn; + s64 ll = 0; /* silence compiler warning */ + ntfs_attr *mftbmp_na, *lcnbmp_na; + runlist_element *rl, *rl2 = NULL; /* silence compiler warning */ + ntfs_attr_search_ctx *ctx; + MFT_RECORD *m = NULL; /* silence compiler warning */ + ATTR_RECORD *a = NULL; /* silence compiler warning */ + int ret, mp_size; + u32 old_alen = 0; /* silence compiler warning */ + u8 b, tb; + struct { + u8 added_cluster:1; + u8 added_run:1; + u8 mp_rebuilt:1; + } status = { 0, 0, 0 }; + + mftbmp_na = vol->mftbmp_na; + lcnbmp_na = vol->lcnbmp_na; + /* + * Determine the last lcn of the mft bitmap. The allocated size of the + * mft bitmap cannot be zero so we are ok to do this. + */ + rl = ntfs_attr_find_vcn(mftbmp_na, (mftbmp_na->allocated_size - 1) >> + vol->cluster_size_bits); + if (!rl || !rl->length || rl->lcn < 0) { + ntfs_log_error("Failed to determine last allocated " + "cluster of mft bitmap attribute.\n"); + if (rl) + errno = EIO; + return -1; + } + lcn = rl->lcn + rl->length; + /* + * Attempt to get the cluster following the last allocated cluster by + * hand as it may be in the MFT zone so the allocator would not give it + * to us. + */ + ret = (int)ntfs_attr_pread(lcnbmp_na, lcn >> 3, 1, &b); + if (ret < 0) { + ntfs_log_error("Failed to read from lcn bitmap.\n"); + return -1; + } + ntfs_log_debug("Read %i byte%s.\n", ret, ret == 1 ? "" : "s"); + tb = 1 << (lcn & 7ull); + if (ret == 1 && b != 0xff && !(b & tb)) { + /* Next cluster is free, allocate it. */ + b |= tb; + ret = (int)ntfs_attr_pwrite(lcnbmp_na, lcn >> 3, 1, &b); + if (ret < 1) { + ntfs_log_error("Failed to write to lcn " + "bitmap.\n"); + if (!ret) + errno = EIO; + return -1; + } + /* Update the mft bitmap runlist. */ + rl->length++; + rl[1].vcn++; + status.added_cluster = 1; + ntfs_log_debug("Appending one cluster to mft bitmap.\n"); + } else { + /* Allocate a cluster from the DATA_ZONE. */ + rl2 = ntfs_cluster_alloc(vol, rl[1].vcn, 1, lcn, DATA_ZONE); + if (!rl2) { + ntfs_log_error("Failed to allocate a cluster for " + "the mft bitmap.\n"); + return -1; + } + rl = ntfs_runlists_merge(mftbmp_na->rl, rl2); + if (!rl) { + ret = errno; + ntfs_log_error("Failed to merge runlists for mft " + "bitmap.\n"); + if (ntfs_cluster_free_from_rl(vol, rl2)) + ntfs_log_error("Failed to deallocate " + "cluster.%s\n", es); + free(rl2); + errno = ret; + return -1; + } + mftbmp_na->rl = rl; + status.added_run = 1; + ntfs_log_debug("Adding one run to mft bitmap.\n"); + /* Find the last run in the new runlist. */ + for (; rl[1].length; rl++) + ; + } + /* + * Update the attribute record as well. Note: @rl is the last + * (non-terminator) runlist element of mft bitmap. + */ + ctx = ntfs_attr_get_search_ctx(mftbmp_na->ni, NULL); + if (!ctx) { + ntfs_log_error("Failed to get search context.\n"); + goto undo_alloc; + } + if (ntfs_attr_lookup(mftbmp_na->type, mftbmp_na->name, + mftbmp_na->name_len, 0, rl[1].vcn, NULL, 0, ctx)) { + ntfs_log_error("Failed to find last attribute extent of " + "mft bitmap attribute.\n"); + goto undo_alloc; + } + m = ctx->mrec; + a = ctx->attr; + ll = sle64_to_cpu(a->lowest_vcn); + rl2 = ntfs_attr_find_vcn(mftbmp_na, ll); + if (!rl2 || !rl2->length) { + ntfs_log_error("Failed to determine previous last " + "allocated cluster of mft bitmap attribute.\n"); + if (rl2) + errno = EIO; + goto undo_alloc; + } + /* Get the size for the new mapping pairs array for this extent. */ + mp_size = ntfs_get_size_for_mapping_pairs(vol, rl2, ll); + if (mp_size <= 0) { + ntfs_log_error("Get size for mapping pairs failed for " + "mft bitmap attribute extent.\n"); + goto undo_alloc; + } + /* Expand the attribute record if necessary. */ + old_alen = le32_to_cpu(a->length); + if (ntfs_attr_record_resize(m, a, mp_size + + le16_to_cpu(a->mapping_pairs_offset))) { + // TODO: Deal with this by moving this extent to a new mft + // record or by starting a new extent in a new mft record. + ntfs_log_error("Not enough space in this mft record to " + "accommodate extended mft bitmap attribute " + "extent. Cannot handle this yet.\n"); + errno = EOPNOTSUPP; + goto undo_alloc; + } + status.mp_rebuilt = 1; + /* Generate the mapping pairs array directly into the attr record. */ + if (ntfs_mapping_pairs_build(vol, (u8*)a + + le16_to_cpu(a->mapping_pairs_offset), mp_size, rl2, ll, + NULL)) { + ntfs_log_error("Failed to build mapping pairs array for " + "mft bitmap attribute.\n"); + errno = EIO; + goto undo_alloc; + } + /* Update the highest_vcn. */ + a->highest_vcn = cpu_to_sle64(rl[1].vcn - 1); + /* + * We now have extended the mft bitmap allocated_size by one cluster. + * Reflect this in the ntfs_attr structure and the attribute record. + */ + if (a->lowest_vcn) { + /* + * We are not in the first attribute extent, switch to it, but + * first ensure the changes will make it to disk later. + */ + ntfs_inode_mark_dirty(ctx->ntfs_ino); + ntfs_attr_reinit_search_ctx(ctx); + if (ntfs_attr_lookup(mftbmp_na->type, mftbmp_na->name, + mftbmp_na->name_len, 0, 0, NULL, 0, ctx)) { + ntfs_log_error("Failed to find first attribute " + "extent of mft bitmap attribute.\n"); + goto restore_undo_alloc; + } + a = ctx->attr; + } + mftbmp_na->allocated_size += vol->cluster_size; + a->allocated_size = cpu_to_sle64(mftbmp_na->allocated_size); + /* Ensure the changes make it to disk. */ + ntfs_inode_mark_dirty(ctx->ntfs_ino); + ntfs_attr_put_search_ctx(ctx); + return 0; +restore_undo_alloc: + ret = errno; + ntfs_attr_reinit_search_ctx(ctx); + if (ntfs_attr_lookup(mftbmp_na->type, mftbmp_na->name, + mftbmp_na->name_len, 0, rl[1].vcn, NULL, 0, ctx)) { + ntfs_log_error("Failed to find last attribute extent of " + "mft bitmap attribute.%s\n", es); + ntfs_attr_put_search_ctx(ctx); + mftbmp_na->allocated_size += vol->cluster_size; + /* + * The only thing that is now wrong is ->allocated_size of the + * base attribute extent which chkdsk should be able to fix. + */ + errno = ret; + return -1; + } + m = ctx->mrec; + a = ctx->attr; + a->highest_vcn = cpu_to_sle64(rl[1].vcn - 2); + errno = ret; +undo_alloc: + ret = errno; + if (status.added_cluster) { + /* Truncate the last run in the runlist by one cluster. */ + rl->length--; + rl[1].vcn--; + } else if (status.added_run) { + lcn = rl->lcn; + /* Remove the last run from the runlist. */ + rl->lcn = rl[1].lcn; + rl->length = 0; + } + /* Deallocate the cluster. */ + if (ntfs_bitmap_clear_bit(lcnbmp_na, lcn)) + ntfs_log_error("Failed to free cluster.%s\n", es); + if (status.mp_rebuilt) { + if (ntfs_mapping_pairs_build(vol, (u8*)a + + le16_to_cpu(a->mapping_pairs_offset), + old_alen - le16_to_cpu(a->mapping_pairs_offset), + rl2, ll, NULL)) + ntfs_log_error("Failed to restore mapping " + "pairs array.%s\n", es); + if (ntfs_attr_record_resize(m, a, old_alen)) + ntfs_log_error("Failed to restore attribute " + "record.%s\n", es); + ntfs_inode_mark_dirty(ctx->ntfs_ino); + } + if (ctx) + ntfs_attr_put_search_ctx(ctx); + errno = ret; + return -1; +} + +/** + * ntfs_mft_bitmap_extend_initialized - extend mft bitmap initialized data + * @vol: volume on which to extend the mft bitmap attribute + * + * Extend the initialized portion of the mft bitmap attribute on the ntfs + * volume @vol by 8 bytes. + * + * Note: Only changes initialized_size and data_size, i.e. requires that + * allocated_size is big enough to fit the new initialized_size. + * + * Return 0 on success and -1 on error with errno set to the error code. + */ +static int ntfs_mft_bitmap_extend_initialized(ntfs_volume *vol) +{ + s64 old_data_size, old_initialized_size, ll; + ntfs_attr *mftbmp_na; + ntfs_attr_search_ctx *ctx; + ATTR_RECORD *a; + int err; + + mftbmp_na = vol->mftbmp_na; + ctx = ntfs_attr_get_search_ctx(mftbmp_na->ni, NULL); + if (!ctx) { + ntfs_log_error("Failed to get search context.\n"); + return -1; + } + if (ntfs_attr_lookup(mftbmp_na->type, mftbmp_na->name, + mftbmp_na->name_len, 0, 0, NULL, 0, ctx)) { + ntfs_log_error("Failed to find first attribute extent of " + "mft bitmap attribute.\n"); + err = errno; + goto put_err_out; + } + a = ctx->attr; + old_data_size = mftbmp_na->data_size; + old_initialized_size = mftbmp_na->initialized_size; + mftbmp_na->initialized_size += 8; + a->initialized_size = cpu_to_sle64(mftbmp_na->initialized_size); + if (mftbmp_na->initialized_size > mftbmp_na->data_size) { + mftbmp_na->data_size = mftbmp_na->initialized_size; + a->data_size = cpu_to_sle64(mftbmp_na->data_size); + } + /* Ensure the changes make it to disk. */ + ntfs_inode_mark_dirty(ctx->ntfs_ino); + ntfs_attr_put_search_ctx(ctx); + /* Initialize the mft bitmap attribute value with zeroes. */ + ll = 0; + ll = ntfs_attr_pwrite(mftbmp_na, old_initialized_size, 8, &ll); + if (ll == 8) { + ntfs_log_debug("Wrote eight initialized bytes to mft bitmap.\n"); + return 0; + } + ntfs_log_error("Failed to write to mft bitmap.\n"); + err = errno; + if (ll >= 0) + err = EIO; + /* Try to recover from the error. */ + ctx = ntfs_attr_get_search_ctx(mftbmp_na->ni, NULL); + if (!ctx) { + ntfs_log_error("Failed to get search context.%s\n", es); + goto err_out; + } + if (ntfs_attr_lookup(mftbmp_na->type, mftbmp_na->name, + mftbmp_na->name_len, 0, 0, NULL, 0, ctx)) { + ntfs_log_error("Failed to find first attribute extent of " + "mft bitmap attribute.%s\n", es); +put_err_out: + ntfs_attr_put_search_ctx(ctx); + goto err_out; + } + a = ctx->attr; + mftbmp_na->initialized_size = old_initialized_size; + a->initialized_size = cpu_to_sle64(old_initialized_size); + if (mftbmp_na->data_size != old_data_size) { + mftbmp_na->data_size = old_data_size; + a->data_size = cpu_to_sle64(old_data_size); + } + ntfs_inode_mark_dirty(ctx->ntfs_ino); + ntfs_attr_put_search_ctx(ctx); + ntfs_log_debug("Restored status of mftbmp: allocated_size 0x%llx, " + "data_size 0x%llx, initialized_size 0x%llx.\n", + (long long)mftbmp_na->allocated_size, + (long long)mftbmp_na->data_size, + (long long)mftbmp_na->initialized_size); +err_out: + errno = err; + return -1; +} + +/** + * ntfs_mft_data_extend_allocation - extend mft data attribute + * @vol: volume on which to extend the mft data attribute + * + * Extend the mft data attribute on the ntfs volume @vol by 16 mft records + * worth of clusters or if not enough space for this by one mft record worth + * of clusters. + * + * Note: Only changes allocated_size, i.e. does not touch initialized_size or + * data_size. + * + * Return 0 on success and -1 on error with errno set to the error code. + */ +static int ntfs_mft_data_extend_allocation(ntfs_volume *vol) +{ + LCN lcn; + VCN old_last_vcn; + s64 min_nr, nr, ll = 0; /* silence compiler warning */ + ntfs_attr *mft_na; + runlist_element *rl, *rl2; + ntfs_attr_search_ctx *ctx; + MFT_RECORD *m = NULL; /* silence compiler warning */ + ATTR_RECORD *a = NULL; /* silence compiler warning */ + int err, mp_size; + u32 old_alen = 0; /* silence compiler warning */ + BOOL mp_rebuilt = FALSE; + + ntfs_log_debug("Extending mft data allocation.\n"); + mft_na = vol->mft_na; + /* + * Determine the preferred allocation location, i.e. the last lcn of + * the mft data attribute. The allocated size of the mft data + * attribute cannot be zero so we are ok to do this. + */ + rl = ntfs_attr_find_vcn(mft_na, + (mft_na->allocated_size - 1) >> vol->cluster_size_bits); + if (!rl || !rl->length || rl->lcn < 0) { + ntfs_log_error("Failed to determine last allocated " + "cluster of mft data attribute.\n"); + if (rl) + errno = EIO; + return -1; + } + lcn = rl->lcn + rl->length; + ntfs_log_debug("Last lcn of mft data attribute is 0x%llx.\n", (long long)lcn); + /* Minimum allocation is one mft record worth of clusters. */ + min_nr = vol->mft_record_size >> vol->cluster_size_bits; + if (!min_nr) + min_nr = 1; + /* Want to allocate 16 mft records worth of clusters. */ + nr = vol->mft_record_size << 4 >> vol->cluster_size_bits; + if (!nr) + nr = min_nr; + ntfs_log_debug("Trying mft data allocation with default cluster count " + "%lli.\n", (long long)nr); + old_last_vcn = rl[1].vcn; + do { + rl2 = ntfs_cluster_alloc(vol, old_last_vcn, nr, lcn, MFT_ZONE); + if (rl2) + break; + if (errno != ENOSPC || nr == min_nr) { + ntfs_log_error("Failed to allocate the minimal " + "number of clusters (%lli) for the " + "mft data attribute.\n", (long long)nr); + return -1; + } + /* + * There is not enough space to do the allocation, but there + * might be enough space to do a minimal allocation so try that + * before failing. + */ + nr = min_nr; + ntfs_log_debug("Retrying mft data allocation with minimal cluster " + "count %lli.\n", (long long)nr); + } while (1); + rl = ntfs_runlists_merge(mft_na->rl, rl2); + if (!rl) { + err = errno; + ntfs_log_error("Failed to merge runlists for mft data " + "attribute.\n"); + if (ntfs_cluster_free_from_rl(vol, rl2)) + ntfs_log_error("Failed to deallocate clusters " + "from the mft data attribute.%s\n", es); + free(rl2); + errno = err; + return -1; + } + mft_na->rl = rl; + ntfs_log_debug("Allocated %lli clusters.\n", nr); + /* Find the last run in the new runlist. */ + for (; rl[1].length; rl++) + ; + /* Update the attribute record as well. */ + ctx = ntfs_attr_get_search_ctx(mft_na->ni, NULL); + if (!ctx) { + ntfs_log_error("Failed to get search context.\n"); + goto undo_alloc; + } + if (ntfs_attr_lookup(mft_na->type, mft_na->name, mft_na->name_len, 0, + rl[1].vcn, NULL, 0, ctx)) { + ntfs_log_error("Failed to find last attribute extent of " + "mft data attribute.\n"); + goto undo_alloc; + } + m = ctx->mrec; + a = ctx->attr; + ll = sle64_to_cpu(a->lowest_vcn); + rl2 = ntfs_attr_find_vcn(mft_na, ll); + if (!rl2 || !rl2->length) { + ntfs_log_error("Failed to determine previous last " + "allocated cluster of mft data attribute.\n"); + if (rl2) + errno = EIO; + goto undo_alloc; + } + /* Get the size for the new mapping pairs array for this extent. */ + mp_size = ntfs_get_size_for_mapping_pairs(vol, rl2, ll); + if (mp_size <= 0) { + ntfs_log_error("Get size for mapping pairs failed for " + "mft data attribute extent.\n"); + goto undo_alloc; + } + /* Expand the attribute record if necessary. */ + old_alen = le32_to_cpu(a->length); + if (ntfs_attr_record_resize(m, a, + mp_size + le16_to_cpu(a->mapping_pairs_offset))) { + // TODO: Deal with this by moving this extent to a new mft + // record or by starting a new extent in a new mft record. + // Note: Use the special reserved mft records and ensure that + // this extent is not required to find the mft record in + // question. + errno = EOPNOTSUPP; + ntfs_log_perror("Not enough space to extended mft data " + "attribute.\n"); + goto undo_alloc; + } + mp_rebuilt = TRUE; + /* + * Generate the mapping pairs array directly into the attribute record. + */ + if (ntfs_mapping_pairs_build(vol, + (u8*)a + le16_to_cpu(a->mapping_pairs_offset), mp_size, + rl2, ll, NULL)) { + ntfs_log_error("Failed to build mapping pairs array of " + "mft data attribute.\n"); + errno = EIO; + goto undo_alloc; + } + /* Update the highest_vcn. */ + a->highest_vcn = cpu_to_sle64(rl[1].vcn - 1); + /* + * We now have extended the mft data allocated_size by nr clusters. + * Reflect this in the ntfs_attr structure and the attribute record. + * @rl is the last (non-terminator) runlist element of mft data + * attribute. + */ + if (a->lowest_vcn) { + /* + * We are not in the first attribute extent, switch to it, but + * first ensure the changes will make it to disk later. + */ + ntfs_inode_mark_dirty(ctx->ntfs_ino); + ntfs_attr_reinit_search_ctx(ctx); + if (ntfs_attr_lookup(mft_na->type, mft_na->name, + mft_na->name_len, 0, 0, NULL, 0, ctx)) { + ntfs_log_error("Failed to find first attribute " + "extent of mft data attribute.\n"); + goto restore_undo_alloc; + } + a = ctx->attr; + } + mft_na->allocated_size += nr << vol->cluster_size_bits; + a->allocated_size = cpu_to_sle64(mft_na->allocated_size); + /* Ensure the changes make it to disk. */ + ntfs_inode_mark_dirty(ctx->ntfs_ino); + ntfs_attr_put_search_ctx(ctx); + return 0; +restore_undo_alloc: + err = errno; + ntfs_attr_reinit_search_ctx(ctx); + if (ntfs_attr_lookup(mft_na->type, mft_na->name, mft_na->name_len, 0, + rl[1].vcn, NULL, 0, ctx)) { + ntfs_log_error("Failed to find last attribute extent of " + "mft data attribute.%s\n", es); + ntfs_attr_put_search_ctx(ctx); + mft_na->allocated_size += nr << vol->cluster_size_bits; + /* + * The only thing that is now wrong is ->allocated_size of the + * base attribute extent which chkdsk should be able to fix. + */ + errno = err; + return -1; + } + m = ctx->mrec; + a = ctx->attr; + a->highest_vcn = cpu_to_sle64(old_last_vcn - 1); + errno = err; +undo_alloc: + err = errno; + if (ntfs_cluster_free(vol, mft_na, old_last_vcn, -1) < 0) + ntfs_log_error("Failed to free clusters from mft data " + "attribute.%s\n", es); + if (ntfs_rl_truncate(&mft_na->rl, old_last_vcn)) + ntfs_log_error("Failed to truncate mft data attribute " + "runlist.%s\n", es); + if (mp_rebuilt) { + if (ntfs_mapping_pairs_build(vol, (u8*)a + + le16_to_cpu(a->mapping_pairs_offset), + old_alen - le16_to_cpu(a->mapping_pairs_offset), + rl2, ll, NULL)) + ntfs_log_error("Failed to restore mapping pairs " + "array.%s\n", es); + if (ntfs_attr_record_resize(m, a, old_alen)) + ntfs_log_error("Failed to restore attribute " + "record.%s\n", es); + ntfs_inode_mark_dirty(ctx->ntfs_ino); + } + if (ctx) + ntfs_attr_put_search_ctx(ctx); + errno = err; + return -1; +} + +/** + * ntfs_mft_record_alloc - allocate an mft record on an ntfs volume + * @vol: volume on which to allocate the mft record + * @base_ni: open base inode if allocating an extent mft record or NULL + * + * Allocate an mft record in $MFT/$DATA of an open ntfs volume @vol. + * + * If @base_ni is NULL make the mft record a base mft record and allocate it at + * the default allocator position. + * + * If @base_ni is not NULL make the allocated mft record an extent record, + * allocate it starting at the mft record after the base mft record and attach + * the allocated and opened ntfs inode to the base inode @base_ni. + * + * On success return the now opened ntfs (extent) inode of the mft record. + * + * On error return NULL with errno set to the error code. + * + * To find a free mft record, we scan the mft bitmap for a zero bit. To + * optimize this we start scanning at the place specified by @base_ni or if + * @base_ni is NULL we start where we last stopped and we perform wrap around + * when we reach the end. Note, we do not try to allocate mft records below + * number 24 because numbers 0 to 15 are the defined system files anyway and 16 + * to 24 are special in that they are used for storing extension mft records + * for the $DATA attribute of $MFT. This is required to avoid the possibility + * of creating a run list with a circular dependence which once written to disk + * can never be read in again. Windows will only use records 16 to 24 for + * normal files if the volume is completely out of space. We never use them + * which means that when the volume is really out of space we cannot create any + * more files while Windows can still create up to 8 small files. We can start + * doing this at some later time, it does not matter much for now. + * + * When scanning the mft bitmap, we only search up to the last allocated mft + * record. If there are no free records left in the range 24 to number of + * allocated mft records, then we extend the $MFT/$DATA attribute in order to + * create free mft records. We extend the allocated size of $MFT/$DATA by 16 + * records at a time or one cluster, if cluster size is above 16kiB. If there + * is not sufficient space to do this, we try to extend by a single mft record + * or one cluster, if cluster size is above the mft record size, but we only do + * this if there is enough free space, which we know from the values returned + * by the failed cluster allocation function when we tried to do the first + * allocation. + * + * No matter how many mft records we allocate, we initialize only the first + * allocated mft record, incrementing mft data size and initialized size + * accordingly, open an ntfs_inode for it and return it to the caller, unless + * there are less than 24 mft records, in which case we allocate and initialize + * mft records until we reach record 24 which we consider as the first free mft + * record for use by normal files. + * + * If during any stage we overflow the initialized data in the mft bitmap, we + * extend the initialized size (and data size) by 8 bytes, allocating another + * cluster if required. The bitmap data size has to be at least equal to the + * number of mft records in the mft, but it can be bigger, in which case the + * superfluous bits are padded with zeroes. + * + * Thus, when we return successfully (return value non-zero), we will have: + * - initialized / extended the mft bitmap if necessary, + * - initialized / extended the mft data if necessary, + * - set the bit corresponding to the mft record being allocated in the + * mft bitmap, + * - open an ntfs_inode for the allocated mft record, and we will + * - return the ntfs_inode. + * + * On error (return value zero), nothing will have changed. If we had changed + * anything before the error occurred, we will have reverted back to the + * starting state before returning to the caller. Thus, except for bugs, we + * should always leave the volume in a consistent state when returning from + * this function. + * + * Note, this function cannot make use of most of the normal functions, like + * for example for attribute resizing, etc, because when the run list overflows + * the base mft record and an attribute list is used, it is very important that + * the extension mft records used to store the $DATA attribute of $MFT can be + * reached without having to read the information contained inside them, as + * this would make it impossible to find them in the first place after the + * volume is dismounted. $MFT/$BITMAP probably does not need to follow this + * rule because the bitmap is not essential for finding the mft records, but on + * the other hand, handling the bitmap in this special way would make life + * easier because otherwise there might be circular invocations of functions + * 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) +{ + s64 ll, bit, old_data_initialized, old_data_size; + ntfs_attr *mft_na, *mftbmp_na; + ntfs_attr_search_ctx *ctx; + MFT_RECORD *m; + ATTR_RECORD *a; + ntfs_inode *ni; + int err; + u16 seq_no, usn; + + if (base_ni) + ntfs_log_trace("Entering (allocating an extent mft record for " + "base mft record 0x%llx).\n", + (long long)base_ni->mft_no); + else + ntfs_log_trace("Entering (allocating a base mft record).\n"); + if (!vol || !vol->mft_na || !vol->mftbmp_na) { + errno = EINVAL; + return NULL; + } + mft_na = vol->mft_na; + mftbmp_na = vol->mftbmp_na; + bit = ntfs_mft_bitmap_find_free_rec(vol, base_ni); + if (bit >= 0) { + ntfs_log_debug("Found free record (#1), bit 0x%llx.\n", + (long long)bit); + goto found_free_rec; + } + if (errno != ENOSPC) + return NULL; + /* + * No free mft records left. If the mft bitmap already covers more + * than the currently used mft records, the next records are all free, + * so we can simply allocate the first unused mft record. + * Note: We also have to make sure that the mft bitmap at least covers + * the first 24 mft records as they are special and whilst they may not + * be in use, we do not allocate from them. + */ + ll = mft_na->initialized_size >> vol->mft_record_size_bits; + if (mftbmp_na->initialized_size << 3 > ll && + mftbmp_na->initialized_size > 3) { + bit = ll; + if (bit < 24) + bit = 24; + ntfs_log_debug("Found free record (#2), bit 0x%llx.\n", + (long long)bit); + goto found_free_rec; + } + /* + * The mft bitmap needs to be expanded until it covers the first unused + * mft record that we can allocate. + * Note: The smallest mft record we allocate is mft record 24. + */ + ntfs_log_debug("Status of mftbmp before extension: allocated_size 0x%llx, " + "data_size 0x%llx, initialized_size 0x%llx.\n", + (long long)mftbmp_na->allocated_size, + (long long)mftbmp_na->data_size, + (long long)mftbmp_na->initialized_size); + if (mftbmp_na->initialized_size + 8 > mftbmp_na->allocated_size) { + /* Need to extend bitmap by one more cluster. */ + ntfs_log_debug("mftbmp: initialized_size + 8 > allocated_size.\n"); + if (ntfs_mft_bitmap_extend_allocation(vol)) + goto err_out; + ntfs_log_debug("Status of mftbmp after allocation extension: " + "allocated_size 0x%llx, data_size 0x%llx, " + "initialized_size 0x%llx.\n", + (long long)mftbmp_na->allocated_size, + (long long)mftbmp_na->data_size, + (long long)mftbmp_na->initialized_size); + } + /* + * We now have sufficient allocated space, extend the initialized_size + * as well as the data_size if necessary and fill the new space with + * zeroes. + */ + bit = mftbmp_na->initialized_size << 3; + if (ntfs_mft_bitmap_extend_initialized(vol)) + goto err_out; + ntfs_log_debug("Status of mftbmp after initialized extension: " + "allocated_size 0x%llx, data_size 0x%llx, " + "initialized_size 0x%llx.\n", + (long long)mftbmp_na->allocated_size, + (long long)mftbmp_na->data_size, + (long long)mftbmp_na->initialized_size); + ntfs_log_debug("Found free record (#3), bit 0x%llx.\n", (long long)bit); +found_free_rec: + /* @bit is the found free mft record, allocate it in the mft bitmap. */ + ntfs_log_debug("At found_free_rec.\n"); + if (ntfs_bitmap_set_bit(mftbmp_na, bit)) { + ntfs_log_error("Failed to allocate bit in mft bitmap.\n"); + goto err_out; + } + ntfs_log_debug("Set bit 0x%llx in mft bitmap.\n", (long long)bit); + /* The mft bitmap is now uptodate. Deal with mft data attribute now. */ + ll = (bit + 1) << vol->mft_record_size_bits; + if (ll <= mft_na->initialized_size) { + ntfs_log_debug("Allocated mft record already initialized.\n"); + goto mft_rec_already_initialized; + } + ntfs_log_debug("Initializing allocated mft record.\n"); + /* + * The mft record is outside the initialized data. Extend the mft data + * attribute until it covers the allocated record. The loop is only + * actually traversed more than once when a freshly formatted volume is + * first written to so it optimizes away nicely in the common case. + */ + ntfs_log_debug("Status of mft data before extension: " + "allocated_size 0x%llx, data_size 0x%llx, " + "initialized_size 0x%llx.\n", + (long long)mft_na->allocated_size, + (long long)mft_na->data_size, + (long long)mft_na->initialized_size); + while (ll > mft_na->allocated_size) { + if (ntfs_mft_data_extend_allocation(vol)) + goto undo_mftbmp_alloc; + ntfs_log_debug("Status of mft data after allocation extension: " + "allocated_size 0x%llx, data_size 0x%llx, " + "initialized_size 0x%llx.\n", + (long long)mft_na->allocated_size, + (long long)mft_na->data_size, + (long long)mft_na->initialized_size); + } + old_data_initialized = mft_na->initialized_size; + old_data_size = mft_na->data_size; + /* + * Extend mft data initialized size (and data size of course) to reach + * the allocated mft record, formatting the mft records along the way. + * Note: We only modify the ntfs_attr structure as that is all that is + * needed by ntfs_mft_record_format(). We will update the attribute + * record itself in one fell swoop later on. + */ + while (ll > mft_na->initialized_size) { + s64 ll2 = mft_na->initialized_size >> vol->mft_record_size_bits; + mft_na->initialized_size += vol->mft_record_size; + if (mft_na->initialized_size > mft_na->data_size) + mft_na->data_size = mft_na->initialized_size; + ntfs_log_debug("Initializing mft record 0x%llx.\n", (long long)ll2); + err = ntfs_mft_record_format(vol, ll2); + if (err) { + ntfs_log_error("Failed to format mft record.\n"); + goto undo_data_init; + } + } + /* Update the mft data attribute record to reflect the new sizes. */ + ctx = ntfs_attr_get_search_ctx(mft_na->ni, NULL); + if (!ctx) { + ntfs_log_error("Failed to get search context.\n"); + goto undo_data_init; + } + if (ntfs_attr_lookup(mft_na->type, mft_na->name, mft_na->name_len, 0, + 0, NULL, 0, ctx)) { + ntfs_log_error("Failed to find first attribute extent of " + "mft data attribute.\n"); + ntfs_attr_put_search_ctx(ctx); + goto undo_data_init; + } + a = ctx->attr; + a->initialized_size = cpu_to_sle64(mft_na->initialized_size); + a->data_size = cpu_to_sle64(mft_na->data_size); + /* Ensure the changes make it to disk. */ + ntfs_inode_mark_dirty(ctx->ntfs_ino); + ntfs_attr_put_search_ctx(ctx); + ntfs_log_debug("Status of mft data after mft record initialization: " + "allocated_size 0x%llx, data_size 0x%llx, " + "initialized_size 0x%llx.\n", + (long long)mft_na->allocated_size, + (long long)mft_na->data_size, + (long long)mft_na->initialized_size); + /* Sanity checks. */ + if (mft_na->data_size > mft_na->allocated_size || + mft_na->initialized_size > mft_na->data_size) + NTFS_BUG("mft_na sanity checks failed"); + // BUG_ON(mft_na->initialized_size > mft_na->data_size); + // BUG_ON(mft_na->data_size > mft_na->allocated_size); + /* Sync MFT to minimize data loss if there won't be clean unmount. */ + if (ntfs_inode_sync(mft_na->ni)) { + ntfs_log_error("Failed to sync $MFT."); + goto undo_data_init; + } +mft_rec_already_initialized: + /* + * We now have allocated and initialized the mft record. Need to read + * it from disk and re-format it, preserving the sequence number if it + * is not zero as well as the update sequence number if it is not zero + * or -1 (0xffff). + */ + m = ntfs_malloc(vol->mft_record_size); + if (!m) + goto undo_mftbmp_alloc; + + if (ntfs_mft_record_read(vol, bit, m)) { + err = errno; + ntfs_log_error("Failed to read mft record.\n"); + free(m); + errno = err; + goto undo_mftbmp_alloc; + } + /* Sanity check that the mft record is really not in use. */ + if (ntfs_is_file_record(m->magic) && (m->flags & MFT_RECORD_IN_USE)) { + ntfs_log_error("Mft record 0x%llx was marked unused in " + "mft bitmap but is marked used itself. " + "Corrupt filesystem or library bug! " + "Run chkdsk immediately!\n", (long long)bit); + free(m); + errno = EIO; + goto undo_mftbmp_alloc; + } + seq_no = m->sequence_number; + usn = *(u16*)((u8*)m + le16_to_cpu(m->usa_ofs)); + if (ntfs_mft_record_layout(vol, bit, m)) { + err = errno; + ntfs_log_error("Failed to re-format mft record.\n"); + free(m); + errno = err; + goto undo_mftbmp_alloc; + } + if (le16_to_cpu(seq_no)) + m->sequence_number = seq_no; + seq_no = le16_to_cpu(usn); + if (seq_no && seq_no != 0xffff) + *(u16*)((u8*)m + le16_to_cpu(m->usa_ofs)) = usn; + /* Set the mft record itself in use. */ + m->flags |= MFT_RECORD_IN_USE; + /* Now need to open an ntfs inode for the mft record. */ + ni = ntfs_inode_allocate(vol); + if (!ni) { + err = errno; + ntfs_log_error("Failed to allocate buffer for inode.\n"); + free(m); + errno = err; + goto undo_mftbmp_alloc; + } + ni->mft_no = bit; + ni->mrec = m; + /* + * If we are allocating an extent mft record, make the opened inode an + * extent inode and attach it to the base inode. Also, set the base + * mft record reference in the extent inode. + */ + if (base_ni) { + ni->nr_extents = -1; + ni->base_ni = base_ni; + m->base_mft_record = MK_LE_MREF(base_ni->mft_no, + le16_to_cpu(base_ni->mrec->sequence_number)); + /* + * Attach the extent inode to the base inode, reallocating + * memory if needed. + */ + if (!(base_ni->nr_extents & 3)) { + ntfs_inode **extent_nis; + int i; + + i = (base_ni->nr_extents + 4) * sizeof(ntfs_inode *); + extent_nis = ntfs_malloc(i); + if (!extent_nis) { + free(m); + free(ni); + goto undo_mftbmp_alloc; + } + if (base_ni->extent_nis) { + memcpy(extent_nis, base_ni->extent_nis, + i - 4 * sizeof(ntfs_inode *)); + free(base_ni->extent_nis); + } + base_ni->extent_nis = extent_nis; + } + base_ni->extent_nis[base_ni->nr_extents++] = ni; + } + /* Make sure the allocated inode is written out to disk later. */ + ntfs_inode_mark_dirty(ni); + /* Initialize time, allocated and data size in ntfs_inode struct. */ + ni->data_size = ni->allocated_size = 0; + ni->flags = 0; + ni->creation_time = ni->last_data_change_time = + ni->last_mft_change_time = + ni->last_access_time = time(NULL); + /* Update the default mft allocation position if it was used. */ + if (!base_ni) + vol->mft_data_pos = bit + 1; + /* Return the opened, allocated inode of the allocated mft record. */ + ntfs_log_debug("Returning opened, allocated %sinode 0x%llx.\n", + base_ni ? "extent " : "", (long long)bit); + return ni; +undo_data_init: + mft_na->initialized_size = old_data_initialized; + mft_na->data_size = old_data_size; +undo_mftbmp_alloc: + err = errno; + if (ntfs_bitmap_clear_bit(mftbmp_na, bit)) + ntfs_log_error("Failed to clear bit in mft bitmap.%s\n", es); + errno = err; +err_out: + if (!errno) + errno = EIO; + return NULL; +} + +/** + * ntfs_mft_record_free - free an mft record on an ntfs volume + * @vol: volume on which to free the mft record + * @ni: open ntfs inode of the mft record to free + * + * Free the mft record of the open inode @ni on the mounted ntfs volume @vol. + * Note that this function calls ntfs_inode_close() internally and hence you + * cannot use the pointer @ni any more after this function returns success. + * + * On success return 0 and on error return -1 with errno set to the error code. + */ +int ntfs_mft_record_free(ntfs_volume *vol, ntfs_inode *ni) +{ + u64 mft_no; + int err; + u16 seq_no, old_seq_no; + + ntfs_log_trace("Entering for inode 0x%llx.\n", (long long) ni->mft_no); + + if (!vol || !vol->mftbmp_na || !ni) { + errno = EINVAL; + return -1; + } + + /* Cache the mft reference for later. */ + mft_no = ni->mft_no; + + /* Mark the mft record as not in use. */ + ni->mrec->flags &= ~MFT_RECORD_IN_USE; + + /* Increment the sequence number, skipping zero, if it is not zero. */ + old_seq_no = ni->mrec->sequence_number; + seq_no = le16_to_cpu(old_seq_no); + if (seq_no == 0xffff) + seq_no = 1; + else if (seq_no) + seq_no++; + ni->mrec->sequence_number = cpu_to_le16(seq_no); + + /* Set the inode dirty and write it out. */ + ntfs_inode_mark_dirty(ni); + if (ntfs_inode_sync(ni)) { + err = errno; + goto sync_rollback; + } + + /* Clear the bit in the $MFT/$BITMAP corresponding to this record. */ + if (ntfs_bitmap_clear_bit(vol->mftbmp_na, mft_no)) { + err = errno; + // FIXME: If ntfs_bitmap_clear_run() guarantees rollback on + // error, this could be changed to goto sync_rollback; + goto bitmap_rollback; + } + + /* Throw away the now freed inode. */ + if (!ntfs_inode_close(ni)) + return 0; + err = errno; + + /* Rollback what we did... */ +bitmap_rollback: + if (ntfs_bitmap_set_bit(vol->mftbmp_na, mft_no)) + ntfs_log_debug("Eeek! Rollback failed in ntfs_mft_record_free(). " + "Leaving inconsistent metadata!\n"); +sync_rollback: + ni->mrec->flags |= MFT_RECORD_IN_USE; + ni->mrec->sequence_number = old_seq_no; + ntfs_inode_mark_dirty(ni); + errno = err; + return -1; +} + +/** + * ntfs_mft_usn_dec - Decrement USN by one + * @mrec: pointer to an mft record + * + * On success return 0 and on error return -1 with errno set. + */ +int ntfs_mft_usn_dec(MFT_RECORD *mrec) +{ + u16 usn, *usnp; + + if (!mrec) { + errno = EINVAL; + return -1; + } + usnp = (u16 *)((char *)mrec + le16_to_cpu(mrec->usa_ofs)); + usn = le16_to_cpup(usnp); + if (usn-- <= 1) + usn = 0xfffe; + *usnp = cpu_to_le16(usn); + + return 0; +} + diff --git a/libntfs-3g/misc.c b/libntfs-3g/misc.c new file mode 100644 index 00000000..9e38347e --- /dev/null +++ b/libntfs-3g/misc.c @@ -0,0 +1,36 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_STDLIB_H +#include +#endif + +#include "misc.h" +#include "logging.h" + +/** + * ntfs_calloc + * + * Return a pointer to the allocated memory or NULL if the request fails. + */ +void *ntfs_calloc(size_t size) +{ + void *p; + + p = calloc(1, size); + if (!p) + ntfs_log_perror("Failed to calloc %lld bytes", (long long)size); + return p; +} + +void *ntfs_malloc(size_t size) +{ + void *p; + + p = malloc(size); + if (!p) + ntfs_log_perror("Failed to malloc %lld bytes", (long long)size); + return p; +} + diff --git a/libntfs-3g/mst.c b/libntfs-3g/mst.c new file mode 100644 index 00000000..4cd1f154 --- /dev/null +++ b/libntfs-3g/mst.c @@ -0,0 +1,222 @@ +/** + * mst.c - Multi sector fixup handling code. Originated from the Linux-NTFS project. + * + * Copyright (c) 2000-2004 Anton Altaparmakov + * Copyright (c) 2006 Szabolcs Szakacsits + * + * 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 + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program/include file 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 + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_ERRNO_H +#include +#endif + +#include "mst.h" +#include "logging.h" + +/** + * ntfs_mst_post_read_fixup - deprotect multi sector transfer protected data + * @b: pointer to the data to deprotect + * @size: size in bytes of @b + * + * Perform the necessary post read multi sector transfer fixups and detect the + * presence of incomplete multi sector transfers. - In that case, overwrite the + * magic of the ntfs record header being processed with "BAAD" (in memory only!) + * and abort processing. + * + * Return 0 on success and -1 on error, with errno set to the error code. The + * following error codes are defined: + * EINVAL Invalid arguments or invalid NTFS record in buffer @b. + * EIO Multi sector transfer error was detected. Magic of the NTFS + * record in @b will have been set to "BAAD". + */ +int ntfs_mst_post_read_fixup(NTFS_RECORD *b, const u32 size) +{ + u16 usa_ofs, usa_count, usn; + u16 *usa_pos, *data_pos; + + ntfs_log_trace("Entering\n"); + + /* Setup the variables. */ + usa_ofs = le16_to_cpu(b->usa_ofs); + /* Decrement usa_count to get number of fixups. */ + usa_count = le16_to_cpu(b->usa_count) - 1; + /* Size and alignment checks. */ + if (size & (NTFS_BLOCK_SIZE - 1) || usa_ofs & 1 || + (u32)(usa_ofs + (usa_count * 2)) > size || + (size >> NTFS_BLOCK_SIZE_BITS) != usa_count) { + errno = EINVAL; + return -1; + } + /* Position of usn in update sequence array. */ + usa_pos = (u16*)b + usa_ofs/sizeof(u16); + /* + * The update sequence number which has to be equal to each of the + * u16 values before they are fixed up. Note no need to care for + * endianness since we are comparing and moving data for on disk + * structures which means the data is consistent. - If it is + * consistency the wrong endianness it doesn't make any difference. + */ + usn = *usa_pos; + /* + * Position in protected data of first u16 that needs fixing up. + */ + data_pos = (u16*)b + NTFS_BLOCK_SIZE/sizeof(u16) - 1; + /* + * Check for incomplete multi sector transfer(s). + */ + while (usa_count--) { + if (*data_pos != usn) { + /* + * Incomplete multi sector transfer detected! )-: + * Set the magic to "BAAD" and return failure. + * Note that magic_BAAD is already converted to le32. + */ + b->magic = magic_BAAD; + errno = EIO; + return -1; + } + data_pos += NTFS_BLOCK_SIZE/sizeof(u16); + } + /* Re-setup the variables. */ + usa_count = le16_to_cpu(b->usa_count) - 1; + data_pos = (u16*)b + NTFS_BLOCK_SIZE/sizeof(u16) - 1; + /* Fixup all sectors. */ + while (usa_count--) { + /* + * Increment position in usa and restore original data from + * the usa into the data buffer. + */ + *data_pos = *(++usa_pos); + /* Increment position in data as well. */ + data_pos += NTFS_BLOCK_SIZE/sizeof(u16); + } + return 0; +} + +/** + * ntfs_mst_pre_write_fixup - apply multi sector transfer protection + * @b: pointer to the data to protect + * @size: size in bytes of @b + * + * Perform the necessary pre write multi sector transfer fixup on the data + * pointer to by @b of @size. + * + * Return 0 if fixups applied successfully or -1 if no fixups were performed + * due to errors. In that case errno i set to the error code (EINVAL). + * + * NOTE: We consider the absence / invalidity of an update sequence array to + * mean error. This means that you have to create a valid update sequence + * array header in the ntfs record before calling this function, otherwise it + * will fail (the header needs to contain the position of the update sequence + * array together with the number of elements in the array). You also need to + * initialise the update sequence number before calling this function + * otherwise a random word will be used (whatever was in the record at that + * position at that time). + */ +int ntfs_mst_pre_write_fixup(NTFS_RECORD *b, const u32 size) +{ + u16 usa_ofs, usa_count, usn; + u16 *usa_pos, *data_pos; + + ntfs_log_trace("Entering\n"); + + /* Sanity check + only fixup if it makes sense. */ + if (!b || ntfs_is_baad_record(b->magic) || + ntfs_is_hole_record(b->magic)) { + errno = EINVAL; + return -1; + } + /* Setup the variables. */ + usa_ofs = le16_to_cpu(b->usa_ofs); + /* Decrement usa_count to get number of fixups. */ + usa_count = le16_to_cpu(b->usa_count) - 1; + /* Size and alignment checks. */ + if (size & (NTFS_BLOCK_SIZE - 1) || usa_ofs & 1 || + (u32)(usa_ofs + (usa_count * 2)) > size || + (size >> NTFS_BLOCK_SIZE_BITS) != usa_count) { + errno = EINVAL; + return -1; + } + /* Position of usn in update sequence array. */ + usa_pos = (u16*)((u8*)b + usa_ofs); + /* + * Cyclically increment the update sequence number + * (skipping 0 and -1, i.e. 0xffff). + */ + usn = le16_to_cpup(usa_pos) + 1; + if (usn == 0xffff || !usn) + usn = 1; + usn = cpu_to_le16(usn); + *usa_pos = usn; + /* Position in data of first u16 that needs fixing up. */ + data_pos = (u16*)b + NTFS_BLOCK_SIZE/sizeof(u16) - 1; + /* Fixup all sectors. */ + while (usa_count--) { + /* + * Increment the position in the usa and save the + * original data from the data buffer into the usa. + */ + *(++usa_pos) = *data_pos; + /* Apply fixup to data. */ + *data_pos = usn; + /* Increment position in data as well. */ + data_pos += NTFS_BLOCK_SIZE/sizeof(u16); + } + return 0; +} + +/** + * ntfs_mst_post_write_fixup - deprotect multi sector transfer protected data + * @b: pointer to the data to deprotect + * + * Perform the necessary post write multi sector transfer fixup, not checking + * for any errors, because we assume we have just used + * ntfs_mst_pre_write_fixup(), thus the data will be fine or we would never + * have gotten here. + */ +void ntfs_mst_post_write_fixup(NTFS_RECORD *b) +{ + u16 *usa_pos, *data_pos; + + u16 usa_ofs = le16_to_cpu(b->usa_ofs); + u16 usa_count = le16_to_cpu(b->usa_count) - 1; + + ntfs_log_trace("Entering\n"); + + /* Position of usn in update sequence array. */ + usa_pos = (u16*)b + usa_ofs/sizeof(u16); + + /* Position in protected data of first u16 that needs fixing up. */ + data_pos = (u16*)b + NTFS_BLOCK_SIZE/sizeof(u16) - 1; + + /* Fixup all sectors. */ + while (usa_count--) { + /* + * Increment position in usa and restore original data from + * the usa into the data buffer. + */ + *data_pos = *(++usa_pos); + + /* Increment position in data as well. */ + data_pos += NTFS_BLOCK_SIZE/sizeof(u16); + } +} + diff --git a/libntfs-3g/runlist.c b/libntfs-3g/runlist.c new file mode 100644 index 00000000..e75aaec6 --- /dev/null +++ b/libntfs-3g/runlist.c @@ -0,0 +1,2136 @@ +/** + * runlist.c - Run list handling code. Originated from the Linux-NTFS project. + * + * Copyright (c) 2002-2005 Anton Altaparmakov + * Copyright (c) 2002-2005 Richard Russon + * Copyright (c) 2002-2006 Szabolcs Szakacsits + * Copyright (c) 2004 Yura Pakhuchiy + * + * 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 + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program/include file 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 + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_STDIO_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_ERRNO_H +#include +#endif + +#include "compat.h" +#include "types.h" +#include "volume.h" +#include "layout.h" +#include "debug.h" +#include "device.h" +#include "logging.h" +#include "misc.h" + +/** + * ntfs_rl_mm - runlist memmove + * @base: + * @dst: + * @src: + * @size: + * + * Description... + * + * Returns: + */ +static void ntfs_rl_mm(runlist_element *base, int dst, int src, int size) +{ + if ((dst != src) && (size > 0)) + memmove(base + dst, base + src, size * sizeof(*base)); +} + +/** + * ntfs_rl_mc - runlist memory copy + * @dstbase: + * @dst: + * @srcbase: + * @src: + * @size: + * + * Description... + * + * Returns: + */ +static void ntfs_rl_mc(runlist_element *dstbase, int dst, + runlist_element *srcbase, int src, int size) +{ + if (size > 0) + memcpy(dstbase + dst, srcbase + src, size * sizeof(*dstbase)); +} + +/** + * ntfs_rl_realloc - Reallocate memory for runlists + * @rl: original runlist + * @old_size: number of runlist elements in the original runlist @rl + * @new_size: number of runlist elements we need space for + * + * As the runlists grow, more memory will be required. To prevent large + * numbers of small reallocations of memory, this function returns a 4kiB block + * of memory. + * + * N.B. If the new allocation doesn't require a different number of 4kiB + * blocks in memory, the function will return the original pointer. + * + * On success, return a pointer to the newly allocated, or recycled, memory. + * On error, return NULL with errno set to the error code. + */ +static runlist_element *ntfs_rl_realloc(runlist_element *rl, int old_size, + int new_size) +{ + old_size = (old_size * sizeof(runlist_element) + 0xfff) & ~0xfff; + new_size = (new_size * sizeof(runlist_element) + 0xfff) & ~0xfff; + if (old_size == new_size) + return rl; + return realloc(rl, new_size); +} + +/** + * ntfs_rl_are_mergeable - test if two runlists can be joined together + * @dst: original runlist + * @src: new runlist to test for mergeability with @dst + * + * Test if two runlists can be joined together. For this, their VCNs and LCNs + * must be adjacent. + * + * Return: TRUE Success, the runlists can be merged. + * FALSE Failure, the runlists cannot be merged. + */ +static BOOL ntfs_rl_are_mergeable(runlist_element *dst, runlist_element *src) +{ + if (!dst || !src) { + ntfs_log_debug("Eeek. ntfs_rl_are_mergeable() invoked with NULL " + "pointer!\n"); + return FALSE; + } + + /* We can merge unmapped regions even if they are misaligned. */ + if ((dst->lcn == LCN_RL_NOT_MAPPED) && (src->lcn == LCN_RL_NOT_MAPPED)) + return TRUE; + /* If the runs are misaligned, we cannot merge them. */ + if ((dst->vcn + dst->length) != src->vcn) + return FALSE; + /* If both runs are non-sparse and contiguous, we can merge them. */ + if ((dst->lcn >= 0) && (src->lcn >= 0) && + ((dst->lcn + dst->length) == src->lcn)) + return TRUE; + /* If we are merging two holes, we can merge them. */ + if ((dst->lcn == LCN_HOLE) && (src->lcn == LCN_HOLE)) + return TRUE; + /* Cannot merge. */ + return FALSE; +} + +/** + * __ntfs_rl_merge - merge two runlists without testing if they can be merged + * @dst: original, destination runlist + * @src: new runlist to merge with @dst + * + * Merge the two runlists, writing into the destination runlist @dst. The + * caller must make sure the runlists can be merged or this will corrupt the + * destination runlist. + */ +static void __ntfs_rl_merge(runlist_element *dst, runlist_element *src) +{ + dst->length += src->length; +} + +/** + * ntfs_rl_append - append a runlist after a given element + * @dst: original runlist to be worked on + * @dsize: number of elements in @dst (including end marker) + * @src: runlist to be inserted into @dst + * @ssize: number of elements in @src (excluding end marker) + * @loc: append the new runlist @src after this element in @dst + * + * Append the runlist @src after element @loc in @dst. Merge the right end of + * the new runlist, if necessary. Adjust the size of the hole before the + * appended runlist. + * + * On success, return a pointer to the new, combined, runlist. Note, both + * runlists @dst and @src are deallocated before returning so you cannot use + * the pointers for anything any more. (Strictly speaking the returned runlist + * may be the same as @dst but this is irrelevant.) + * + * On error, return NULL, with errno set to the error code. Both runlists are + * left unmodified. + */ +static runlist_element *ntfs_rl_append(runlist_element *dst, int dsize, + runlist_element *src, int ssize, int loc) +{ + BOOL right = FALSE; /* Right end of @src needs merging */ + int marker; /* End of the inserted runs */ + + if (!dst || !src) { + ntfs_log_debug("Eeek. ntfs_rl_append() invoked with NULL " + "pointer!\n"); + errno = EINVAL; + return NULL; + } + + /* First, check if the right hand end needs merging. */ + if ((loc + 1) < dsize) + right = ntfs_rl_are_mergeable(src + ssize - 1, dst + loc + 1); + + /* Space required: @dst size + @src size, less one if we merged. */ + dst = ntfs_rl_realloc(dst, dsize, dsize + ssize - right); + if (!dst) + return NULL; + /* + * We are guaranteed to succeed from here so can start modifying the + * original runlists. + */ + + /* First, merge the right hand end, if necessary. */ + if (right) + __ntfs_rl_merge(src + ssize - 1, dst + loc + 1); + + /* marker - First run after the @src runs that have been inserted */ + marker = loc + ssize + 1; + + /* Move the tail of @dst out of the way, then copy in @src. */ + ntfs_rl_mm(dst, marker, loc + 1 + right, dsize - loc - 1 - right); + ntfs_rl_mc(dst, loc + 1, src, 0, ssize); + + /* Adjust the size of the preceding hole. */ + dst[loc].length = dst[loc + 1].vcn - dst[loc].vcn; + + /* We may have changed the length of the file, so fix the end marker */ + if (dst[marker].lcn == LCN_ENOENT) + dst[marker].vcn = dst[marker-1].vcn + dst[marker-1].length; + + return dst; +} + +/** + * ntfs_rl_insert - insert a runlist into another + * @dst: original runlist to be worked on + * @dsize: number of elements in @dst (including end marker) + * @src: new runlist to be inserted + * @ssize: number of elements in @src (excluding end marker) + * @loc: insert the new runlist @src before this element in @dst + * + * Insert the runlist @src before element @loc in the runlist @dst. Merge the + * left end of the new runlist, if necessary. Adjust the size of the hole + * after the inserted runlist. + * + * On success, return a pointer to the new, combined, runlist. Note, both + * runlists @dst and @src are deallocated before returning so you cannot use + * the pointers for anything any more. (Strictly speaking the returned runlist + * may be the same as @dst but this is irrelevant.) + * + * On error, return NULL, with errno set to the error code. Both runlists are + * left unmodified. + */ +static runlist_element *ntfs_rl_insert(runlist_element *dst, int dsize, + runlist_element *src, int ssize, int loc) +{ + BOOL left = FALSE; /* Left end of @src needs merging */ + BOOL disc = FALSE; /* Discontinuity between @dst and @src */ + int marker; /* End of the inserted runs */ + + if (!dst || !src) { + ntfs_log_debug("Eeek. ntfs_rl_insert() invoked with NULL " + "pointer!\n"); + errno = EINVAL; + return NULL; + } + + /* disc => Discontinuity between the end of @dst and the start of @src. + * This means we might need to insert a "notmapped" run. + */ + if (loc == 0) + disc = (src[0].vcn > 0); + else { + s64 merged_length; + + left = ntfs_rl_are_mergeable(dst + loc - 1, src); + + merged_length = dst[loc - 1].length; + if (left) + merged_length += src->length; + + disc = (src[0].vcn > dst[loc - 1].vcn + merged_length); + } + + /* Space required: @dst size + @src size, less one if we merged, plus + * one if there was a discontinuity. + */ + dst = ntfs_rl_realloc(dst, dsize, dsize + ssize - left + disc); + if (!dst) + return NULL; + /* + * We are guaranteed to succeed from here so can start modifying the + * original runlist. + */ + + if (left) + __ntfs_rl_merge(dst + loc - 1, src); + + /* + * marker - First run after the @src runs that have been inserted + * Nominally: marker = @loc + @ssize (location + number of runs in @src) + * If "left", then the first run in @src has been merged with one in @dst. + * If "disc", then @dst and @src don't meet and we need an extra run to fill the gap. + */ + marker = loc + ssize - left + disc; + + /* Move the tail of @dst out of the way, then copy in @src. */ + ntfs_rl_mm(dst, marker, loc, dsize - loc); + ntfs_rl_mc(dst, loc + disc, src, left, ssize - left); + + /* Adjust the VCN of the first run after the insertion ... */ + dst[marker].vcn = dst[marker - 1].vcn + dst[marker - 1].length; + /* ... and the length. */ + if (dst[marker].lcn == LCN_HOLE || dst[marker].lcn == LCN_RL_NOT_MAPPED) + dst[marker].length = dst[marker + 1].vcn - dst[marker].vcn; + + /* Writing beyond the end of the file and there's a discontinuity. */ + if (disc) { + if (loc > 0) { + dst[loc].vcn = dst[loc - 1].vcn + dst[loc - 1].length; + dst[loc].length = dst[loc + 1].vcn - dst[loc].vcn; + } else { + dst[loc].vcn = 0; + dst[loc].length = dst[loc + 1].vcn; + } + dst[loc].lcn = LCN_RL_NOT_MAPPED; + } + return dst; +} + +/** + * ntfs_rl_replace - overwrite a runlist element with another runlist + * @dst: original runlist to be worked on + * @dsize: number of elements in @dst (including end marker) + * @src: new runlist to be inserted + * @ssize: number of elements in @src (excluding end marker) + * @loc: index in runlist @dst to overwrite with @src + * + * Replace the runlist element @dst at @loc with @src. Merge the left and + * right ends of the inserted runlist, if necessary. + * + * On success, return a pointer to the new, combined, runlist. Note, both + * runlists @dst and @src are deallocated before returning so you cannot use + * the pointers for anything any more. (Strictly speaking the returned runlist + * may be the same as @dst but this is irrelevant.) + * + * On error, return NULL, with errno set to the error code. Both runlists are + * left unmodified. + */ +static runlist_element *ntfs_rl_replace(runlist_element *dst, int dsize, + runlist_element *src, int ssize, + int loc) +{ + signed delta; + BOOL left = FALSE; /* Left end of @src needs merging */ + BOOL right = FALSE; /* Right end of @src needs merging */ + int tail; /* Start of tail of @dst */ + int marker; /* End of the inserted runs */ + + if (!dst || !src) { + ntfs_log_debug("Eeek. ntfs_rl_replace() invoked with NULL " + "pointer!\n"); + errno = EINVAL; + return NULL; + } + + /* First, see if the left and right ends need merging. */ + if ((loc + 1) < dsize) + right = ntfs_rl_are_mergeable(src + ssize - 1, dst + loc + 1); + if (loc > 0) + left = ntfs_rl_are_mergeable(dst + loc - 1, src); + + /* Allocate some space. We'll need less if the left, right, or both + * ends get merged. The -1 accounts for the run being replaced. + */ + delta = ssize - 1 - left - right; + if (delta > 0) { + dst = ntfs_rl_realloc(dst, dsize, dsize + delta); + if (!dst) + return NULL; + } + /* + * We are guaranteed to succeed from here so can start modifying the + * original runlists. + */ + + /* First, merge the left and right ends, if necessary. */ + if (right) + __ntfs_rl_merge(src + ssize - 1, dst + loc + 1); + if (left) + __ntfs_rl_merge(dst + loc - 1, src); + + /* + * tail - Offset of the tail of @dst + * Nominally: @tail = @loc + 1 (location, skipping the replaced run) + * If "right", then one of @dst's runs is already merged into @src. + */ + tail = loc + right + 1; + + /* + * marker - First run after the @src runs that have been inserted + * Nominally: @marker = @loc + @ssize (location + number of runs in @src) + * If "left", then the first run in @src has been merged with one in @dst. + */ + marker = loc + ssize - left; + + /* Move the tail of @dst out of the way, then copy in @src. */ + ntfs_rl_mm(dst, marker, tail, dsize - tail); + ntfs_rl_mc(dst, loc, src, left, ssize - left); + + /* We may have changed the length of the file, so fix the end marker */ + if (((dsize - tail) > 0) && (dst[marker].lcn == LCN_ENOENT)) + dst[marker].vcn = dst[marker - 1].vcn + dst[marker - 1].length; + + return dst; +} + +/** + * ntfs_rl_split - insert a runlist into the centre of a hole + * @dst: original runlist to be worked on + * @dsize: number of elements in @dst (including end marker) + * @src: new runlist to be inserted + * @ssize: number of elements in @src (excluding end marker) + * @loc: index in runlist @dst at which to split and insert @src + * + * Split the runlist @dst at @loc into two and insert @new in between the two + * fragments. No merging of runlists is necessary. Adjust the size of the + * holes either side. + * + * On success, return a pointer to the new, combined, runlist. Note, both + * runlists @dst and @src are deallocated before returning so you cannot use + * the pointers for anything any more. (Strictly speaking the returned runlist + * may be the same as @dst but this is irrelevant.) + * + * On error, return NULL, with errno set to the error code. Both runlists are + * left unmodified. + */ +static runlist_element *ntfs_rl_split(runlist_element *dst, int dsize, + runlist_element *src, int ssize, int loc) +{ + if (!dst || !src) { + ntfs_log_debug("Eeek. ntfs_rl_split() invoked with NULL pointer!\n"); + errno = EINVAL; + return NULL; + } + + /* Space required: @dst size + @src size + one new hole. */ + dst = ntfs_rl_realloc(dst, dsize, dsize + ssize + 1); + if (!dst) + return dst; + /* + * We are guaranteed to succeed from here so can start modifying the + * original runlists. + */ + + /* Move the tail of @dst out of the way, then copy in @src. */ + ntfs_rl_mm(dst, loc + 1 + ssize, loc, dsize - loc); + ntfs_rl_mc(dst, loc + 1, src, 0, ssize); + + /* Adjust the size of the holes either size of @src. */ + dst[loc].length = dst[loc+1].vcn - dst[loc].vcn; + dst[loc+ssize+1].vcn = dst[loc+ssize].vcn + dst[loc+ssize].length; + dst[loc+ssize+1].length = dst[loc+ssize+2].vcn - dst[loc+ssize+1].vcn; + + return dst; +} + + +/** + * ntfs_runlists_merge - merge two runlists into one + * @drl: original runlist to be worked on + * @srl: new runlist to be merged into @drl + * + * First we sanity check the two runlists @srl and @drl to make sure that they + * are sensible and can be merged. The runlist @srl must be either after the + * runlist @drl or completely within a hole (or unmapped region) in @drl. + * + * Merging of runlists is necessary in two cases: + * 1. When attribute lists are used and a further extent is being mapped. + * 2. When new clusters are allocated to fill a hole or extend a file. + * + * There are four possible ways @srl can be merged. It can: + * - be inserted at the beginning of a hole, + * - split the hole in two and be inserted between the two fragments, + * - be appended at the end of a hole, or it can + * - replace the whole hole. + * It can also be appended to the end of the runlist, which is just a variant + * of the insert case. + * + * On success, return a pointer to the new, combined, runlist. Note, both + * runlists @drl and @srl are deallocated before returning so you cannot use + * the pointers for anything any more. (Strictly speaking the returned runlist + * may be the same as @dst but this is irrelevant.) + * + * On error, return NULL, with errno set to the error code. Both runlists are + * left unmodified. The following error codes are defined: + * ENOMEM Not enough memory to allocate runlist array. + * EINVAL Invalid parameters were passed in. + * ERANGE The runlists overlap and cannot be merged. + */ +runlist_element *ntfs_runlists_merge(runlist_element *drl, + runlist_element *srl) +{ + int di, si; /* Current index into @[ds]rl. */ + int sstart; /* First index with lcn > LCN_RL_NOT_MAPPED. */ + int dins; /* Index into @drl at which to insert @srl. */ + int dend, send; /* Last index into @[ds]rl. */ + int dfinal, sfinal; /* The last index into @[ds]rl with + lcn >= LCN_HOLE. */ + int marker = 0; + VCN marker_vcn = 0; + + ntfs_log_debug("dst:\n"); + ntfs_debug_runlist_dump(drl); + ntfs_log_debug("src:\n"); + ntfs_debug_runlist_dump(srl); + + /* Check for silly calling... */ + if (!srl) + return drl; + + /* Check for the case where the first mapping is being done now. */ + if (!drl) { + drl = srl; + /* Complete the source runlist if necessary. */ + if (drl[0].vcn) { + /* Scan to the end of the source runlist. */ + for (dend = 0; drl[dend].length; dend++) + ; + dend++; + drl = ntfs_rl_realloc(drl, dend, dend + 1); + if (!drl) + return drl; + /* Insert start element at the front of the runlist. */ + ntfs_rl_mm(drl, 1, 0, dend); + drl[0].vcn = 0; + drl[0].lcn = LCN_RL_NOT_MAPPED; + drl[0].length = drl[1].vcn; + } + goto finished; + } + + si = di = 0; + + /* Skip any unmapped start element(s) in the source runlist. */ + while (srl[si].length && srl[si].lcn < (LCN)LCN_HOLE) + si++; + + /* Can't have an entirely unmapped source runlist. */ + if (!srl[si].length) { + ntfs_log_debug("Eeek! ntfs_runlists_merge() received entirely " + "unmapped source runlist.\n"); + errno = EINVAL; + return NULL; + } + + /* Record the starting points. */ + sstart = si; + + /* + * Skip forward in @drl until we reach the position where @srl needs to + * be inserted. If we reach the end of @drl, @srl just needs to be + * appended to @drl. + */ + for (; drl[di].length; di++) { + if (drl[di].vcn + drl[di].length > srl[sstart].vcn) + break; + } + dins = di; + + /* Sanity check for illegal overlaps. */ + if ((drl[di].vcn == srl[si].vcn) && (drl[di].lcn >= 0) && + (srl[si].lcn >= 0)) { + ntfs_log_debug("Run lists overlap. Cannot merge!\n"); + errno = ERANGE; + return NULL; + } + + /* Scan to the end of both runlists in order to know their sizes. */ + for (send = si; srl[send].length; send++) + ; + for (dend = di; drl[dend].length; dend++) + ; + + if (srl[send].lcn == (LCN)LCN_ENOENT) + marker_vcn = srl[marker = send].vcn; + + /* Scan to the last element with lcn >= LCN_HOLE. */ + for (sfinal = send; sfinal >= 0 && srl[sfinal].lcn < LCN_HOLE; sfinal--) + ; + for (dfinal = dend; dfinal >= 0 && drl[dfinal].lcn < LCN_HOLE; dfinal--) + ; + + { + BOOL start; + BOOL finish; + int ds = dend + 1; /* Number of elements in drl & srl */ + int ss = sfinal - sstart + 1; + + start = ((drl[dins].lcn < LCN_RL_NOT_MAPPED) || /* End of file */ + (drl[dins].vcn == srl[sstart].vcn)); /* Start of hole */ + finish = ((drl[dins].lcn >= LCN_RL_NOT_MAPPED) && /* End of file */ + ((drl[dins].vcn + drl[dins].length) <= /* End of hole */ + (srl[send - 1].vcn + srl[send - 1].length))); + + /* Or we'll lose an end marker */ + if (finish && !drl[dins].length) + ss++; + if (marker && (drl[dins].vcn + drl[dins].length > srl[send - 1].vcn)) + finish = FALSE; + + ntfs_log_debug("dfinal = %i, dend = %i\n", dfinal, dend); + ntfs_log_debug("sstart = %i, sfinal = %i, send = %i\n", sstart, sfinal, send); + ntfs_log_debug("start = %i, finish = %i\n", start, finish); + ntfs_log_debug("ds = %i, ss = %i, dins = %i\n", ds, ss, dins); + + if (start) { + if (finish) + drl = ntfs_rl_replace(drl, ds, srl + sstart, ss, dins); + else + drl = ntfs_rl_insert(drl, ds, srl + sstart, ss, dins); + } else { + if (finish) + drl = ntfs_rl_append(drl, ds, srl + sstart, ss, dins); + else + drl = ntfs_rl_split(drl, ds, srl + sstart, ss, dins); + } + if (!drl) { + ntfs_log_perror("Merge failed"); + return drl; + } + free(srl); + if (marker) { + ntfs_log_debug("Triggering marker code.\n"); + for (ds = dend; drl[ds].length; ds++) + ; + /* We only need to care if @srl ended after @drl. */ + if (drl[ds].vcn <= marker_vcn) { + int slots = 0; + + if (drl[ds].vcn == marker_vcn) { + ntfs_log_debug("Old marker = %lli, replacing with " + "LCN_ENOENT.\n", + (long long)drl[ds].lcn); + drl[ds].lcn = (LCN)LCN_ENOENT; + goto finished; + } + /* + * We need to create an unmapped runlist element in + * @drl or extend an existing one before adding the + * ENOENT terminator. + */ + if (drl[ds].lcn == (LCN)LCN_ENOENT) { + ds--; + slots = 1; + } + if (drl[ds].lcn != (LCN)LCN_RL_NOT_MAPPED) { + /* Add an unmapped runlist element. */ + if (!slots) { + /* FIXME/TODO: We need to have the + * extra memory already! (AIA) + */ + drl = ntfs_rl_realloc(drl, ds, ds + 2); + if (!drl) + goto critical_error; + slots = 2; + } + ds++; + /* Need to set vcn if it isn't set already. */ + if (slots != 1) + drl[ds].vcn = drl[ds - 1].vcn + + drl[ds - 1].length; + drl[ds].lcn = (LCN)LCN_RL_NOT_MAPPED; + /* We now used up a slot. */ + slots--; + } + drl[ds].length = marker_vcn - drl[ds].vcn; + /* Finally add the ENOENT terminator. */ + ds++; + if (!slots) { + /* FIXME/TODO: We need to have the extra + * memory already! (AIA) + */ + drl = ntfs_rl_realloc(drl, ds, ds + 1); + if (!drl) + goto critical_error; + } + drl[ds].vcn = marker_vcn; + drl[ds].lcn = (LCN)LCN_ENOENT; + drl[ds].length = (s64)0; + } + } + } + +finished: + /* The merge was completed successfully. */ + ntfs_log_debug("Merged runlist:\n"); + ntfs_debug_runlist_dump(drl); + return drl; + +critical_error: + /* Critical error! We cannot afford to fail here. */ + ntfs_log_perror("libntfs: Critical error"); + ntfs_log_debug("Forcing segmentation fault!\n"); + marker_vcn = ((runlist*)NULL)->lcn; + return drl; +} + +/** + * ntfs_mapping_pairs_decompress - convert mapping pairs array to runlist + * @vol: ntfs volume on which the attribute resides + * @attr: attribute record whose mapping pairs array to decompress + * @old_rl: optional runlist in which to insert @attr's runlist + * + * Decompress the attribute @attr's mapping pairs array into a runlist. On + * success, return the decompressed runlist. + * + * If @old_rl is not NULL, decompressed runlist is inserted into the + * appropriate place in @old_rl and the resultant, combined runlist is + * returned. The original @old_rl is deallocated. + * + * On error, return NULL with errno set to the error code. @old_rl is left + * unmodified in that case. + * + * The following error codes are defined: + * ENOMEM Not enough memory to allocate runlist array. + * EIO Corrupt runlist. + * EINVAL Invalid parameters were passed in. + * ERANGE The two runlists overlap. + * + * FIXME: For now we take the conceptionally simplest approach of creating the + * new runlist disregarding the already existing one and then splicing the + * two into one, if that is possible (we check for overlap and discard the new + * runlist if overlap present before returning NULL, with errno = ERANGE). + */ +runlist_element *ntfs_mapping_pairs_decompress(const ntfs_volume *vol, + const ATTR_RECORD *attr, runlist_element *old_rl) +{ + VCN vcn; /* Current vcn. */ + LCN lcn; /* Current lcn. */ + s64 deltaxcn; /* Change in [vl]cn. */ + runlist_element *rl; /* The output runlist. */ + const u8 *buf; /* Current position in mapping pairs array. */ + const u8 *attr_end; /* End of attribute. */ + int err, rlsize; /* Size of runlist buffer. */ + u16 rlpos; /* Current runlist position in units of + runlist_elements. */ + u8 b; /* Current byte offset in buf. */ + + ntfs_log_trace("Entering for attr 0x%x.\n", + (unsigned)le32_to_cpu(attr->type)); + /* Make sure attr exists and is non-resident. */ + if (!attr || !attr->non_resident || + sle64_to_cpu(attr->lowest_vcn) < (VCN)0) { + errno = EINVAL; + return NULL; + } + /* Start at vcn = lowest_vcn and lcn 0. */ + vcn = sle64_to_cpu(attr->lowest_vcn); + lcn = 0; + /* Get start of the mapping pairs array. */ + buf = (const u8*)attr + le16_to_cpu(attr->mapping_pairs_offset); + attr_end = (const u8*)attr + le32_to_cpu(attr->length); + if (buf < (const u8*)attr || buf > attr_end) { + ntfs_log_debug("Corrupt attribute.\n"); + errno = EIO; + return NULL; + } + /* Current position in runlist array. */ + rlpos = 0; + /* Allocate first 4kiB block and set current runlist size to 4kiB. */ + rlsize = 0x1000; + rl = ntfs_malloc(rlsize); + if (!rl) + return NULL; + /* Insert unmapped starting element if necessary. */ + if (vcn) { + rl->vcn = (VCN)0; + rl->lcn = (LCN)LCN_RL_NOT_MAPPED; + rl->length = vcn; + rlpos++; + } + while (buf < attr_end && *buf) { + /* + * Allocate more memory if needed, including space for the + * not-mapped and terminator elements. + */ + if ((int)((rlpos + 3) * sizeof(*old_rl)) > rlsize) { + runlist_element *rl2; + + rlsize += 0x1000; + rl2 = realloc(rl, rlsize); + if (!rl2) { + int eo = errno; + free(rl); + errno = eo; + return NULL; + } + rl = rl2; + } + /* Enter the current vcn into the current runlist element. */ + rl[rlpos].vcn = vcn; + /* + * Get the change in vcn, i.e. the run length in clusters. + * Doing it this way ensures that we signextend negative values. + * A negative run length doesn't make any sense, but hey, I + * didn't make up the NTFS specs and Windows NT4 treats the run + * length as a signed value so that's how it is... + */ + b = *buf & 0xf; + if (b) { + if (buf + b > attr_end) + goto io_error; + for (deltaxcn = (s8)buf[b--]; b; b--) + deltaxcn = (deltaxcn << 8) + buf[b]; + } else { /* The length entry is compulsory. */ + ntfs_log_debug("Missing length entry in mapping pairs " + "array.\n"); + deltaxcn = (s64)-1; + } + /* + * Assume a negative length to indicate data corruption and + * hence clean-up and return NULL. + */ + if (deltaxcn < 0) { + ntfs_log_debug("Invalid length in mapping pairs array.\n"); + goto err_out; + } + /* + * Enter the current run length into the current runlist + * element. + */ + rl[rlpos].length = deltaxcn; + /* Increment the current vcn by the current run length. */ + vcn += deltaxcn; + /* + * There might be no lcn change at all, as is the case for + * sparse clusters on NTFS 3.0+, in which case we set the lcn + * to LCN_HOLE. + */ + if (!(*buf & 0xf0)) + rl[rlpos].lcn = (LCN)LCN_HOLE; + else { + /* Get the lcn change which really can be negative. */ + u8 b2 = *buf & 0xf; + b = b2 + ((*buf >> 4) & 0xf); + if (buf + b > attr_end) + goto io_error; + for (deltaxcn = (s8)buf[b--]; b > b2; b--) + deltaxcn = (deltaxcn << 8) + buf[b]; + /* Change the current lcn to it's new value. */ + lcn += deltaxcn; +#ifdef DEBUG + /* + * On NTFS 1.2-, apparently can have lcn == -1 to + * indicate a hole. But we haven't verified ourselves + * whether it is really the lcn or the deltaxcn that is + * -1. So if either is found give us a message so we + * can investigate it further! + */ + if (vol->major_ver < 3) { + if (deltaxcn == (LCN)-1) + ntfs_log_debug("lcn delta == -1\n"); + if (lcn == (LCN)-1) + ntfs_log_debug("lcn == -1\n"); + } +#endif + /* Check lcn is not below -1. */ + if (lcn < (LCN)-1) { + ntfs_log_debug("Invalid LCN < -1 in mapping pairs " + "array.\n"); + goto err_out; + } + /* Enter the current lcn into the runlist element. */ + rl[rlpos].lcn = lcn; + } + /* Get to the next runlist element. */ + rlpos++; + /* Increment the buffer position to the next mapping pair. */ + buf += (*buf & 0xf) + ((*buf >> 4) & 0xf) + 1; + } + if (buf >= attr_end) + goto io_error; + /* + * If there is a highest_vcn specified, it must be equal to the final + * vcn in the runlist - 1, or something has gone badly wrong. + */ + deltaxcn = sle64_to_cpu(attr->highest_vcn); + if (deltaxcn && vcn - 1 != deltaxcn) { +mpa_err: + ntfs_log_debug("Corrupt mapping pairs array in non-resident " + "attribute.\n"); + goto err_out; + } + /* Setup not mapped runlist element if this is the base extent. */ + if (!attr->lowest_vcn) { + VCN max_cluster; + + max_cluster = ((sle64_to_cpu(attr->allocated_size) + + 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) { + /* + * 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. + */ + 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; + } + } + rl[rlpos].lcn = (LCN)LCN_ENOENT; + } else /* Not the base extent. There may be more extents to follow. */ + rl[rlpos].lcn = (LCN)LCN_RL_NOT_MAPPED; + + /* Setup terminating runlist element. */ + rl[rlpos].vcn = vcn; + rl[rlpos].length = (s64)0; + /* If no existing runlist was specified, we are done. */ + if (!old_rl) { + ntfs_log_debug("Mapping pairs array successfully decompressed:\n"); + ntfs_debug_runlist_dump(rl); + return rl; + } + /* Now combine the new and old runlists checking for overlaps. */ + old_rl = ntfs_runlists_merge(old_rl, rl); + if (old_rl) + return old_rl; + err = errno; + free(rl); + ntfs_log_debug("Failed to merge runlists.\n"); + errno = err; + return NULL; +io_error: + ntfs_log_debug("Corrupt attribute.\n"); +err_out: + free(rl); + errno = EIO; + return NULL; +} + +/** + * ntfs_rl_vcn_to_lcn - convert a vcn into a lcn given a runlist + * @rl: runlist to use for conversion + * @vcn: vcn to convert + * + * Convert the virtual cluster number @vcn of an attribute into a logical + * cluster number (lcn) of a device using the runlist @rl to map vcns to their + * corresponding lcns. + * + * Since lcns must be >= 0, we use negative return values with special meaning: + * + * Return value Meaning / Description + * ================================================== + * -1 = LCN_HOLE Hole / not allocated on disk. + * -2 = LCN_RL_NOT_MAPPED This is part of the runlist which has not been + * inserted into the runlist yet. + * -3 = LCN_ENOENT There is no such vcn in the attribute. + * -4 = LCN_EINVAL Input parameter error. + */ +LCN ntfs_rl_vcn_to_lcn(const runlist_element *rl, const VCN vcn) +{ + int i; + + if (vcn < (VCN)0) + return (LCN)LCN_EINVAL; + /* + * If rl is NULL, assume that we have found an unmapped runlist. The + * caller can then attempt to map it and fail appropriately if + * necessary. + */ + if (!rl) + return (LCN)LCN_RL_NOT_MAPPED; + + /* Catch out of lower bounds vcn. */ + if (vcn < rl[0].vcn) + return (LCN)LCN_ENOENT; + + for (i = 0; rl[i].length; i++) { + if (vcn < rl[i+1].vcn) { + if (rl[i].lcn >= (LCN)0) + return rl[i].lcn + (vcn - rl[i].vcn); + return rl[i].lcn; + } + } + /* + * The terminator element is setup to the correct value, i.e. one of + * LCN_HOLE, LCN_RL_NOT_MAPPED, or LCN_ENOENT. + */ + if (rl[i].lcn < (LCN)0) + return rl[i].lcn; + /* Just in case... We could replace this with BUG() some day. */ + return (LCN)LCN_ENOENT; +} + +/** + * ntfs_rl_pread - gather read from disk + * @vol: ntfs volume to read from + * @rl: runlist specifying where to read the data from + * @pos: byte position within runlist @rl at which to begin the read + * @count: number of bytes to read + * @b: data buffer into which to read from disk + * + * This function will read @count bytes from the volume @vol to the data buffer + * @b gathering the data as specified by the runlist @rl. The read begins at + * offset @pos into the runlist @rl. + * + * On success, return the number of successfully read bytes. If this number is + * lower than @count this means that the read reached end of file or that an + * error was encountered during the read so that the read is partial. 0 means + * nothing was read (also return 0 when @count is 0). + * + * On error and nothing has been read, return -1 with errno set appropriately + * to the return code of ntfs_pread(), or to EINVAL in case of invalid + * arguments. + * + * NOTE: If we encounter EOF while reading we return EIO because we assume that + * the run list must point to valid locations within the ntfs volume. + */ +s64 ntfs_rl_pread(const ntfs_volume *vol, const runlist_element *rl, + const s64 pos, s64 count, void *b) +{ + s64 bytes_read, to_read, ofs, total; + int err = EIO; + + if (!vol || !rl || pos < 0 || count < 0) { + errno = EINVAL; + ntfs_log_perror("Failed to read runlist [vol: %p rl: %p " + "pos: %lld count: %lld]", vol, rl, pos, count); + return -1; + } + if (!count) + return count; + /* Seek in @rl to the run containing @pos. */ + for (ofs = 0; rl->length && (ofs + (rl->length << + vol->cluster_size_bits) <= pos); rl++) + ofs += (rl->length << vol->cluster_size_bits); + /* Offset in the run at which to begin reading. */ + ofs = pos - ofs; + for (total = 0LL; count; rl++, ofs = 0) { + if (!rl->length) + goto rl_err_out; + if (rl->lcn < (LCN)0) { + if (rl->lcn != (LCN)LCN_HOLE) + goto rl_err_out; + /* It is a hole. Just fill buffer @b with zeroes. */ + to_read = min(count, (rl->length << + vol->cluster_size_bits) - ofs); + memset(b, 0, to_read); + /* Update counters and proceed with next run. */ + total += to_read; + count -= to_read; + b = (u8*)b + to_read; + continue; + } + /* It is a real lcn, read it from the volume. */ + to_read = min(count, (rl->length << vol->cluster_size_bits) - + ofs); +retry: + bytes_read = ntfs_pread(vol->dev, (rl->lcn << + vol->cluster_size_bits) + ofs, to_read, b); + /* If everything ok, update progress counters and continue. */ + if (bytes_read > 0) { + total += bytes_read; + count -= bytes_read; + b = (u8*)b + bytes_read; + continue; + } + /* If the syscall was interrupted, try again. */ + if (bytes_read == (s64)-1 && errno == EINTR) + goto retry; + if (bytes_read == (s64)-1) + err = errno; + goto rl_err_out; + } + /* Finally, return the number of bytes read. */ + return total; +rl_err_out: + if (total) + return total; + errno = err; + return -1; +} + +/** + * ntfs_rl_pwrite - scatter write to disk + * @vol: ntfs volume to write to + * @rl: runlist specifying where to write the data to + * @pos: byte position within runlist @rl at which to begin the write + * @count: number of bytes to write + * @b: data buffer to write to disk + * + * This function will write @count bytes from data buffer @b to the volume @vol + * scattering the data as specified by the runlist @rl. The write begins at + * offset @pos into the runlist @rl. + * + * On success, return the number of successfully written bytes. If this number + * is lower than @count this means that the write has been interrupted in + * flight or that an error was encountered during the write so that the write + * is partial. 0 means nothing was written (also return 0 when @count is 0). + * + * On error and nothing has been written, return -1 with errno set + * appropriately to the return code of ntfs_pwrite(), or to to EINVAL in case + * of invalid arguments. + */ +s64 ntfs_rl_pwrite(const ntfs_volume *vol, const runlist_element *rl, + const s64 pos, s64 count, void *b) +{ + s64 written, to_write, ofs, total = 0; + int err = EIO; + + if (!vol || !rl || pos < 0 || count < 0) { + errno = EINVAL; + ntfs_log_perror("Failed to write runlist [vol: %p rl: %p " + "pos: %lld count: %lld]", vol, rl, pos, count); + goto errno_set; + } + if (!count) + goto out; + /* Seek in @rl to the run containing @pos. */ + for (ofs = 0; rl->length && (ofs + (rl->length << + vol->cluster_size_bits) <= pos); rl++) + ofs += (rl->length << vol->cluster_size_bits); + /* Offset in the run at which to begin writing. */ + ofs = pos - ofs; + for (total = 0LL; count; rl++, ofs = 0) { + if (!rl->length) + goto rl_err_out; + if (rl->lcn < (LCN)0) { + s64 t; + int cnt; + + if (rl->lcn != (LCN)LCN_HOLE) + goto rl_err_out; + /* + * It is a hole. Check if the buffer is zero in this + * region and if not abort with error. + */ + to_write = min(count, (rl->length << + vol->cluster_size_bits) - ofs); + written = to_write / sizeof(unsigned long); + for (t = 0; t < written; t++) { + if (((unsigned long*)b)[t]) + goto rl_err_out; + } + cnt = to_write & (sizeof(unsigned long) - 1); + if (cnt) { + int i; + u8 *b2; + + b2 = (u8*)b + (to_write & + ~(sizeof(unsigned long) - 1)); + for (i = 0; i < cnt; i++) { + if (b2[i]) + goto rl_err_out; + } + } + /* + * The buffer region is zero, update progress counters + * and proceed with next run. + */ + total += to_write; + count -= to_write; + b = (u8*)b + to_write; + continue; + } + /* It is a real lcn, write it to the volume. */ + to_write = min(count, (rl->length << vol->cluster_size_bits) - + ofs); +retry: + if (!NVolReadOnly(vol)) + written = ntfs_pwrite(vol->dev, (rl->lcn << + vol->cluster_size_bits) + ofs, + to_write, b); + else + written = to_write; + /* If everything ok, update progress counters and continue. */ + if (written > 0) { + total += written; + count -= written; + b = (u8*)b + written; + continue; + } + /* If the syscall was interrupted, try again. */ + if (written == (s64)-1 && errno == EINTR) + goto retry; + if (written == (s64)-1) + err = errno; + goto rl_err_out; + } +out: + return total; +rl_err_out: + if (total) + goto out; + errno = err; +errno_set: + total = -1; + goto out; +} + +/** + * ntfs_get_nr_significant_bytes - get number of bytes needed to store a number + * @n: number for which to get the number of bytes for + * + * Return the number of bytes required to store @n unambiguously as + * a signed number. + * + * This is used in the context of the mapping pairs array to determine how + * many bytes will be needed in the array to store a given logical cluster + * number (lcn) or a specific run length. + * + * Return the number of bytes written. This function cannot fail. + */ +int ntfs_get_nr_significant_bytes(const s64 n) +{ + s64 l = n; + int i; + s8 j; + + i = 0; + do { + l >>= 8; + i++; + } while (l != 0LL && l != -1LL); + j = (n >> 8 * (i - 1)) & 0xff; + /* If the sign bit is wrong, we need an extra byte. */ + if ((n < 0LL && j >= 0) || (n > 0LL && j < 0)) + i++; + return i; +} + +/** + * ntfs_get_size_for_mapping_pairs - get bytes needed for mapping pairs array + * @vol: ntfs volume (needed for the ntfs version) + * @rl: runlist for which to determine the size of the mapping pairs + * @start_vcn: vcn at which to start the mapping pairs array + * + * Walk the runlist @rl and calculate the size in bytes of the mapping pairs + * array corresponding to the runlist @rl, starting at vcn @start_vcn. This + * for example allows us to allocate a buffer of the right size when building + * the mapping pairs array. + * + * If @rl is NULL, just return 1 (for the single terminator byte). + * + * Return the calculated size in bytes on success. On error, return -1 with + * errno set to the error code. The following error codes are defined: + * EINVAL - Run list contains unmapped elements. Make sure to only pass + * fully mapped runlists to this function. + * - @start_vcn is invalid. + * EIO - The runlist is corrupt. + */ +int ntfs_get_size_for_mapping_pairs(const ntfs_volume *vol, + const runlist_element *rl, const VCN start_vcn) +{ + LCN prev_lcn; + int rls; + + if (start_vcn < 0) { + ntfs_log_trace("start_vcn %lld (should be >= 0)\n", + (long long) start_vcn); + errno = EINVAL; + goto errno_set; + } + if (!rl) { + if (start_vcn) { + ntfs_log_trace("rl NULL, start_vcn %lld (should be > 0)\n", + (long long) start_vcn); + errno = EINVAL; + goto errno_set; + } + rls = 1; + goto out; + } + /* Skip to runlist element containing @start_vcn. */ + while (rl->length && start_vcn >= rl[1].vcn) + rl++; + if ((!rl->length && start_vcn > rl->vcn) || start_vcn < rl->vcn) { + errno = EINVAL; + goto errno_set; + } + prev_lcn = 0; + /* Always need the terminating zero byte. */ + rls = 1; + /* Do the first partial run if present. */ + if (start_vcn > rl->vcn) { + s64 delta; + + /* We know rl->length != 0 already. */ + if (rl->length < 0 || rl->lcn < LCN_HOLE) + goto err_out; + delta = start_vcn - rl->vcn; + /* Header byte + length. */ + rls += 1 + ntfs_get_nr_significant_bytes(rl->length - delta); + /* + * If the logical cluster number (lcn) denotes a hole and we + * are on NTFS 3.0+, we don't store it at all, i.e. we need + * zero space. On earlier NTFS versions we just store the lcn. + * Note: this assumes that on NTFS 1.2-, holes are stored with + * an lcn of -1 and not a delta_lcn of -1 (unless both are -1). + */ + if (rl->lcn >= 0 || vol->major_ver < 3) { + prev_lcn = rl->lcn; + if (rl->lcn >= 0) + prev_lcn += delta; + /* Change in lcn. */ + rls += ntfs_get_nr_significant_bytes(prev_lcn); + } + /* Go to next runlist element. */ + rl++; + } + /* Do the full runs. */ + for (; rl->length; rl++) { + if (rl->length < 0 || rl->lcn < LCN_HOLE) + goto err_out; + /* Header byte + length. */ + rls += 1 + ntfs_get_nr_significant_bytes(rl->length); + /* + * If the logical cluster number (lcn) denotes a hole and we + * are on NTFS 3.0+, we don't store it at all, i.e. we need + * zero space. On earlier NTFS versions we just store the lcn. + * Note: this assumes that on NTFS 1.2-, holes are stored with + * an lcn of -1 and not a delta_lcn of -1 (unless both are -1). + */ + if (rl->lcn >= 0 || vol->major_ver < 3) { + /* Change in lcn. */ + rls += ntfs_get_nr_significant_bytes(rl->lcn - + prev_lcn); + prev_lcn = rl->lcn; + } + } +out: + return rls; +err_out: + if (rl->lcn == LCN_RL_NOT_MAPPED) + errno = EINVAL; + else + errno = EIO; +errno_set: + rls = -1; + goto out; +} + +/** + * ntfs_write_significant_bytes - write the significant bytes of a number + * @dst: destination buffer to write to + * @dst_max: pointer to last byte of destination buffer for bounds checking + * @n: number whose significant bytes to write + * + * Store in @dst, the minimum bytes of the number @n which are required to + * identify @n unambiguously as a signed number, taking care not to exceed + * @dest_max, the maximum position within @dst to which we are allowed to + * write. + * + * This is used when building the mapping pairs array of a runlist to compress + * a given logical cluster number (lcn) or a specific run length to the minimum + * size possible. + * + * Return the number of bytes written on success. On error, i.e. the + * destination buffer @dst is too small, return -1 with errno set ENOSPC. + */ +int ntfs_write_significant_bytes(u8 *dst, const u8 *dst_max, const s64 n) +{ + s64 l = n; + int i; + s8 j; + + i = 0; + do { + if (dst > dst_max) + goto err_out; + *dst++ = l & 0xffLL; + l >>= 8; + i++; + } while (l != 0LL && l != -1LL); + j = (n >> 8 * (i - 1)) & 0xff; + /* If the sign bit is wrong, we need an extra byte. */ + if (n < 0LL && j >= 0) { + if (dst > dst_max) + goto err_out; + i++; + *dst = (u8)-1; + } else if (n > 0LL && j < 0) { + if (dst > dst_max) + goto err_out; + i++; + *dst = 0; + } + return i; +err_out: + errno = ENOSPC; + return -1; +} + +/** + * ntfs_mapping_pairs_build - build the mapping pairs array from a runlist + * @vol: ntfs volume (needed for the ntfs version) + * @dst: destination buffer to which to write the mapping pairs array + * @dst_len: size of destination buffer @dst in bytes + * @rl: runlist for which to build the mapping pairs array + * @start_vcn: vcn at which to start the mapping pairs array + * @stop_vcn: first vcn outside destination buffer on success or ENOSPC error + * + * Create the mapping pairs array from the runlist @rl, starting at vcn + * @start_vcn and save the array in @dst. @dst_len is the size of @dst in + * bytes and it should be at least equal to the value obtained by calling + * ntfs_get_size_for_mapping_pairs(). + * + * If @rl is NULL, just write a single terminator byte to @dst. + * + * On success or ENOSPC error, if @stop_vcn is not NULL, *@stop_vcn is set to + * the first vcn outside the destination buffer. Note that on error @dst has + * been filled with all the mapping pairs that will fit, thus it can be treated + * as partial success, in that a new attribute extent needs to be created or the + * next extent has to be used and the mapping pairs build has to be continued + * with @start_vcn set to *@stop_vcn. + * + * Return 0 on success. On error, return -1 with errno set to the error code. + * The following error codes are defined: + * EINVAL - Run list contains unmapped elements. Make sure to only pass + * fully mapped runlists to this function. + * - @start_vcn is invalid. + * EIO - The runlist is corrupt. + * ENOSPC - The destination buffer is too small. + */ +int ntfs_mapping_pairs_build(const ntfs_volume *vol, u8 *dst, + const int dst_len, const runlist_element *rl, + const VCN start_vcn, VCN *const stop_vcn) +{ + LCN prev_lcn; + u8 *dst_max, *dst_next; + s8 len_len, lcn_len; + int ret = 0; + + if (start_vcn < 0) + goto val_err; + if (!rl) { + if (start_vcn) + goto val_err; + if (stop_vcn) + *stop_vcn = 0; + if (dst_len < 1) + goto nospc_err; + goto ok; + } + /* Skip to runlist element containing @start_vcn. */ + while (rl->length && start_vcn >= rl[1].vcn) + rl++; + if ((!rl->length && start_vcn > rl->vcn) || start_vcn < rl->vcn) + goto val_err; + /* + * @dst_max is used for bounds checking in + * ntfs_write_significant_bytes(). + */ + dst_max = dst + dst_len - 1; + prev_lcn = 0; + /* Do the first partial run if present. */ + if (start_vcn > rl->vcn) { + s64 delta; + + /* We know rl->length != 0 already. */ + if (rl->length < 0 || rl->lcn < LCN_HOLE) + goto err_out; + delta = start_vcn - rl->vcn; + /* Write length. */ + len_len = ntfs_write_significant_bytes(dst + 1, dst_max, + rl->length - delta); + if (len_len < 0) + goto size_err; + /* + * If the logical cluster number (lcn) denotes a hole and we + * are on NTFS 3.0+, we don't store it at all, i.e. we need + * zero space. On earlier NTFS versions we just write the lcn + * change. FIXME: Do we need to write the lcn change or just + * the lcn in that case? Not sure as I have never seen this + * case on NT4. - We assume that we just need to write the lcn + * change until someone tells us otherwise... (AIA) + */ + if (rl->lcn >= 0 || vol->major_ver < 3) { + prev_lcn = rl->lcn; + if (rl->lcn >= 0) + prev_lcn += delta; + /* Write change in lcn. */ + lcn_len = ntfs_write_significant_bytes(dst + 1 + + len_len, dst_max, prev_lcn); + if (lcn_len < 0) + goto size_err; + } else + lcn_len = 0; + dst_next = dst + len_len + lcn_len + 1; + if (dst_next > dst_max) + goto size_err; + /* Update header byte. */ + *dst = lcn_len << 4 | len_len; + /* Position at next mapping pairs array element. */ + dst = dst_next; + /* Go to next runlist element. */ + rl++; + } + /* Do the full runs. */ + for (; rl->length; rl++) { + if (rl->length < 0 || rl->lcn < LCN_HOLE) + goto err_out; + /* Write length. */ + len_len = ntfs_write_significant_bytes(dst + 1, dst_max, + rl->length); + if (len_len < 0) + goto size_err; + /* + * If the logical cluster number (lcn) denotes a hole and we + * are on NTFS 3.0+, we don't store it at all, i.e. we need + * zero space. On earlier NTFS versions we just write the lcn + * change. FIXME: Do we need to write the lcn change or just + * the lcn in that case? Not sure as I have never seen this + * case on NT4. - We assume that we just need to write the lcn + * change until someone tells us otherwise... (AIA) + */ + if (rl->lcn >= 0 || vol->major_ver < 3) { + /* Write change in lcn. */ + lcn_len = ntfs_write_significant_bytes(dst + 1 + + len_len, dst_max, rl->lcn - prev_lcn); + if (lcn_len < 0) + goto size_err; + prev_lcn = rl->lcn; + } else + lcn_len = 0; + dst_next = dst + len_len + lcn_len + 1; + if (dst_next > dst_max) + goto size_err; + /* Update header byte. */ + *dst = lcn_len << 4 | len_len; + /* Position at next mapping pairs array element. */ + dst += 1 + len_len + lcn_len; + } + /* Set stop vcn. */ + if (stop_vcn) + *stop_vcn = rl->vcn; +ok: + /* Add terminator byte. */ + *dst = 0; +out: + return ret; +size_err: + /* Set stop vcn. */ + if (stop_vcn) + *stop_vcn = rl->vcn; + /* Add terminator byte. */ + *dst = 0; +nospc_err: + errno = ENOSPC; + goto errno_set; +val_err: + errno = EINVAL; + goto errno_set; +err_out: + if (rl->lcn == LCN_RL_NOT_MAPPED) + errno = EINVAL; + else + errno = EIO; +errno_set: + ret = -1; + goto out; +} + +/** + * ntfs_rl_truncate - truncate a runlist starting at a specified vcn + * @arl: address of runlist to truncate + * @start_vcn: first vcn which should be cut off + * + * Truncate the runlist *@arl starting at vcn @start_vcn as well as the memory + * buffer holding the runlist. + * + * Return 0 on success and -1 on error with errno set to the error code. + * + * NOTE: @arl is the address of the runlist. We need the address so we can + * modify the pointer to the runlist with the new, reallocated memory buffer. + */ +int ntfs_rl_truncate(runlist **arl, const VCN start_vcn) +{ + runlist *rl; + BOOL is_end = FALSE; + + if (!arl || !*arl) { + errno = EINVAL; + ntfs_log_perror("rl_truncate error: arl: %p *arl: %p", arl, *arl); + return -1; + } + + rl = *arl; + + if (start_vcn < rl->vcn) { + errno = EINVAL; + ntfs_log_perror("Start_vcn lies outside front of runlist"); + return -1; + } + + /* Find the starting vcn in the run list. */ + while (rl->length) { + if (start_vcn < rl[1].vcn) + break; + rl++; + } + + if (!rl->length) { + errno = EIO; + ntfs_log_trace("Truncating already truncated runlist?\n"); + return -1; + } + + /* Truncate the run. */ + rl->length = start_vcn - rl->vcn; + + /* + * If a run was partially truncated, make the following runlist + * element a terminator instead of the truncated runlist + * element itself. + */ + if (rl->length) { + ++rl; + if (!rl->length) + is_end = TRUE; + rl->vcn = start_vcn; + rl->length = 0; + } + rl->lcn = (LCN)LCN_ENOENT; + /** + * Reallocate memory if necessary. + * FIXME: Below code is broken, because runlist allocations must be + * a multiply of 4096. The code caused crashes and corruptions. + */ +/* + if (!is_end) { + size_t new_size = (rl - *arl + 1) * sizeof(runlist_element); + rl = realloc(*arl, new_size); + if (rl) + *arl = rl; + } +*/ + return 0; +} + +/** + * ntfs_rl_sparse - check whether runlist have sparse regions or not. + * @rl: runlist to check + * + * Return 1 if have, 0 if not, -1 on error with errno set to the error code. + */ +int ntfs_rl_sparse(runlist *rl) +{ + runlist *rlc; + + if (!rl) { + ntfs_log_trace("Invalid argument passed.\n"); + errno = EINVAL; + return -1; + } + + for (rlc = rl; rlc->length; rlc++) + if (rlc->lcn < 0) { + if (rlc->lcn != LCN_HOLE) { + ntfs_log_trace("Received unmapped runlist.\n"); + errno = EINVAL; + return -1; + } + return 1; + } + return 0; +} + +/** + * ntfs_rl_get_compressed_size - calculate length of non sparse regions + * @vol: ntfs volume (need for cluster size) + * @rl: runlist to calculate for + * + * Return compressed size or -1 on error with errno set to the error code. + */ +s64 ntfs_rl_get_compressed_size(ntfs_volume *vol, runlist *rl) +{ + runlist *rlc; + s64 ret = 0; + + if (!rl) { + ntfs_log_trace("Invalid argument passed.\n"); + errno = EINVAL; + return -1; + } + + for (rlc = rl; rlc->length; rlc++) { + if (rlc->lcn < 0) { + if (rlc->lcn != LCN_HOLE) { + ntfs_log_trace("Received unmapped runlist.\n"); + errno = EINVAL; + return -1; + } + } else + ret += rlc->length; + } + return ret << vol->cluster_size_bits; +} + + +#ifdef NTFS_TEST +/** + * test_rl_helper + */ +#define MKRL(R,V,L,S) \ + (R)->vcn = V; \ + (R)->lcn = L; \ + (R)->length = S; +/* +} +*/ +/** + * test_rl_dump_runlist - Runlist test: Display the contents of a runlist + * @rl: + * + * Description... + * + * Returns: + */ +static void test_rl_dump_runlist(const runlist_element *rl) +{ + int abbr = 0; /* abbreviate long lists */ + int len = 0; + int i; + const char *lcn_str[5] = { "HOLE", "NOTMAP", "ENOENT", "XXXX" }; + + if (!rl) { + printf(" Run list not present.\n"); + return; + } + + if (abbr) + for (len = 0; rl[len].length; len++) ; + + printf(" VCN LCN len\n"); + for (i = 0; ; i++, rl++) { + LCN lcn = rl->lcn; + + if ((abbr) && (len > 20)) { + if (i == 4) + printf(" ...\n"); + if ((i > 3) && (i < (len - 3))) + continue; + } + + if (lcn < (LCN)0) { + int ind = -lcn - 1; + + if (ind > -LCN_ENOENT - 1) + ind = 3; + printf("%8lld %8s %8lld\n", + rl->vcn, lcn_str[ind], rl->length); + } else + printf("%8lld %8lld %8lld\n", + rl->vcn, rl->lcn, rl->length); + if (!rl->length) + break; + } + if ((abbr) && (len > 20)) + printf(" (%d entries)\n", len+1); + printf("\n"); +} + +/** + * test_rl_runlists_merge - Runlist test: Merge two runlists + * @drl: + * @srl: + * + * Description... + * + * Returns: + */ +static runlist_element * test_rl_runlists_merge(runlist_element *drl, runlist_element *srl) +{ + runlist_element *res = NULL; + + printf("dst:\n"); + test_rl_dump_runlist(drl); + printf("src:\n"); + test_rl_dump_runlist(srl); + + res = ntfs_runlists_merge(drl, srl); + + printf("res:\n"); + test_rl_dump_runlist(res); + + return res; +} + +/** + * test_rl_read_buffer - Runlist test: Read a file containing a runlist + * @file: + * @buf: + * @bufsize: + * + * Description... + * + * Returns: + */ +static int test_rl_read_buffer(const char *file, u8 *buf, int bufsize) +{ + FILE *fptr; + + fptr = fopen(file, "r"); + if (!fptr) { + printf("open %s\n", file); + return 0; + } + + if (fread(buf, bufsize, 1, fptr) == 99) { + printf("read %s\n", file); + return 0; + } + + fclose(fptr); + return 1; +} + +/** + * test_rl_pure_src - Runlist test: Complicate the simple tests a little + * @contig: + * @multi: + * @vcn: + * @len: + * + * Description... + * + * Returns: + */ +static runlist_element * test_rl_pure_src(BOOL contig, BOOL multi, int vcn, int len) +{ + runlist_element *result; + int fudge; + + if (contig) + fudge = 0; + else + fudge = 999; + + result = ntfs_malloc(4096); + if (!result) + return NULL; + + if (multi) { + MKRL(result+0, vcn + (0*len/4), fudge + vcn + 1000 + (0*len/4), len / 4) + MKRL(result+1, vcn + (1*len/4), fudge + vcn + 1000 + (1*len/4), len / 4) + MKRL(result+2, vcn + (2*len/4), fudge + vcn + 1000 + (2*len/4), len / 4) + MKRL(result+3, vcn + (3*len/4), fudge + vcn + 1000 + (3*len/4), len / 4) + MKRL(result+4, vcn + (4*len/4), LCN_RL_NOT_MAPPED, 0) + } else { + MKRL(result+0, vcn, fudge + vcn + 1000, len) + MKRL(result+1, vcn + len, LCN_RL_NOT_MAPPED, 0) + } + return result; +} + +/** + * test_rl_pure_test - Runlist test: Perform tests using simple runlists + * @test: + * @contig: + * @multi: + * @vcn: + * @len: + * @file: + * @size: + * + * Description... + * + * Returns: + */ +static void test_rl_pure_test(int test, BOOL contig, BOOL multi, int vcn, int len, runlist_element *file, int size) +{ + runlist_element *src; + runlist_element *dst; + runlist_element *res; + + src = test_rl_pure_src(contig, multi, vcn, len); + dst = ntfs_malloc(4096); + if (!src || !dst) { + printf("Test %2d ---------- FAILED! (no free memory?)\n", test); + return; + } + + memcpy(dst, file, size); + + printf("Test %2d ----------\n", test); + res = test_rl_runlists_merge(dst, src); + + free(res); +} + +/** + * test_rl_pure - Runlist test: Create tests using simple runlists + * @contig: + * @multi: + * + * Description... + * + * Returns: + */ +static void test_rl_pure(char *contig, char *multi) +{ + /* VCN, LCN, len */ + static runlist_element file1[] = { + { 0, -1, 100 }, /* HOLE */ + { 100, 1100, 100 }, /* DATA */ + { 200, -1, 100 }, /* HOLE */ + { 300, 1300, 100 }, /* DATA */ + { 400, -1, 100 }, /* HOLE */ + { 500, -3, 0 } /* NOENT */ + }; + static runlist_element file2[] = { + { 0, 1000, 100 }, /* DATA */ + { 100, -1, 100 }, /* HOLE */ + { 200, -3, 0 } /* NOENT */ + }; + static runlist_element file3[] = { + { 0, 1000, 100 }, /* DATA */ + { 100, -3, 0 } /* NOENT */ + }; + static runlist_element file4[] = { + { 0, -3, 0 } /* NOENT */ + }; + static runlist_element file5[] = { + { 0, -2, 100 }, /* NOTMAP */ + { 100, 1100, 100 }, /* DATA */ + { 200, -2, 100 }, /* NOTMAP */ + { 300, 1300, 100 }, /* DATA */ + { 400, -2, 100 }, /* NOTMAP */ + { 500, -3, 0 } /* NOENT */ + }; + static runlist_element file6[] = { + { 0, 1000, 100 }, /* DATA */ + { 100, -2, 100 }, /* NOTMAP */ + { 200, -3, 0 } /* NOENT */ + }; + BOOL c, m; + + if (strcmp(contig, "contig") == 0) + c = TRUE; + else if (strcmp(contig, "noncontig") == 0) + c = FALSE; + else { + printf("rl pure [contig|noncontig] [single|multi]\n"); + return; + } + if (strcmp(multi, "multi") == 0) + m = TRUE; + else if (strcmp(multi, "single") == 0) + m = FALSE; + else { + printf("rl pure [contig|noncontig] [single|multi]\n"); + return; + } + + test_rl_pure_test(1, c, m, 0, 40, file1, sizeof(file1)); + test_rl_pure_test(2, c, m, 40, 40, file1, sizeof(file1)); + test_rl_pure_test(3, c, m, 60, 40, file1, sizeof(file1)); + test_rl_pure_test(4, c, m, 0, 100, file1, sizeof(file1)); + test_rl_pure_test(5, c, m, 200, 40, file1, sizeof(file1)); + test_rl_pure_test(6, c, m, 240, 40, file1, sizeof(file1)); + test_rl_pure_test(7, c, m, 260, 40, file1, sizeof(file1)); + test_rl_pure_test(8, c, m, 200, 100, file1, sizeof(file1)); + test_rl_pure_test(9, c, m, 400, 40, file1, sizeof(file1)); + test_rl_pure_test(10, c, m, 440, 40, file1, sizeof(file1)); + test_rl_pure_test(11, c, m, 460, 40, file1, sizeof(file1)); + test_rl_pure_test(12, c, m, 400, 100, file1, sizeof(file1)); + test_rl_pure_test(13, c, m, 160, 100, file2, sizeof(file2)); + test_rl_pure_test(14, c, m, 100, 140, file2, sizeof(file2)); + test_rl_pure_test(15, c, m, 200, 40, file2, sizeof(file2)); + test_rl_pure_test(16, c, m, 240, 40, file2, sizeof(file2)); + test_rl_pure_test(17, c, m, 100, 40, file3, sizeof(file3)); + test_rl_pure_test(18, c, m, 140, 40, file3, sizeof(file3)); + test_rl_pure_test(19, c, m, 0, 40, file4, sizeof(file4)); + test_rl_pure_test(20, c, m, 40, 40, file4, sizeof(file4)); + test_rl_pure_test(21, c, m, 0, 40, file5, sizeof(file5)); + test_rl_pure_test(22, c, m, 40, 40, file5, sizeof(file5)); + test_rl_pure_test(23, c, m, 60, 40, file5, sizeof(file5)); + test_rl_pure_test(24, c, m, 0, 100, file5, sizeof(file5)); + test_rl_pure_test(25, c, m, 200, 40, file5, sizeof(file5)); + test_rl_pure_test(26, c, m, 240, 40, file5, sizeof(file5)); + test_rl_pure_test(27, c, m, 260, 40, file5, sizeof(file5)); + test_rl_pure_test(28, c, m, 200, 100, file5, sizeof(file5)); + test_rl_pure_test(29, c, m, 400, 40, file5, sizeof(file5)); + test_rl_pure_test(30, c, m, 440, 40, file5, sizeof(file5)); + test_rl_pure_test(31, c, m, 460, 40, file5, sizeof(file5)); + test_rl_pure_test(32, c, m, 400, 100, file5, sizeof(file5)); + test_rl_pure_test(33, c, m, 160, 100, file6, sizeof(file6)); + test_rl_pure_test(34, c, m, 100, 140, file6, sizeof(file6)); +} + +/** + * test_rl_zero - Runlist test: Merge a zero-length runlist + * + * Description... + * + * Returns: + */ +static void test_rl_zero(void) +{ + runlist_element *jim = NULL; + runlist_element *bob = NULL; + + bob = calloc(3, sizeof(runlist_element)); + if (!bob) + return; + + MKRL(bob+0, 10, 99, 5) + MKRL(bob+1, 15, LCN_RL_NOT_MAPPED, 0) + + jim = test_rl_runlists_merge(jim, bob); + if (!jim) + return; + + free(jim); +} + +/** + * test_rl_frag_combine - Runlist test: Perform tests using fragmented files + * @vol: + * @attr1: + * @attr2: + * @attr3: + * + * Description... + * + * Returns: + */ +static void test_rl_frag_combine(ntfs_volume *vol, ATTR_RECORD *attr1, ATTR_RECORD *attr2, ATTR_RECORD *attr3) +{ + runlist_element *run1; + runlist_element *run2; + runlist_element *run3; + + run1 = ntfs_mapping_pairs_decompress(vol, attr1, NULL); + if (!run1) + return; + + run2 = ntfs_mapping_pairs_decompress(vol, attr2, NULL); + if (!run2) + return; + + run1 = test_rl_runlists_merge(run1, run2); + + run3 = ntfs_mapping_pairs_decompress(vol, attr3, NULL); + if (!run3) + return; + + run1 = test_rl_runlists_merge(run1, run3); + + free(run1); +} + +/** + * test_rl_frag - Runlist test: Create tests using very fragmented files + * @test: + * + * Description... + * + * Returns: + */ +static void test_rl_frag(char *test) +{ + ntfs_volume vol; + ATTR_RECORD *attr1 = ntfs_malloc(1024); + ATTR_RECORD *attr2 = ntfs_malloc(1024); + ATTR_RECORD *attr3 = ntfs_malloc(1024); + + if (!attr1 || !attr2 || !attr3) + goto out; + + vol.sb = NULL; + vol.sector_size_bits = 9; + vol.cluster_size = 2048; + vol.cluster_size_bits = 11; + vol.major_ver = 3; + + if (!test_rl_read_buffer("runlist-data/attr1.bin", (u8*) attr1, 1024)) + goto out; + if (!test_rl_read_buffer("runlist-data/attr2.bin", (u8*) attr2, 1024)) + goto out; + if (!test_rl_read_buffer("runlist-data/attr3.bin", (u8*) attr3, 1024)) + goto out; + + if (strcmp(test, "123") == 0) test_rl_frag_combine(&vol, attr1, attr2, attr3); + else if (strcmp(test, "132") == 0) test_rl_frag_combine(&vol, attr1, attr3, attr2); + else if (strcmp(test, "213") == 0) test_rl_frag_combine(&vol, attr2, attr1, attr3); + else if (strcmp(test, "231") == 0) test_rl_frag_combine(&vol, attr2, attr3, attr1); + else if (strcmp(test, "312") == 0) test_rl_frag_combine(&vol, attr3, attr1, attr2); + else if (strcmp(test, "321") == 0) test_rl_frag_combine(&vol, attr3, attr2, attr1); + else + printf("Frag: No such test '%s'\n", test); + +out: + free(attr1); + free(attr2); + free(attr3); +} + +/** + * test_rl_main - Runlist test: Program start (main) + * @argc: + * @argv: + * + * Description... + * + * Returns: + */ +int test_rl_main(int argc, char *argv[]) +{ + if ((argc == 2) && (strcmp(argv[1], "zero") == 0)) test_rl_zero(); + else if ((argc == 3) && (strcmp(argv[1], "frag") == 0)) test_rl_frag(argv[2]); + else if ((argc == 4) && (strcmp(argv[1], "pure") == 0)) test_rl_pure(argv[2], argv[3]); + else + printf("rl [zero|frag|pure] {args}\n"); + + return 0; +} + +#endif + diff --git a/libntfs-3g/security.c b/libntfs-3g/security.c new file mode 100644 index 00000000..926a1829 --- /dev/null +++ b/libntfs-3g/security.c @@ -0,0 +1,340 @@ +/** + * security.c - Handling security/ACLs in NTFS. Originated from the Linux-NTFS project. + * + * Copyright (c) 2004 Anton Altaparmakov + * Copyright (c) 2005-2006 Szabolcs Szakacsits + * Copyright (c) 2006 Yura Pakhuchiy + * + * 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 + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program/include file 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 + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_STDIO_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif +#ifdef HAVE_ERRNO_H +#include +#endif + +#include "types.h" +#include "layout.h" +#include "attrib.h" +#include "security.h" +#include "misc.h" + +/* + * The zero GUID. + */ +static const GUID __zero_guid = { const_cpu_to_le32(0), const_cpu_to_le16(0), + const_cpu_to_le16(0), { 0, 0, 0, 0, 0, 0, 0, 0 } }; +const GUID *const zero_guid = &__zero_guid; + +/** + * ntfs_guid_is_zero - check if a GUID is zero + * @guid: [IN] guid to check + * + * Return TRUE if @guid is a valid pointer to a GUID and it is the zero GUID + * and FALSE otherwise. + */ +BOOL ntfs_guid_is_zero(const GUID *guid) +{ + return (memcmp(guid, zero_guid, sizeof(*zero_guid))); +} + +/** + * ntfs_guid_to_mbs - convert a GUID to a multi byte string + * @guid: [IN] guid to convert + * @guid_str: [OUT] string in which to return the GUID (optional) + * + * Convert the GUID pointed to by @guid to a multi byte string of the form + * "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX". Therefore, @guid_str (if not NULL) + * needs to be able to store at least 37 bytes. + * + * If @guid_str is not NULL it will contain the converted GUID on return. If + * it is NULL a string will be allocated and this will be returned. The caller + * is responsible for free()ing the string in that case. + * + * On success return the converted string and on failure return NULL with errno + * set to the error code. + */ +char *ntfs_guid_to_mbs(const GUID *guid, char *guid_str) +{ + char *_guid_str; + int res; + + if (!guid) { + errno = EINVAL; + return NULL; + } + _guid_str = guid_str; + if (!_guid_str) { + _guid_str = ntfs_malloc(37); + if (!_guid_str) + return _guid_str; + } + res = snprintf(_guid_str, 37, + "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x", + (unsigned int)le32_to_cpu(guid->data1), + le16_to_cpu(guid->data2), le16_to_cpu(guid->data3), + guid->data4[0], guid->data4[1], + guid->data4[2], guid->data4[3], guid->data4[4], + guid->data4[5], guid->data4[6], guid->data4[7]); + if (res == 36) + return _guid_str; + if (!guid_str) + free(_guid_str); + errno = EINVAL; + return NULL; +} + +/** + * ntfs_sid_to_mbs_size - determine maximum size for the string of a SID + * @sid: [IN] SID for which to determine the maximum string size + * + * Determine the maximum multi byte string size in bytes which is needed to + * store the standard textual representation of the SID pointed to by @sid. + * See ntfs_sid_to_mbs(), below. + * + * On success return the maximum number of bytes needed to store the multi byte + * string and on failure return -1 with errno set to the error code. + */ +int ntfs_sid_to_mbs_size(const SID *sid) +{ + int size, i; + + if (!ntfs_sid_is_valid(sid)) { + errno = EINVAL; + return -1; + } + /* Start with "S-". */ + size = 2; + /* + * Add the SID_REVISION. Hopefully the compiler will optimize this + * away as SID_REVISION is a constant. + */ + for (i = SID_REVISION; i > 0; i /= 10) + size++; + /* Add the "-". */ + size++; + /* + * Add the identifier authority. If it needs to be in decimal, the + * maximum is 2^32-1 = 4294967295 = 10 characters. If it needs to be + * in hexadecimal, then maximum is 0x665544332211 = 14 characters. + */ + if (!sid->identifier_authority.high_part) + size += 10; + else + size += 14; + /* + * Finally, add the sub authorities. For each we have a "-" followed + * by a decimal which can be up to 2^32-1 = 4294967295 = 10 characters. + */ + size += (1 + 10) * sid->sub_authority_count; + /* We need the zero byte at the end, too. */ + size++; + return size * sizeof(char); +} + +/** + * ntfs_sid_to_mbs - convert a SID to a multi byte string + * @sid: [IN] SID to convert + * @sid_str: [OUT] string in which to return the SID (optional) + * @sid_str_size: [IN] size in bytes of @sid_str + * + * Convert the SID pointed to by @sid to its standard textual representation. + * @sid_str (if not NULL) needs to be able to store at least + * ntfs_sid_to_mbs_size() bytes. @sid_str_size is the size in bytes of + * @sid_str if @sid_str is not NULL. + * + * The standard textual representation of the SID is of the form: + * S-R-I-S-S... + * Where: + * - The first "S" is the literal character 'S' identifying the following + * digits as a SID. + * - R is the revision level of the SID expressed as a sequence of digits + * in decimal. + * - I is the 48-bit identifier_authority, expressed as digits in decimal, + * if I < 2^32, or hexadecimal prefixed by "0x", if I >= 2^32. + * - S... is one or more sub_authority values, expressed as digits in + * decimal. + * + * If @sid_str is not NULL it will contain the converted SUID on return. If it + * is NULL a string will be allocated and this will be returned. The caller is + * responsible for free()ing the string in that case. + * + * On success return the converted string and on failure return NULL with errno + * set to the error code. + */ +char *ntfs_sid_to_mbs(const SID *sid, char *sid_str, size_t sid_str_size) +{ + u64 u; + char *s; + int i, j, cnt; + + /* + * 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))) { + errno = EINVAL; + return NULL; + } + /* Allocate string if not provided. */ + if (!sid_str) { + cnt = ntfs_sid_to_mbs_size(sid); + if (cnt < 0) + return NULL; + s = ntfs_malloc(cnt); + if (!s) + return s; + sid_str = s; + /* So we know we allocated it. */ + sid_str_size = 0; + } else { + s = sid_str; + cnt = sid_str_size; + } + /* Start with "S-R-". */ + i = snprintf(s, cnt, "S-%hhu-", (unsigned char)sid->revision); + if (i < 0 || i >= cnt) + goto err_out; + s += i; + cnt -= i; + /* Add the identifier authority. */ + for (u = i = 0, j = 40; i < 6; i++, j -= 8) + u += (u64)sid->identifier_authority.value[i] << j; + if (!sid->identifier_authority.high_part) + i = snprintf(s, cnt, "%lu", (unsigned long)u); + else + i = snprintf(s, cnt, "0x%llx", (unsigned long long)u); + if (i < 0 || i >= cnt) + goto err_out; + s += i; + cnt -= i; + /* Finally, add the sub authorities. */ + for (j = 0; j < sid->sub_authority_count; j++) { + i = snprintf(s, cnt, "-%u", (unsigned int) + le32_to_cpu(sid->sub_authority[j])); + if (i < 0 || i >= cnt) + goto err_out; + s += i; + cnt -= i; + } + return sid_str; +err_out: + if (i >= cnt) + i = EMSGSIZE; + else + i = errno; + if (!sid_str_size) + free(sid_str); + errno = i; + return NULL; +} + +/** + * ntfs_generate_guid - generatates a random current guid. + * @guid: [OUT] pointer to a GUID struct to hold the generated guid. + * + * perhaps not a very good random number generator though... + */ +void ntfs_generate_guid(GUID *guid) +{ + unsigned int i; + u8 *p = (u8 *)guid; + + for (i = 0; i < sizeof(GUID); i++) { + p[i] = (u8)(random() & 0xFF); + if (i == 7) + p[7] = (p[7] & 0x0F) | 0x40; + if (i == 8) + p[8] = (p[8] & 0x3F) | 0x80; + } +} + +int ntfs_sd_add_everyone(ntfs_inode *ni) +{ + SECURITY_DESCRIPTOR_ATTR *sd; + ACL *acl; + ACCESS_ALLOWED_ACE *ace; + SID *sid; + int ret, sd_len; + + /* Create SECURITY_DESCRIPTOR attribute (everyone has full access). */ + /* + * Calculate security descriptor length. We have 2 sub-authorities in + * owner and group SIDs, but structure SID contain only one, so add + * 4 bytes to every SID. + */ + sd_len = sizeof(SECURITY_DESCRIPTOR_ATTR) + 2 * (sizeof(SID) + 4) + + sizeof(ACL) + sizeof(ACCESS_ALLOWED_ACE); + sd = ntfs_calloc(sd_len); + if (!sd) + return -1; + + sd->revision = 1; + sd->control = SE_DACL_PRESENT | SE_SELF_RELATIVE; + + sid = (SID *)((u8 *)sd + sizeof(SECURITY_DESCRIPTOR_ATTR)); + sid->revision = 1; + sid->sub_authority_count = 2; + sid->sub_authority[0] = cpu_to_le32(SECURITY_BUILTIN_DOMAIN_RID); + sid->sub_authority[1] = cpu_to_le32(DOMAIN_ALIAS_RID_ADMINS); + sid->identifier_authority.value[5] = 5; + sd->owner = cpu_to_le32((u8 *)sid - (u8 *)sd); + + sid = (SID *)((u8 *)sid + sizeof(SID) + 4); + sid->revision = 1; + sid->sub_authority_count = 2; + sid->sub_authority[0] = cpu_to_le32(SECURITY_BUILTIN_DOMAIN_RID); + sid->sub_authority[1] = cpu_to_le32(DOMAIN_ALIAS_RID_ADMINS); + sid->identifier_authority.value[5] = 5; + sd->group = cpu_to_le32((u8 *)sid - (u8 *)sd); + + acl = (ACL *)((u8 *)sid + sizeof(SID) + 4); + acl->revision = 2; + acl->size = cpu_to_le16(sizeof(ACL) + sizeof(ACCESS_ALLOWED_ACE)); + acl->ace_count = cpu_to_le16(1); + sd->dacl = cpu_to_le32((u8 *)acl - (u8 *)sd); + + ace = (ACCESS_ALLOWED_ACE *)((u8 *)acl + sizeof(ACL)); + ace->type = ACCESS_ALLOWED_ACE_TYPE; + ace->flags = OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE; + ace->size = cpu_to_le16(sizeof(ACCESS_ALLOWED_ACE)); + ace->mask = cpu_to_le32(0x1f01ff); /* FIXME */ + ace->sid.revision = 1; + ace->sid.sub_authority_count = 1; + ace->sid.sub_authority[0] = 0; + ace->sid.identifier_authority.value[5] = 1; + + ret = ntfs_attr_add(ni, AT_SECURITY_DESCRIPTOR, AT_UNNAMED, 0, (u8 *)sd, + sd_len); + if (ret) + ntfs_log_perror("Failed to add SECURITY_DESCRIPTOR\n"); + + free(sd); + return ret; +} + diff --git a/libntfs-3g/unistr.c b/libntfs-3g/unistr.c new file mode 100644 index 00000000..4eb9dee9 --- /dev/null +++ b/libntfs-3g/unistr.c @@ -0,0 +1,757 @@ +/** + * unistr.c - Unicode string handling. Originated from the Linux-NTFS project. + * + * Copyright (c) 2000-2004 Anton Altaparmakov + * Copyright (c) 2002-2006 Szabolcs Szakacsits + * + * 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 + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program/include file 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 + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_STDIO_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_WCHAR_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif +#ifdef HAVE_ERRNO_H +#include +#endif + +#include "attrib.h" +#include "types.h" +#include "unistr.h" +#include "debug.h" +#include "logging.h" +#include "misc.h" + +/* + * IMPORTANT + * ========= + * + * All these routines assume that the Unicode characters are in little endian + * encoding inside the strings!!! + */ + +/* + * This is used by the name collation functions to quickly determine what + * characters are (in)valid. + */ +#if 0 +static const u8 legal_ansi_char_array[0x40] = { + 0x00, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + + 0x17, 0x07, 0x18, 0x17, 0x17, 0x17, 0x17, 0x17, + 0x17, 0x17, 0x18, 0x16, 0x16, 0x17, 0x07, 0x00, + + 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, + 0x17, 0x17, 0x04, 0x16, 0x18, 0x16, 0x18, 0x18, +}; +#endif + +/** + * ntfs_names_are_equal - compare two Unicode names for equality + * @s1: name to compare to @s2 + * @s1_len: length in Unicode characters of @s1 + * @s2: name to compare to @s1 + * @s2_len: length in Unicode characters of @s2 + * @ic: ignore case bool + * @upcase: upcase table (only if @ic == IGNORE_CASE) + * @upcase_size: length in Unicode characters of @upcase (if present) + * + * Compare the names @s1 and @s2 and return TRUE (1) if the names are + * identical, or FALSE (0) if they are not identical. If @ic is IGNORE_CASE, + * the @upcase table is used to perform a case insensitive comparison. + */ +BOOL ntfs_names_are_equal(const ntfschar *s1, size_t s1_len, + const ntfschar *s2, size_t s2_len, + const IGNORE_CASE_BOOL ic, + const ntfschar *upcase, const u32 upcase_size) +{ + if (s1_len != s2_len) + return FALSE; + if (!s1_len) + return TRUE; + if (ic == CASE_SENSITIVE) + return ntfs_ucsncmp(s1, s2, s1_len) ? FALSE: TRUE; + return ntfs_ucsncasecmp(s1, s2, s1_len, upcase, upcase_size) ? FALSE: + TRUE; +} + +/** + * ntfs_names_collate - collate two Unicode names + * @name1: first Unicode name to compare + * @name1_len: length of first Unicode name to compare + * @name2: second Unicode name to compare + * @name2_len: length of second Unicode name to compare + * @err_val: if @name1 contains an invalid character return this value + * @ic: either CASE_SENSITIVE or IGNORE_CASE + * @upcase: upcase table (ignored if @ic is CASE_SENSITIVE) + * @upcase_len: upcase table size (ignored if @ic is CASE_SENSITIVE) + * + * ntfs_names_collate() collates two Unicode names and returns: + * + * -1 if the first name collates before the second one, + * 0 if the names match, + * 1 if the second name collates before the first one, or + * @err_val if an invalid character is found in @name1 during the comparison. + * + * The following characters are considered invalid: '"', '*', '<', '>' and '?'. + */ +int ntfs_names_collate(const ntfschar *name1, const u32 name1_len, + const ntfschar *name2, const u32 name2_len, + const int err_val __attribute__((unused)), + const IGNORE_CASE_BOOL ic, const ntfschar *upcase, + const u32 upcase_len) +{ + u32 cnt; + ntfschar c1, c2; + +#ifdef DEBUG + if (!name1 || !name2 || (ic && (!upcase || !upcase_len))) { + ntfs_log_debug("ntfs_names_collate received NULL pointer!\n"); + exit(1); + } +#endif + for (cnt = 0; cnt < min(name1_len, name2_len); ++cnt) { + c1 = le16_to_cpu(*name1); + name1++; + c2 = le16_to_cpu(*name2); + name2++; + if (ic) { + if (c1 < upcase_len) + c1 = le16_to_cpu(upcase[c1]); + if (c2 < upcase_len) + c2 = le16_to_cpu(upcase[c2]); + } +#if 0 + if (c1 < 64 && legal_ansi_char_array[c1] & 8) + return err_val; +#endif + if (c1 < c2) + return -1; + if (c1 > c2) + return 1; + } + if (name1_len < name2_len) + return -1; + if (name1_len == name2_len) + return 0; + /* name1_len > name2_len */ +#if 0 + c1 = le16_to_cpu(*name1); + if (c1 < 64 && legal_ansi_char_array[c1] & 8) + return err_val; +#endif + return 1; +} + +/** + * ntfs_ucsncmp - compare two little endian Unicode strings + * @s1: first string + * @s2: second string + * @n: maximum unicode characters to compare + * + * Compare the first @n characters of the Unicode strings @s1 and @s2, + * The strings in little endian format and appropriate le16_to_cpu() + * conversion is performed on non-little endian machines. + * + * The function returns an integer less than, equal to, or greater than zero + * if @s1 (or the first @n Unicode characters thereof) is found, respectively, + * to be less than, to match, or be greater than @s2. + */ +int ntfs_ucsncmp(const ntfschar *s1, const ntfschar *s2, size_t n) +{ + ntfschar c1, c2; + size_t i; + +#ifdef DEBUG + if (!s1 || !s2) { + ntfs_log_debug("ntfs_wcsncmp() received NULL pointer!\n"); + exit(1); + } +#endif + for (i = 0; i < n; ++i) { + c1 = le16_to_cpu(s1[i]); + c2 = le16_to_cpu(s2[i]); + if (c1 < c2) + return -1; + if (c1 > c2) + return 1; + if (!c1) + break; + } + return 0; +} + +/** + * ntfs_ucsncasecmp - compare two little endian Unicode strings, ignoring case + * @s1: first string + * @s2: second string + * @n: maximum unicode characters to compare + * @upcase: upcase table + * @upcase_size: upcase table size in Unicode characters + * + * Compare the first @n characters of the Unicode strings @s1 and @s2, + * ignoring case. The strings in little endian format and appropriate + * le16_to_cpu() conversion is performed on non-little endian machines. + * + * Each character is uppercased using the @upcase table before the comparison. + * + * The function returns an integer less than, equal to, or greater than zero + * if @s1 (or the first @n Unicode characters thereof) is found, respectively, + * to be less than, to match, or be greater than @s2. + */ +int ntfs_ucsncasecmp(const ntfschar *s1, const ntfschar *s2, size_t n, + const ntfschar *upcase, const u32 upcase_size) +{ + ntfschar c1, c2; + size_t i; + +#ifdef DEBUG + if (!s1 || !s2 || !upcase) { + ntfs_log_debug("ntfs_wcsncasecmp() received NULL pointer!\n"); + exit(1); + } +#endif + for (i = 0; i < n; ++i) { + if ((c1 = le16_to_cpu(s1[i])) < upcase_size) + c1 = le16_to_cpu(upcase[c1]); + if ((c2 = le16_to_cpu(s2[i])) < upcase_size) + c2 = le16_to_cpu(upcase[c2]); + if (c1 < c2) + return -1; + if (c1 > c2) + return 1; + if (!c1) + break; + } + return 0; +} + +/** + * ntfs_ucsnlen - determine the length of a little endian Unicode string + * @s: pointer to Unicode string + * @maxlen: maximum length of string @s + * + * Return the number of Unicode characters in the little endian Unicode + * string @s up to a maximum of maxlen Unicode characters, not including + * the terminating (ntfschar)'\0'. If there is no (ntfschar)'\0' between @s + * and @s + @maxlen, @maxlen is returned. + * + * This function never looks beyond @s + @maxlen. + */ +u32 ntfs_ucsnlen(const ntfschar *s, u32 maxlen) +{ + u32 i; + + for (i = 0; i < maxlen; i++) { + if (!le16_to_cpu(s[i])) + break; + } + return i; +} + +/** + * ntfs_ucsndup - duplicate little endian Unicode string + * @s: pointer to Unicode string + * @maxlen: maximum length of string @s + * + * Return a pointer to a new little endian Unicode string which is a duplicate + * of the string s. Memory for the new string is obtained with ntfs_malloc(3), + * and can be freed with free(3). + * + * A maximum of @maxlen Unicode characters are copied and a terminating + * (ntfschar)'\0' little endian Unicode character is added. + * + * This function never looks beyond @s + @maxlen. + * + * Return a pointer to the new little endian Unicode string on success and NULL + * on failure with errno set to the error code. + */ +ntfschar *ntfs_ucsndup(const ntfschar *s, u32 maxlen) +{ + ntfschar *dst; + u32 len; + + len = ntfs_ucsnlen(s, maxlen); + dst = ntfs_malloc((len + 1) * sizeof(ntfschar)); + if (dst) { + memcpy(dst, s, len * sizeof(ntfschar)); + dst[len] = cpu_to_le16(L'\0'); + } + return dst; +} + +/** + * ntfs_name_upcase - Map an Unicode name to its uppercase equivalent + * @name: + * @name_len: + * @upcase: + * @upcase_len: + * + * Description... + * + * Returns: + */ +void ntfs_name_upcase(ntfschar *name, u32 name_len, const ntfschar *upcase, + const u32 upcase_len) +{ + u32 i; + ntfschar u; + + for (i = 0; i < name_len; i++) + if ((u = le16_to_cpu(name[i])) < upcase_len) + name[i] = upcase[u]; +} + +/** + * ntfs_file_value_upcase - Convert a filename to upper case + * @file_name_attr: + * @upcase: + * @upcase_len: + * + * Description... + * + * Returns: + */ +void ntfs_file_value_upcase(FILE_NAME_ATTR *file_name_attr, + const ntfschar *upcase, const u32 upcase_len) +{ + ntfs_name_upcase((ntfschar*)&file_name_attr->file_name, + file_name_attr->file_name_length, upcase, upcase_len); +} + +/** + * ntfs_file_values_compare - Which of two filenames should be listed first + * @file_name_attr1: + * @file_name_attr2: + * @err_val: + * @ic: + * @upcase: + * @upcase_len: + * + * Description... + * + * Returns: + */ +int ntfs_file_values_compare(const FILE_NAME_ATTR *file_name_attr1, + const FILE_NAME_ATTR *file_name_attr2, + const int err_val, const IGNORE_CASE_BOOL ic, + const ntfschar *upcase, const u32 upcase_len) +{ + return ntfs_names_collate((ntfschar*)&file_name_attr1->file_name, + file_name_attr1->file_name_length, + (ntfschar*)&file_name_attr2->file_name, + file_name_attr2->file_name_length, + err_val, ic, upcase, upcase_len); +} + +/** + * ntfs_ucstombs - convert a little endian Unicode string to a multibyte string + * @ins: input Unicode string buffer + * @ins_len: length of input string in Unicode characters + * @outs: on return contains the (allocated) output multibyte string + * @outs_len: length of output buffer in bytes + * + * Convert the input little endian, 2-byte Unicode string @ins, of length + * @ins_len into the multibyte string format dictated by the current locale. + * + * If *@outs is NULL, the function allocates the string and the caller is + * responsible for calling free(*@outs); when finished with it. + * + * On success the function returns the number of bytes written to the output + * string *@outs (>= 0), not counting the terminating NULL byte. If the output + * string buffer was allocated, *@outs is set to it. + * + * On error, -1 is returned, and errno is set to the error code. The following + * error codes can be expected: + * EINVAL Invalid arguments (e.g. @ins or @outs is NULL). + * EILSEQ The input string cannot be represented as a multibyte + * sequence according to the current locale. + * ENAMETOOLONG Destination buffer is too small for input string. + * ENOMEM Not enough memory to allocate destination buffer. + */ +int ntfs_ucstombs(const ntfschar *ins, const int ins_len, char **outs, + int outs_len) +{ + char *mbs; + wchar_t wc; + int i, o, mbs_len; + int cnt = 0; +#ifdef HAVE_MBSINIT + mbstate_t mbstate; +#endif + + if (!ins || !outs) { + errno = EINVAL; + return -1; + } + mbs = *outs; + mbs_len = outs_len; + if (mbs && !mbs_len) { + errno = ENAMETOOLONG; + return -1; + } + if (!mbs) { + mbs_len = (ins_len + 1) * MB_CUR_MAX; + mbs = ntfs_malloc(mbs_len); + if (!mbs) + return -1; + } +#ifdef HAVE_MBSINIT + memset(&mbstate, 0, sizeof(mbstate)); +#else + wctomb(NULL, 0); +#endif + for (i = o = 0; i < ins_len; i++) { + /* Reallocate memory if necessary or abort. */ + if ((int)(o + MB_CUR_MAX) > mbs_len) { + char *tc; + if (mbs == *outs) { + errno = ENAMETOOLONG; + return -1; + } + tc = ntfs_malloc((mbs_len + 64) & ~63); + if (!tc) + goto err_out; + memcpy(tc, mbs, mbs_len); + mbs_len = (mbs_len + 64) & ~63; + free(mbs); + mbs = tc; + } + /* Convert the LE Unicode character to a CPU wide character. */ + wc = (wchar_t)le16_to_cpu(ins[i]); + if (!wc) + break; + /* Convert the CPU endian wide character to multibyte. */ +#ifdef HAVE_MBSINIT + cnt = wcrtomb(mbs + o, wc, &mbstate); +#else + cnt = wctomb(mbs + o, wc); +#endif + if (cnt == -1) + goto err_out; + if (cnt <= 0) { + ntfs_log_debug("Eeek. cnt <= 0, cnt = %i\n", cnt); + errno = EINVAL; + goto err_out; + } + o += cnt; + } +#ifdef HAVE_MBSINIT + /* Make sure we are back in the initial state. */ + if (!mbsinit(&mbstate)) { + ntfs_log_debug("Eeek. mbstate not in initial state!\n"); + errno = EILSEQ; + goto err_out; + } +#endif + /* Now write the NULL character. */ + mbs[o] = '\0'; + if (*outs != mbs) + *outs = mbs; + return o; +err_out: + if (mbs != *outs) { + int eo = errno; + free(mbs); + errno = eo; + } + return -1; +} + +/** + * ntfs_mbstoucs - convert a multibyte string to a little endian Unicode string + * @ins: input multibyte string buffer + * @outs: on return contains the (allocated) output Unicode string + * @outs_len: length of output buffer in Unicode characters + * + * Convert the input multibyte string @ins, from the current locale into the + * corresponding little endian, 2-byte Unicode string. + * + * If *@outs is NULL, the function allocates the string and the caller is + * responsible for calling free(*@outs); when finished with it. + * + * On success the function returns the number of Unicode characters written to + * the output string *@outs (>= 0), not counting the terminating Unicode NULL + * character. If the output string buffer was allocated, *@outs is set to it. + * + * On error, -1 is returned, and errno is set to the error code. The following + * error codes can be expected: + * EINVAL Invalid arguments (e.g. @ins or @outs is NULL). + * EILSEQ The input string cannot be represented as a Unicode + * string according to the current locale. + * ENAMETOOLONG Destination buffer is too small for input string. + * ENOMEM Not enough memory to allocate destination buffer. + */ +int ntfs_mbstoucs(const char *ins, ntfschar **outs, int outs_len) +{ + ntfschar *ucs; + const char *s; + wchar_t wc; + int i, o, cnt, ins_len, ucs_len, ins_size; +#ifdef HAVE_MBSINIT + mbstate_t mbstate; +#endif + + if (!ins || !outs) { + errno = EINVAL; + return -1; + } + ucs = *outs; + ucs_len = outs_len; + if (ucs && !ucs_len) { + errno = ENAMETOOLONG; + return -1; + } + /* Determine the size of the multi-byte string in bytes. */ + ins_size = strlen(ins); + /* Determine the length of the multi-byte string. */ + s = ins; +#if defined(HAVE_MBSINIT) + memset(&mbstate, 0, sizeof(mbstate)); + ins_len = mbsrtowcs(NULL, (const char **)&s, 0, &mbstate); +#ifdef __CYGWIN32__ + if (!ins_len && *ins) { + /* Older Cygwin had broken mbsrtowcs() implementation. */ + ins_len = strlen(ins); + } +#endif +#elif !defined(DJGPP) + ins_len = mbstowcs(NULL, s, 0); +#else + /* Eeek!!! DJGPP has broken mbstowcs() implementation!!! */ + ins_len = strlen(ins); +#endif + if (ins_len == -1) + return ins_len; +#ifdef HAVE_MBSINIT + if ((s != ins) || !mbsinit(&mbstate)) { +#else + if (s != ins) { +#endif + errno = EILSEQ; + return -1; + } + /* Add the NULL terminator. */ + ins_len++; + if (!ucs) { + ucs_len = ins_len; + ucs = ntfs_malloc(ucs_len * sizeof(ntfschar)); + if (!ucs) + return -1; + } +#ifdef HAVE_MBSINIT + memset(&mbstate, 0, sizeof(mbstate)); +#else + mbtowc(NULL, NULL, 0); +#endif + for (i = o = cnt = 0; i < ins_size; i += cnt, o++) { + /* Reallocate memory if necessary or abort. */ + if (o >= ucs_len) { + ntfschar *tc; + if (ucs == *outs) { + errno = ENAMETOOLONG; + return -1; + } + /* + * We will never get here but hey, it's only a bit of + * extra code... + */ + ucs_len = (ucs_len * sizeof(ntfschar) + 64) & ~63; + tc = (ntfschar*)realloc(ucs, ucs_len); + if (!tc) + goto err_out; + ucs = tc; + ucs_len /= sizeof(ntfschar); + } + /* Convert the multibyte character to a wide character. */ +#ifdef HAVE_MBSINIT + cnt = mbrtowc(&wc, ins + i, ins_size - i, &mbstate); +#else + cnt = mbtowc(&wc, ins + i, ins_size - i); +#endif + if (!cnt) + break; + if (cnt == -1) + goto err_out; + if (cnt < -1) { + ntfs_log_trace("Eeek. cnt = %i\n", cnt); + errno = EINVAL; + goto err_out; + } + /* Make sure we are not overflowing the NTFS Unicode set. */ + if ((unsigned long)wc >= (unsigned long)(1 << + (8 * sizeof(ntfschar)))) { + errno = EILSEQ; + goto err_out; + } + /* Convert the CPU wide character to a LE Unicode character. */ + ucs[o] = cpu_to_le16(wc); + } +#ifdef HAVE_MBSINIT + /* Make sure we are back in the initial state. */ + if (!mbsinit(&mbstate)) { + ntfs_log_trace("Eeek. mbstate not in initial state!\n"); + errno = EILSEQ; + goto err_out; + } +#endif + /* Now write the NULL character. */ + ucs[o] = cpu_to_le16(L'\0'); + if (*outs != ucs) + *outs = ucs; + return o; +err_out: + if (ucs != *outs) { + int eo = errno; + free(ucs); + errno = eo; + } + return -1; +} + +/** + * ntfs_upcase_table_build - build the default upcase table for NTFS + * @uc: destination buffer where to store the built table + * @uc_len: size of destination buffer in bytes + * + * ntfs_upcase_table_build() builds the default upcase table for NTFS and + * stores it in the caller supplied buffer @uc of size @uc_len. + * + * Note, @uc_len must be at least 128kiB in size or bad things will happen! + */ +void ntfs_upcase_table_build(ntfschar *uc, u32 uc_len) +{ + static int uc_run_table[][3] = { /* Start, End, Add */ + {0x0061, 0x007B, -32}, {0x0451, 0x045D, -80}, {0x1F70, 0x1F72, 74}, + {0x00E0, 0x00F7, -32}, {0x045E, 0x0460, -80}, {0x1F72, 0x1F76, 86}, + {0x00F8, 0x00FF, -32}, {0x0561, 0x0587, -48}, {0x1F76, 0x1F78, 100}, + {0x0256, 0x0258, -205}, {0x1F00, 0x1F08, 8}, {0x1F78, 0x1F7A, 128}, + {0x028A, 0x028C, -217}, {0x1F10, 0x1F16, 8}, {0x1F7A, 0x1F7C, 112}, + {0x03AC, 0x03AD, -38}, {0x1F20, 0x1F28, 8}, {0x1F7C, 0x1F7E, 126}, + {0x03AD, 0x03B0, -37}, {0x1F30, 0x1F38, 8}, {0x1FB0, 0x1FB2, 8}, + {0x03B1, 0x03C2, -32}, {0x1F40, 0x1F46, 8}, {0x1FD0, 0x1FD2, 8}, + {0x03C2, 0x03C3, -31}, {0x1F51, 0x1F52, 8}, {0x1FE0, 0x1FE2, 8}, + {0x03C3, 0x03CC, -32}, {0x1F53, 0x1F54, 8}, {0x1FE5, 0x1FE6, 7}, + {0x03CC, 0x03CD, -64}, {0x1F55, 0x1F56, 8}, {0x2170, 0x2180, -16}, + {0x03CD, 0x03CF, -63}, {0x1F57, 0x1F58, 8}, {0x24D0, 0x24EA, -26}, + {0x0430, 0x0450, -32}, {0x1F60, 0x1F68, 8}, {0xFF41, 0xFF5B, -32}, + {0} + }; + static int uc_dup_table[][2] = { /* Start, End */ + {0x0100, 0x012F}, {0x01A0, 0x01A6}, {0x03E2, 0x03EF}, {0x04CB, 0x04CC}, + {0x0132, 0x0137}, {0x01B3, 0x01B7}, {0x0460, 0x0481}, {0x04D0, 0x04EB}, + {0x0139, 0x0149}, {0x01CD, 0x01DD}, {0x0490, 0x04BF}, {0x04EE, 0x04F5}, + {0x014A, 0x0178}, {0x01DE, 0x01EF}, {0x04BF, 0x04BF}, {0x04F8, 0x04F9}, + {0x0179, 0x017E}, {0x01F4, 0x01F5}, {0x04C1, 0x04C4}, {0x1E00, 0x1E95}, + {0x018B, 0x018B}, {0x01FA, 0x0218}, {0x04C7, 0x04C8}, {0x1EA0, 0x1EF9}, + {0} + }; + static int uc_byte_table[][2] = { /* Offset, Value */ + {0x00FF, 0x0178}, {0x01AD, 0x01AC}, {0x01F3, 0x01F1}, {0x0269, 0x0196}, + {0x0183, 0x0182}, {0x01B0, 0x01AF}, {0x0253, 0x0181}, {0x026F, 0x019C}, + {0x0185, 0x0184}, {0x01B9, 0x01B8}, {0x0254, 0x0186}, {0x0272, 0x019D}, + {0x0188, 0x0187}, {0x01BD, 0x01BC}, {0x0259, 0x018F}, {0x0275, 0x019F}, + {0x018C, 0x018B}, {0x01C6, 0x01C4}, {0x025B, 0x0190}, {0x0283, 0x01A9}, + {0x0192, 0x0191}, {0x01C9, 0x01C7}, {0x0260, 0x0193}, {0x0288, 0x01AE}, + {0x0199, 0x0198}, {0x01CC, 0x01CA}, {0x0263, 0x0194}, {0x0292, 0x01B7}, + {0x01A8, 0x01A7}, {0x01DD, 0x018E}, {0x0268, 0x0197}, + {0} + }; + int i, r; + + memset((char*)uc, 0, uc_len); + uc_len >>= 1; + if (uc_len > 65536) + uc_len = 65536; + for (i = 0; (u32)i < uc_len; i++) + uc[i] = i; + for (r = 0; uc_run_table[r][0]; r++) + for (i = uc_run_table[r][0]; i < uc_run_table[r][1]; i++) + uc[i] += uc_run_table[r][2]; + for (r = 0; uc_dup_table[r][0]; r++) + for (i = uc_dup_table[r][0]; i < uc_dup_table[r][1]; i += 2) + uc[i + 1]--; + for (r = 0; uc_byte_table[r][0]; r++) + uc[uc_byte_table[r][0]] = uc_byte_table[r][1]; +} + +/** + * ntfs_str2ucs - convert a string to a valid NTFS file name + * @s: input string + * @len: length of output buffer in Unicode characters + * + * Convert the input @s string into the corresponding little endian, + * 2-byte Unicode string. The length of the converted string is less + * or equal to the maximum length allowed by the NTFS format (255). + * + * If @s is NULL then return AT_UNNAMED. + * + * On success the function returns the Unicode string in an allocated + * buffer and the caller is responsible to free it when it's not needed + * anymore. + * + * On error NULL is returned and errno is set to the error code. + */ +ntfschar *ntfs_str2ucs(const char *s, int *len) +{ + ntfschar *ucs = NULL; + + if (s && ((*len = ntfs_mbstoucs(s, &ucs, 0)) == -1)) { + ntfs_log_perror("Couldn't convert '%s' to Unicode", s); + return NULL; + } + if (*len > NTFS_MAX_NAME_LEN) { + free(ucs); + errno = ENAMETOOLONG; + return NULL; + } + if (!ucs || !*len) { + ucs = AT_UNNAMED; + *len = 0; + } + return ucs; +} + +/** + * ntfs_ucsfree - free memory allocated by ntfs_str2ucs() + * @ucs input string to be freed + * + * Free memory at @ucs and which was allocated by ntfs_str2ucs. + * + * Return value: none. + */ +void ntfs_ucsfree(ntfschar *ucs) +{ + if (ucs && (ucs != AT_UNNAMED)) + free(ucs); +} + diff --git a/libntfs-3g/unix_io.c b/libntfs-3g/unix_io.c new file mode 100644 index 00000000..02ed2f49 --- /dev/null +++ b/libntfs-3g/unix_io.c @@ -0,0 +1,327 @@ +/** + * unix_io.c - Unix style disk io functions. Originated from the Linux-NTFS project. + * + * Copyright (c) 2000-2006 Anton Altaparmakov + * + * 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 + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program/include file 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 + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_UNISTD_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_STDIO_H +#include +#endif +#ifdef HAVE_SYS_TYPES_H +#include +#endif +#ifdef HAVE_SYS_STAT_H +#include +#endif +#ifdef HAVE_FCNTL_H +#include +#endif +#ifdef HAVE_SYS_IOCTL_H +#include +#endif +#ifdef HAVE_LINUX_FD_H +#include +#endif + +#include "types.h" +#include "mst.h" +#include "debug.h" +#include "device.h" +#include "logging.h" +#include "misc.h" + +#define DEV_FD(dev) (*(int *)dev->d_private) + +/* Define to nothing if not present on this system. */ +#ifndef O_EXCL +# define O_EXCL 0 +#endif + +/** + * ntfs_device_unix_io_open - Open a device and lock it exclusively + * @dev: + * @flags: + * + * Description... + * + * Returns: + */ +static int ntfs_device_unix_io_open(struct ntfs_device *dev, int flags) +{ + struct flock flk; + struct stat sbuf; + int err; + + if (NDevOpen(dev)) { + errno = EBUSY; + return -1; + } + dev->d_private = ntfs_malloc(sizeof(int)); + if (!dev->d_private) + return -1; + /* + * Open the device/file obtaining the file descriptor for exclusive + * access (but only if mounting r/w). + */ + if ((flags & O_RDWR) == O_RDWR) + flags |= O_EXCL; + *(int*)dev->d_private = open(dev->d_name, flags); + if (*(int*)dev->d_private == -1) { + err = errno; + goto err_out; + } + /* Setup our read-only flag. */ + if ((flags & O_RDWR) != O_RDWR) + NDevSetReadOnly(dev); + /* Acquire exclusive (mandatory) lock on the whole device. */ + memset(&flk, 0, sizeof(flk)); + if (NDevReadOnly(dev)) + flk.l_type = F_RDLCK; + else + flk.l_type = F_WRLCK; + flk.l_whence = SEEK_SET; + flk.l_start = flk.l_len = 0LL; + if (fcntl(DEV_FD(dev), F_SETLK, &flk)) { + err = errno; + ntfs_log_debug("ntfs_device_unix_io_open: Could not lock %s for %s\n", + dev->d_name, NDevReadOnly(dev) ? "reading" : "writing"); + if (close(DEV_FD(dev))) + ntfs_log_perror("ntfs_device_unix_io_open: Warning: Could not " + "close %s", dev->d_name); + goto err_out; + } + /* Determine if device is a block device or not, ignoring errors. */ + if (!fstat(DEV_FD(dev), &sbuf) && S_ISBLK(sbuf.st_mode)) + NDevSetBlock(dev); + /* Set our open flag. */ + NDevSetOpen(dev); + return 0; +err_out: + free(dev->d_private); + dev->d_private = NULL; + errno = err; + return -1; +} + +/** + * ntfs_device_unix_io_close - Close the device, releasing the lock + * @dev: + * + * Description... + * + * Returns: + */ +static int ntfs_device_unix_io_close(struct ntfs_device *dev) +{ + struct flock flk; + + if (!NDevOpen(dev)) { + errno = EBADF; + return -1; + } + if (NDevDirty(dev)) + fsync(DEV_FD(dev)); + /* Release exclusive (mandatory) lock on the whole device. */ + memset(&flk, 0, sizeof(flk)); + flk.l_type = F_UNLCK; + flk.l_whence = SEEK_SET; + flk.l_start = flk.l_len = 0LL; + if (fcntl(DEV_FD(dev), F_SETLK, &flk)) + ntfs_log_perror("ntfs_device_unix_io_close: Warning: Could not " + "unlock %s", dev->d_name); + /* Close the file descriptor and clear our open flag. */ + if (close(DEV_FD(dev))) + return -1; + NDevClearOpen(dev); + free(dev->d_private); + dev->d_private = NULL; + return 0; +} + +/** + * ntfs_device_unix_io_seek - Seek to a place on the device + * @dev: + * @offset: + * @whence: + * + * Description... + * + * Returns: + */ +static s64 ntfs_device_unix_io_seek(struct ntfs_device *dev, s64 offset, + int whence) +{ + return lseek(DEV_FD(dev), offset, whence); +} + +/** + * ntfs_device_unix_io_read - Read from the device, from the current location + * @dev: + * @buf: + * @count: + * + * Description... + * + * Returns: + */ +static s64 ntfs_device_unix_io_read(struct ntfs_device *dev, void *buf, + s64 count) +{ + return read(DEV_FD(dev), buf, count); +} + +/** + * ntfs_device_unix_io_write - Write to the device, at the current location + * @dev: + * @buf: + * @count: + * + * Description... + * + * Returns: + */ +static s64 ntfs_device_unix_io_write(struct ntfs_device *dev, const void *buf, + s64 count) +{ + if (NDevReadOnly(dev)) { + errno = EROFS; + return -1; + } + NDevSetDirty(dev); + return write(DEV_FD(dev), buf, count); +} + +/** + * ntfs_device_unix_io_pread - Perform a positioned read from the device + * @dev: + * @buf: + * @count: + * @offset: + * + * Description... + * + * Returns: + */ +static s64 ntfs_device_unix_io_pread(struct ntfs_device *dev, void *buf, + s64 count, s64 offset) +{ + return ntfs_pread(dev, offset, count, buf); +} + +/** + * ntfs_device_unix_io_pwrite - Perform a positioned write to the device + * @dev: + * @buf: + * @count: + * @offset: + * + * Description... + * + * Returns: + */ +static s64 ntfs_device_unix_io_pwrite(struct ntfs_device *dev, const void *buf, + s64 count, s64 offset) +{ + if (NDevReadOnly(dev)) { + errno = EROFS; + return -1; + } + NDevSetDirty(dev); + return ntfs_pwrite(dev, offset, count, buf); +} + +/** + * ntfs_device_unix_io_sync - Flush any buffered changes to the device + * @dev: + * + * Description... + * + * Returns: + */ +static int ntfs_device_unix_io_sync(struct ntfs_device *dev) +{ + if (!NDevReadOnly(dev) && NDevDirty(dev)) { + int res = fsync(DEV_FD(dev)); + if (!res) + NDevClearDirty(dev); + return res; + } + return 0; +} + +/** + * ntfs_device_unix_io_stat - Get information about the device + * @dev: + * @buf: + * + * Description... + * + * Returns: + */ +static int ntfs_device_unix_io_stat(struct ntfs_device *dev, struct stat *buf) +{ + return fstat(DEV_FD(dev), buf); +} + +/** + * ntfs_device_unix_io_ioctl - Perform an ioctl on the device + * @dev: + * @request: + * @argp: + * + * Description... + * + * Returns: + */ +static int ntfs_device_unix_io_ioctl(struct ntfs_device *dev, int request, + void *argp) +{ + return ioctl(DEV_FD(dev), request, argp); +} + +/** + * Device operations for working with unix style devices and files. + */ +struct ntfs_device_operations ntfs_device_unix_io_ops = { + .open = ntfs_device_unix_io_open, + .close = ntfs_device_unix_io_close, + .seek = ntfs_device_unix_io_seek, + .read = ntfs_device_unix_io_read, + .write = ntfs_device_unix_io_write, + .pread = ntfs_device_unix_io_pread, + .pwrite = ntfs_device_unix_io_pwrite, + .sync = ntfs_device_unix_io_sync, + .stat = ntfs_device_unix_io_stat, + .ioctl = ntfs_device_unix_io_ioctl, +}; diff --git a/libntfs-3g/version.c b/libntfs-3g/version.c new file mode 100644 index 00000000..76b742f8 --- /dev/null +++ b/libntfs-3g/version.c @@ -0,0 +1,40 @@ +/** + * version.c - Info about the NTFS library. Originated from the Linux-NTFS project. + * + * Copyright (c) 2005 Anton Altaparmakov + * Copyright (c) 2005 Richard Russon + * + * 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 + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program/include file 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 + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "version.h" + +/* FIXME: merge libntfs into the user space NTFS driver */ +static const char *libntfs_version_string = "22:0:0"; + +/** + * ntfs_libntfs_version - query version number of the ntfs library libntfs + * + * Returns pointer to a text string representing the version of libntfs. + */ +const char *ntfs_libntfs_version(void) +{ + return libntfs_version_string; +} diff --git a/libntfs-3g/volume.c b/libntfs-3g/volume.c new file mode 100644 index 00000000..7088ba83 --- /dev/null +++ b/libntfs-3g/volume.c @@ -0,0 +1,1526 @@ +/** + * volume.c - NTFS volume handling code. Originated from the Linux-NTFS project. + * + * Copyright (c) 2000-2006 Anton Altaparmakov + * Copyright (c) 2002-2006 Szabolcs Szakacsits + * Copyright (c) 2004-2005 Richard Russon + * + * 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 + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program/include file 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 + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STDIO_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif +#ifdef HAVE_FCNTL_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_SYS_STAT_H +#include +#endif +#ifdef HAVE_LIMITS_H +#include +#endif + +#include "volume.h" +#include "attrib.h" +#include "mft.h" +#include "bootsect.h" +#include "device.h" +#include "debug.h" +#include "inode.h" +#include "runlist.h" +#include "logfile.h" +#include "dir.h" +#include "logging.h" +#include "misc.h" + +#ifndef PATH_MAX +#define PATH_MAX 4096 +#endif + +/** + * ntfs_volume_alloc - Create an NTFS volume object and initialise it + * + * Description... + * + * Returns: + */ +ntfs_volume *ntfs_volume_alloc(void) +{ + return calloc(1, sizeof(ntfs_volume)); +} + +/** + * __ntfs_volume_release - Destroy an NTFS volume object + * @v: + * + * Description... + * + * Returns: + */ +static void __ntfs_volume_release(ntfs_volume *v) +{ + if (v->lcnbmp_ni && NInoDirty(v->lcnbmp_ni)) + ntfs_inode_sync(v->lcnbmp_ni); + if (v->vol_ni) + ntfs_inode_close(v->vol_ni); + if (v->lcnbmp_na) + ntfs_attr_close(v->lcnbmp_na); + if (v->lcnbmp_ni) + ntfs_inode_close(v->lcnbmp_ni); + if (v->mft_ni && NInoDirty(v->mft_ni)) + ntfs_inode_sync(v->mft_ni); + if (v->mftbmp_na) + ntfs_attr_close(v->mftbmp_na); + if (v->mft_na) + ntfs_attr_close(v->mft_na); + if (v->mft_ni) + ntfs_inode_close(v->mft_ni); + if (v->mftmirr_ni && NInoDirty(v->mftmirr_ni)) + ntfs_inode_sync(v->mftmirr_ni); + if (v->mftmirr_na) + ntfs_attr_close(v->mftmirr_na); + if (v->mftmirr_ni) + ntfs_inode_close(v->mftmirr_ni); + if (v->dev) { + struct ntfs_device *dev = v->dev; + + if (NDevDirty(dev)) + dev->d_ops->sync(dev); + if (dev->d_ops->close(dev)) + ntfs_log_perror("Eeek! Failed to close the device. Error: "); + } + free(v->vol_name); + free(v->upcase); + free(v->attrdef); + free(v); +} + +static void ntfs_attr_setup_flag(ntfs_inode *ni) +{ + STANDARD_INFORMATION *si; + + si = ntfs_attr_readall(ni, AT_STANDARD_INFORMATION, AT_UNNAMED, 0, NULL); + if (si) { + ni->flags = si->file_attributes; + free(si); + } +} + +/** + * ntfs_mft_load - load the $MFT and setup the ntfs volume with it + * @vol: ntfs volume whose $MFT to load + * + * Load $MFT from @vol and setup @vol with it. After calling this function the + * volume @vol is ready for use by all read access functions provided by the + * ntfs library. + * + * Return 0 on success and -1 on error with errno set to the error code. + */ +static int ntfs_mft_load(ntfs_volume *vol) +{ + VCN next_vcn, last_vcn, highest_vcn; + s64 l; + MFT_RECORD *mb = NULL; + ntfs_attr_search_ctx *ctx = NULL; + ATTR_RECORD *a; + int eo; + + /* Manually setup an ntfs_inode. */ + vol->mft_ni = ntfs_inode_allocate(vol); + mb = ntfs_malloc(vol->mft_record_size); + if (!vol->mft_ni || !mb) { + ntfs_log_perror("Error allocating memory for $MFT"); + goto error_exit; + } + vol->mft_ni->mft_no = 0; + vol->mft_ni->mrec = mb; + /* Can't use any of the higher level functions yet! */ + l = ntfs_mst_pread(vol->dev, vol->mft_lcn << vol->cluster_size_bits, 1, + vol->mft_record_size, mb); + if (l != 1) { + if (l != -1) + errno = EIO; + ntfs_log_perror("Error reading $MFT"); + goto error_exit; + } + if (ntfs_is_baad_record(mb->magic)) { + ntfs_log_debug("Error: Incomplete multi sector transfer detected in " + "$MFT.\n"); + goto io_error_exit; + } + if (!ntfs_is_mft_record(mb->magic)) { + ntfs_log_debug("Error: $MFT has invalid magic.\n"); + goto io_error_exit; + } + ctx = ntfs_attr_get_search_ctx(vol->mft_ni, NULL); + if (!ctx) { + ntfs_log_perror("Failed to allocate attribute search context"); + goto error_exit; + } + if (p2n(ctx->attr) < p2n(mb) || + (char*)ctx->attr > (char*)mb + vol->mft_record_size) { + ntfs_log_debug("Error: $MFT is corrupt.\n"); + goto io_error_exit; + } + /* Find the $ATTRIBUTE_LIST attribute in $MFT if present. */ + if (ntfs_attr_lookup(AT_ATTRIBUTE_LIST, AT_UNNAMED, 0, 0, 0, NULL, 0, + ctx)) { + if (errno != ENOENT) { + ntfs_log_debug("Error: $MFT has corrupt attribute list.\n"); + goto io_error_exit; + } + goto mft_has_no_attr_list; + } + NInoSetAttrList(vol->mft_ni); + l = ntfs_get_attribute_value_length(ctx->attr); + if (l <= 0 || l > 0x40000) { + ntfs_log_debug("Error: $MFT/$ATTRIBUTE_LIST has invalid length.\n"); + goto io_error_exit; + } + vol->mft_ni->attr_list_size = l; + vol->mft_ni->attr_list = ntfs_malloc(l); + if (!vol->mft_ni->attr_list) + goto error_exit; + + l = ntfs_get_attribute_value(vol, ctx->attr, vol->mft_ni->attr_list); + if (!l) { + ntfs_log_debug("Error: failed to get value of " + "$MFT/$ATTRIBUTE_LIST.\n"); + goto io_error_exit; + } + if (l != vol->mft_ni->attr_list_size) { + ntfs_log_debug("Error: got unexpected amount of data when reading " + "$MFT/$ATTRIBUTE_LIST.\n"); + goto io_error_exit; + } + +mft_has_no_attr_list: + + ntfs_attr_setup_flag(vol->mft_ni); + + /* We now have a fully setup ntfs inode for $MFT in vol->mft_ni. */ + + /* Get an ntfs attribute for $MFT/$DATA and set it up, too. */ + vol->mft_na = ntfs_attr_open(vol->mft_ni, AT_DATA, AT_UNNAMED, 0); + if (!vol->mft_na) { + ntfs_log_perror("Failed to open ntfs attribute"); + goto error_exit; + } + /* Read all extents from the $DATA attribute in $MFT. */ + ntfs_attr_reinit_search_ctx(ctx); + last_vcn = vol->mft_na->allocated_size >> vol->cluster_size_bits; + highest_vcn = next_vcn = 0; + a = NULL; + while (!ntfs_attr_lookup(AT_DATA, AT_UNNAMED, 0, 0, next_vcn, NULL, 0, + ctx)) { + runlist_element *nrl; + + a = ctx->attr; + /* $MFT must be non-resident. */ + if (!a->non_resident) { + ntfs_log_debug("$MFT must be non-resident but a resident " + "extent was found. $MFT is corrupt. Run " + "chkdsk.\n"); + goto io_error_exit; + } + /* $MFT must be uncompressed and unencrypted. */ + if (a->flags & ATTR_COMPRESSION_MASK || + a->flags & ATTR_IS_ENCRYPTED) { + ntfs_log_debug("$MFT must be uncompressed and unencrypted " + "but a compressed/encrypted extent was " + "found. $MFT is corrupt. Run chkdsk.\n"); + goto io_error_exit; + } + /* + * Decompress the mapping pairs array of this extent and merge + * the result into the existing runlist. No need for locking + * as we have exclusive access to the inode at this time and we + * are a mount in progress task, too. + */ + nrl = ntfs_mapping_pairs_decompress(vol, a, vol->mft_na->rl); + if (!nrl) { + ntfs_log_perror("ntfs_mapping_pairs_decompress() failed"); + goto error_exit; + } + vol->mft_na->rl = nrl; + + /* Get the lowest vcn for the next extent. */ + highest_vcn = sle64_to_cpu(a->highest_vcn); + next_vcn = highest_vcn + 1; + + /* Only one extent or error, which we catch below. */ + if (next_vcn <= 0) + break; + + /* Avoid endless loops due to corruption. */ + if (next_vcn < sle64_to_cpu(a->lowest_vcn)) { + ntfs_log_debug("$MFT has corrupt attribute list attribute. " + "Run chkdsk.\n"); + goto io_error_exit; + } + } + if (!a) { + ntfs_log_debug("$MFT/$DATA attribute not found. $MFT is corrupt. Run " + "chkdsk.\n"); + goto io_error_exit; + } + if (highest_vcn && highest_vcn != last_vcn - 1) { + ntfs_log_debug("Failed to load the complete runlist for $MFT/$DATA. " + "Bug or corrupt $MFT. Run chkdsk.\n"); + ntfs_log_debug("highest_vcn = 0x%llx, last_vcn - 1 = 0x%llx\n", + (long long)highest_vcn, (long long)last_vcn - 1); + goto io_error_exit; + } + /* Done with the $Mft mft record. */ + ntfs_attr_put_search_ctx(ctx); + ctx = NULL; + /* + * The volume is now setup so we can use all read access functions. + */ + vol->mftbmp_na = ntfs_attr_open(vol->mft_ni, AT_BITMAP, AT_UNNAMED, 0); + if (!vol->mftbmp_na) { + ntfs_log_perror("Failed to open $MFT/$BITMAP"); + goto error_exit; + } + return 0; +io_error_exit: + errno = EIO; +error_exit: + eo = errno; + if (ctx) + ntfs_attr_put_search_ctx(ctx); + if (vol->mft_na) { + ntfs_attr_close(vol->mft_na); + vol->mft_na = NULL; + } + if (vol->mft_ni) { + ntfs_inode_close(vol->mft_ni); + vol->mft_ni = NULL; + } + errno = eo; + return -1; +} + +/** + * ntfs_mftmirr_load - load the $MFTMirr and setup the ntfs volume with it + * @vol: ntfs volume whose $MFTMirr to load + * + * Load $MFTMirr from @vol and setup @vol with it. After calling this function + * the volume @vol is ready for use by all write access functions provided by + * the ntfs library (assuming ntfs_mft_load() has been called successfully + * beforehand). + * + * Return 0 on success and -1 on error with errno set to the error code. + */ +static int ntfs_mftmirr_load(ntfs_volume *vol) +{ + int err; + + vol->mftmirr_ni = ntfs_inode_open(vol, FILE_MFTMirr); + if (!vol->mftmirr_ni) { + ntfs_log_perror("Failed to open inode $MFTMirr"); + return -1; + } + + vol->mftmirr_na = ntfs_attr_open(vol->mftmirr_ni, AT_DATA, AT_UNNAMED, 0); + if (!vol->mftmirr_na) { + ntfs_log_perror("Failed to open $MFTMirr/$DATA"); + goto error_exit; + } + + if (ntfs_attr_map_runlist(vol->mftmirr_na, 0) < 0) { + ntfs_log_perror("Failed to map runlist of $MFTMirr/$DATA"); + goto error_exit; + } + + return 0; + +error_exit: + err = errno; + if (vol->mftmirr_na) { + ntfs_attr_close(vol->mftmirr_na); + vol->mftmirr_na = NULL; + } + ntfs_inode_close(vol->mftmirr_ni); + vol->mftmirr_ni = NULL; + errno = err; + return -1; +} + +/** + * ntfs_volume_startup - allocate and setup an ntfs volume + * @dev: device to open + * @flags: optional mount flags + * + * Load, verify, and parse bootsector; load and setup $MFT and $MFTMirr. After + * calling this function, the volume is setup sufficiently to call all read + * and write access functions provided by the library. + * + * Return the allocated volume structure on success and NULL on error with + * errno set to the error code. + */ +ntfs_volume *ntfs_volume_startup(struct ntfs_device *dev, unsigned long flags) +{ + LCN mft_zone_size, mft_lcn; + s64 br; + ntfs_volume *vol; + NTFS_BOOT_SECTOR *bs; + int eo; +#ifdef DEBUG + const char *OK = "OK\n"; + const char *FAILED = "FAILED\n"; + BOOL debug = 1; +#else + BOOL debug = 0; +#endif + + if (!dev || !dev->d_ops || !dev->d_name) { + errno = EINVAL; + return NULL; + } + + bs = ntfs_malloc(sizeof(NTFS_BOOT_SECTOR)); + if (!bs) + return NULL; + + /* Allocate the volume structure. */ + vol = ntfs_volume_alloc(); + if (!vol) + goto error_exit; + /* Create the default upcase table. */ + vol->upcase_len = 65536; + vol->upcase = ntfs_malloc(vol->upcase_len * sizeof(ntfschar)); + if (!vol->upcase) + goto error_exit; + + ntfs_upcase_table_build(vol->upcase, + vol->upcase_len * sizeof(ntfschar)); + if (flags & MS_RDONLY) + NVolSetReadOnly(vol); + if (flags & MS_NOATIME) + NVolSetNoATime(vol); + ntfs_log_debug("Reading bootsector... "); + if (dev->d_ops->open(dev, NVolReadOnly(vol) ? O_RDONLY: O_RDWR)) { + ntfs_log_debug(FAILED); + ntfs_log_perror("Error opening partition device"); + goto error_exit; + } + /* Attach the device to the volume. */ + vol->dev = dev; + /* Now read the bootsector. */ + br = ntfs_pread(dev, 0, sizeof(NTFS_BOOT_SECTOR), bs); + if (br != sizeof(NTFS_BOOT_SECTOR)) { + ntfs_log_debug(FAILED); + if (br != -1) + errno = EINVAL; + if (!br) + ntfs_log_debug("Error: partition is smaller than bootsector " + "size. Weird!\n"); + else + ntfs_log_perror("Error reading bootsector"); + goto error_exit; + } + ntfs_log_debug(OK); + if (!ntfs_boot_sector_is_ntfs(bs, !debug)) { + ntfs_log_debug("Error: %s is not a valid NTFS partition!\n", + dev->d_name); + errno = EINVAL; + goto error_exit; + } + if (ntfs_boot_sector_parse(vol, bs) < 0) { + ntfs_log_perror("Failed to parse ntfs bootsector"); + goto error_exit; + } + free(bs); + bs = NULL; + /* Now set the device block size to the sector size. */ + if (ntfs_device_block_size_set(vol->dev, vol->sector_size)) + ntfs_log_debug("Failed to set the device block size to the " + "sector size. This may affect performance " + "but should be harmless otherwise. Error: " + "%s\n", strerror(errno)); + /* + * We now initialize the cluster allocator. + * + * FIXME: Move this to its own function? (AIA) + */ + + // TODO: Make this tunable at mount time. (AIA) + vol->mft_zone_multiplier = 1; + + /* Determine the size of the MFT zone. */ + mft_zone_size = vol->nr_clusters; + switch (vol->mft_zone_multiplier) { /* % of volume size in clusters */ + case 4: + mft_zone_size >>= 1; /* 50% */ + break; + case 3: + mft_zone_size = mft_zone_size * 3 >> 3; /* 37.5% */ + break; + case 2: + mft_zone_size >>= 2; /* 25% */ + break; + /* case 1: */ + default: + mft_zone_size >>= 3; /* 12.5% */ + break; + } + + /* Setup the mft zone. */ + vol->mft_zone_start = vol->mft_zone_pos = vol->mft_lcn; + ntfs_log_debug("mft_zone_pos = 0x%llx\n", (long long)vol->mft_zone_pos); + + /* + * Calculate the mft_lcn for an unmodified NTFS volume (see mkntfs + * source) and if the actual mft_lcn is in the expected place or even + * further to the front of the volume, extend the mft_zone to cover the + * beginning of the volume as well. This is in order to protect the + * area reserved for the mft bitmap as well within the mft_zone itself. + * On non-standard volumes we don't protect it as the overhead would be + * higher than the speed increase we would get by doing it. + */ + mft_lcn = (8192 + 2 * vol->cluster_size - 1) / vol->cluster_size; + if (mft_lcn * vol->cluster_size < 16 * 1024) + mft_lcn = (16 * 1024 + vol->cluster_size - 1) / + vol->cluster_size; + if (vol->mft_zone_start <= mft_lcn) + vol->mft_zone_start = 0; + ntfs_log_debug("mft_zone_start = 0x%llx\n", (long long)vol->mft_zone_start); + + /* + * Need to cap the mft zone on non-standard volumes so that it does + * not point outside the boundaries of the volume. We do this by + * halving the zone size until we are inside the volume. + */ + vol->mft_zone_end = vol->mft_lcn + mft_zone_size; + while (vol->mft_zone_end >= vol->nr_clusters) { + mft_zone_size >>= 1; + vol->mft_zone_end = vol->mft_lcn + mft_zone_size; + } + ntfs_log_debug("mft_zone_end = 0x%llx\n", (long long)vol->mft_zone_end); + + /* + * Set the current position within each data zone to the start of the + * respective zone. + */ + vol->data1_zone_pos = vol->mft_zone_end; + ntfs_log_debug("data1_zone_pos = 0x%llx\n", vol->data1_zone_pos); + vol->data2_zone_pos = 0; + ntfs_log_debug("data2_zone_pos = 0x%llx\n", vol->data2_zone_pos); + + /* Set the mft data allocation position to mft record 24. */ + vol->mft_data_pos = 24; + + /* + * The cluster allocator is now fully operational. + */ + + /* Need to setup $MFT so we can use the library read functions. */ + ntfs_log_debug("Loading $MFT... "); + if (ntfs_mft_load(vol) < 0) { + ntfs_log_debug(FAILED); + ntfs_log_perror("Failed to load $MFT"); + goto error_exit; + } + ntfs_log_debug(OK); + + /* Need to setup $MFTMirr so we can use the write functions, too. */ + ntfs_log_debug("Loading $MFTMirr... "); + if (ntfs_mftmirr_load(vol) < 0) { + ntfs_log_debug(FAILED); + ntfs_log_perror("Failed to load $MFTMirr"); + goto error_exit; + } + ntfs_log_debug(OK); + return vol; +error_exit: + eo = errno; + free(bs); + if (vol) + __ntfs_volume_release(vol); + errno = eo; + return NULL; +} + +/** + * ntfs_volume_check_logfile - check logfile on target volume + * @vol: volume on which to check logfile + * + * Return 0 on success and -1 on error with errno set error code. + */ +static int ntfs_volume_check_logfile(ntfs_volume *vol) +{ + ntfs_inode *ni; + ntfs_attr *na = NULL; + RESTART_PAGE_HEADER *rp = NULL; + int err = 0; + + if ((ni = ntfs_inode_open(vol, FILE_LogFile)) == NULL) { + ntfs_log_debug("Failed to open inode FILE_LogFile.\n"); + errno = EIO; + return -1; + } + if ((na = ntfs_attr_open(ni, AT_DATA, AT_UNNAMED, 0)) == NULL) { + ntfs_log_debug("Failed to open $FILE_LogFile/$DATA\n"); + err = EIO; + goto exit; + } + if (!ntfs_check_logfile(na, &rp) || !ntfs_is_logfile_clean(na, rp)) + err = EOPNOTSUPP; + free(rp); +exit: + if (na) + ntfs_attr_close(na); + ntfs_inode_close(ni); + if (err) { + errno = err; + return -1; + } + return 0; +} + +/** + * ntfs_hiberfile_open - Find and open '/hiberfil.sys' + * @vol: An ntfs volume obtained from ntfs_mount + * + * Return: inode Success, hiberfil.sys is valid + * NULL hiberfil.sys doesn't exist or some other error occurred + */ +static ntfs_inode *ntfs_hiberfile_open(ntfs_volume *vol) +{ + u64 inode; + ntfs_inode *ni_root; + ntfs_inode *ni_hibr = NULL; + ntfschar *unicode = NULL; + int unicode_len; + const char *hiberfile = "hiberfil.sys"; + + if (!vol) { + errno = EINVAL; + return NULL; + } + + ni_root = ntfs_inode_open(vol, FILE_root); + if (!ni_root) { + ntfs_log_debug("Couldn't open the root directory.\n"); + return NULL; + } + + unicode_len = ntfs_mbstoucs(hiberfile, &unicode, 0); + if (unicode_len < 0) { + ntfs_log_perror("Couldn't convert 'hiberfil.sys' to Unicode"); + goto out; + } + + inode = ntfs_inode_lookup_by_name(ni_root, unicode, unicode_len); + if (inode == (u64)-1) { + ntfs_log_debug("Couldn't find file '%s'.\n", hiberfile); + goto out; + } + + inode = MREF(inode); + ni_hibr = ntfs_inode_open(vol, inode); + if (!ni_hibr) { + ntfs_log_debug("Couldn't open inode %lld.\n", (long long)inode); + goto out; + } +out: + ntfs_inode_close(ni_root); + free(unicode); + return ni_hibr; +} + + +#define NTFS_HIBERFILE_HEADER_SIZE 4096 + +/** + * ntfs_volume_check_hiberfile - check hiberfil.sys whether Windows is + * hibernated on the target volume + * @vol: volume on which to check hiberfil.sys + * + * Return: 0 if Windows isn't hibernated for sure + * -1 otherwise and errno is set to the appropriate value + */ +static int ntfs_volume_check_hiberfile(ntfs_volume *vol) +{ + ntfs_inode *ni; + ntfs_attr *na = NULL; + int i, bytes_read, ret = -1; + char *buf = NULL; + + ni = ntfs_hiberfile_open(vol); + if (!ni) { + if (errno == ENOENT) + return 0; + return -1; + } + + buf = ntfs_malloc(NTFS_HIBERFILE_HEADER_SIZE); + if (!buf) + goto out; + + na = ntfs_attr_open(ni, AT_DATA, AT_UNNAMED, 0); + if (!na) { + ntfs_log_perror("Failed to open hiberfil.sys data attribute"); + goto out; + } + + bytes_read = ntfs_attr_pread(na, 0, NTFS_HIBERFILE_HEADER_SIZE, buf); + if (bytes_read == -1) { + ntfs_log_perror("Failed to read hiberfil.sys"); + goto out; + } + if (bytes_read < NTFS_HIBERFILE_HEADER_SIZE) { + ntfs_log_debug("Hibernated non-system partition, refused to " + "mount!\n"); + errno = EPERM; + goto out; + } + if (memcmp(buf, "hibr", 4) == 0) { + ntfs_log_debug("Windows is hibernated, refused to mount!\n"); + errno = EPERM; + goto out; + } + for (i = 0; i < NTFS_HIBERFILE_HEADER_SIZE; i++) { + if (buf[i]) { + ntfs_log_debug("Windows is hibernated, won't mount!\n"); + errno = EPERM; + goto out; + } + } + /* All right, all header bytes are zero */ + ret = 0; +out: + if (na) + ntfs_attr_close(na); + free(buf); + ntfs_inode_close(ni); + return ret; +} + +/** + * ntfs_device_mount - open ntfs volume + * @dev: device to open + * @flags: optional mount flags + * + * This function mounts an ntfs volume. @dev should describe the device which + * to mount as the ntfs volume. + * + * @flags is an optional second parameter. The same flags are used as for + * the mount system call (man 2 mount). Currently only the following flags + * are implemented: + * MS_RDONLY - mount volume read-only + * MS_NOATIME - do not update access time + * + * The function opens the device @dev and verifies that it contains a valid + * bootsector. Then, it allocates an ntfs_volume structure and initializes + * some of the values inside the structure from the information stored in the + * bootsector. It proceeds to load the necessary system files and completes + * setting up the structure. + * + * Return the allocated volume structure on success and NULL on error with + * errno set to the error code. + */ +ntfs_volume *ntfs_device_mount(struct ntfs_device *dev, unsigned long flags) +{ + s64 l; +#ifdef DEBUG + const char *OK = "OK\n"; + const char *FAILED = "FAILED\n"; +#endif + ntfs_volume *vol; + u8 *m = NULL, *m2 = NULL; + ntfs_attr_search_ctx *ctx = NULL; + ntfs_inode *ni; + ntfs_attr *na; + ATTR_RECORD *a; + VOLUME_INFORMATION *vinf; + ntfschar *vname; + int i, j, eo; + u32 u; + + vol = ntfs_volume_startup(dev, flags); + if (!vol) { + ntfs_log_perror("Failed to startup volume"); + return NULL; + } + + /* Load data from $MFT and $MFTMirr and compare the contents. */ + m = ntfs_malloc(vol->mftmirr_size << vol->mft_record_size_bits); + m2 = ntfs_malloc(vol->mftmirr_size << vol->mft_record_size_bits); + if (!m || !m2) + goto error_exit; + + l = ntfs_attr_mst_pread(vol->mft_na, 0, vol->mftmirr_size, + vol->mft_record_size, m); + if (l != vol->mftmirr_size) { + if (l == -1) + ntfs_log_perror("Failed to read $MFT"); + else { + ntfs_log_debug("Failed to read $MFT, unexpected length " + "(%d != %lld).\n", vol->mftmirr_size, l); + errno = EIO; + } + goto error_exit; + } + l = ntfs_attr_mst_pread(vol->mftmirr_na, 0, vol->mftmirr_size, + vol->mft_record_size, m2); + if (l != vol->mftmirr_size) { + if (l == 4) + vol->mftmirr_size = 4; + else { + if (l == -1) + ntfs_log_perror("Failed to read $MFTMirr"); + else { + ntfs_log_error("Failed to read $MFTMirr " + "unexpected length (%d != %lld)." + "\n", vol->mftmirr_size, l); + errno = EIO; + } + goto error_exit; + } + } + ntfs_log_debug("Comparing $MFTMirr to $MFT... "); + for (i = 0; i < vol->mftmirr_size; ++i) { + MFT_RECORD *mrec, *mrec2; + const char *ESTR[12] = { "$MFT", "$MFTMirr", "$LogFile", + "$Volume", "$AttrDef", "root directory", "$Bitmap", + "$Boot", "$BadClus", "$Secure", "$UpCase", "$Extend" }; + const char *s; + + if (i < 12) + s = ESTR[i]; + else if (i < 16) + s = "system file"; + else + s = "mft record"; + + mrec = (MFT_RECORD*)(m + i * vol->mft_record_size); + if (mrec->flags & MFT_RECORD_IN_USE) { + if (ntfs_is_baad_recordp(mrec)) { + ntfs_log_debug("FAILED\n"); + ntfs_log_debug("$MFT error: Incomplete multi " + "sector transfer detected in " + "%s.\n", s); + goto io_error_exit; + } + if (!ntfs_is_mft_recordp(mrec)) { + ntfs_log_debug("FAILED\n"); + ntfs_log_debug("$MFT error: Invalid mft " + "record for %s.\n", s); + goto io_error_exit; + } + } + mrec2 = (MFT_RECORD*)(m2 + i * vol->mft_record_size); + if (mrec2->flags & MFT_RECORD_IN_USE) { + if (ntfs_is_baad_recordp(mrec2)) { + ntfs_log_debug("FAILED\n"); + ntfs_log_debug("$MFTMirr error: Incomplete " + "multi sector transfer " + "detected in %s.\n", s); + goto io_error_exit; + } + if (!ntfs_is_mft_recordp(mrec2)) { + ntfs_log_debug("FAILED\n"); + ntfs_log_debug("$MFTMirr error: Invalid mft " + "record for %s.\n", s); + goto io_error_exit; + } + } + if (memcmp(mrec, mrec2, ntfs_mft_record_get_data_size(mrec))) { + ntfs_log_debug(FAILED); + ntfs_log_debug("$MFTMirr does not match $MFT. Run " + "chkdsk.\n"); + goto io_error_exit; + } + } + ntfs_log_debug(OK); + + free(m2); + free(m); + m = m2 = NULL; + + /* Now load the bitmap from $Bitmap. */ + ntfs_log_debug("Loading $Bitmap... "); + vol->lcnbmp_ni = ntfs_inode_open(vol, FILE_Bitmap); + if (!vol->lcnbmp_ni) { + ntfs_log_debug(FAILED); + ntfs_log_perror("Failed to open inode"); + goto error_exit; + } + /* Get an ntfs attribute for $Bitmap/$DATA. */ + vol->lcnbmp_na = ntfs_attr_open(vol->lcnbmp_ni, AT_DATA, AT_UNNAMED, 0); + if (!vol->lcnbmp_na) { + ntfs_log_debug(FAILED); + ntfs_log_perror("Failed to open ntfs attribute"); + goto error_exit; + } + /* Done with the $Bitmap mft record. */ + ntfs_log_debug(OK); + + /* Now load the upcase table from $UpCase. */ + ntfs_log_debug("Loading $UpCase... "); + ni = ntfs_inode_open(vol, FILE_UpCase); + if (!ni) { + ntfs_log_debug(FAILED); + ntfs_log_perror("Failed to open inode"); + goto error_exit; + } + /* Get an ntfs attribute for $UpCase/$DATA. */ + na = ntfs_attr_open(ni, AT_DATA, AT_UNNAMED, 0); + if (!na) { + ntfs_log_debug(FAILED); + ntfs_log_perror("Failed to open ntfs attribute"); + goto error_exit; + } + /* + * Note: Normally, the upcase table has a length equal to 65536 + * 2-byte Unicode characters but allow for different cases, so no + * checks done. Just check we don't overflow 32-bits worth of Unicode + * characters. + */ + if (na->data_size & ~0x1ffffffffULL) { + ntfs_log_debug(FAILED); + ntfs_log_debug("Error: Upcase table is too big (max 32-bit " + "allowed).\n"); + errno = EINVAL; + goto error_exit; + } + if (vol->upcase_len != na->data_size >> 1) { + vol->upcase_len = na->data_size >> 1; + /* Throw away default table. */ + free(vol->upcase); + vol->upcase = ntfs_malloc(na->data_size); + if (!vol->upcase) { + ntfs_log_debug(FAILED); + goto error_exit; + } + } + /* Read in the $DATA attribute value into the buffer. */ + l = ntfs_attr_pread(na, 0, na->data_size, vol->upcase); + if (l != na->data_size) { + ntfs_log_debug(FAILED); + ntfs_log_debug("Amount of data read does not correspond to expected " + "length!\n"); + errno = EIO; + goto error_exit; + } + /* Done with the $UpCase mft record. */ + ntfs_log_debug(OK); + ntfs_attr_close(na); + if (ntfs_inode_close(ni)) + ntfs_log_perror("Failed to close inode, leaking memory"); + + /* + * Now load $Volume and set the version information and flags in the + * vol structure accordingly. + */ + ntfs_log_debug("Loading $Volume... "); + vol->vol_ni = ntfs_inode_open(vol, FILE_Volume); + if (!vol->vol_ni) { + ntfs_log_debug(FAILED); + ntfs_log_perror("Failed to open inode"); + goto error_exit; + } + /* Get a search context for the $Volume/$VOLUME_INFORMATION lookup. */ + ctx = ntfs_attr_get_search_ctx(vol->vol_ni, NULL); + if (!ctx) { + ntfs_log_debug(FAILED); + ntfs_log_perror("Failed to allocate attribute search context"); + goto error_exit; + } + /* Find the $VOLUME_INFORMATION attribute. */ + if (ntfs_attr_lookup(AT_VOLUME_INFORMATION, AT_UNNAMED, 0, 0, 0, NULL, + 0, ctx)) { + ntfs_log_debug(FAILED); + ntfs_log_debug("$VOLUME_INFORMATION attribute not found in " + "$Volume?!?\n"); + goto error_exit; + } + a = ctx->attr; + /* Has to be resident. */ + if (a->non_resident) { + ntfs_log_debug(FAILED); + ntfs_log_debug("Error: Attribute $VOLUME_INFORMATION must be " + "resident (and it isn't)!\n"); + errno = EIO; + goto error_exit; + } + /* Get a pointer to the value of the attribute. */ + vinf = (VOLUME_INFORMATION*)(le16_to_cpu(a->value_offset) + (char*)a); + /* Sanity checks. */ + if ((char*)vinf + le32_to_cpu(a->value_length) > (char*)ctx->mrec + + le32_to_cpu(ctx->mrec->bytes_in_use) || + le16_to_cpu(a->value_offset) + le32_to_cpu( + a->value_length) > le32_to_cpu(a->length)) { + ntfs_log_debug(FAILED); + ntfs_log_debug("Error: Attribute $VOLUME_INFORMATION in $Volume is " + "corrupt!\n"); + errno = EIO; + goto error_exit; + } + /* Setup vol from the volume information attribute value. */ + vol->major_ver = vinf->major_ver; + vol->minor_ver = vinf->minor_ver; + /* Do not use le16_to_cpu() macro here as our VOLUME_FLAGS are + defined using cpu_to_le16() macro and hence are consistent. */ + vol->flags = vinf->flags; + /* + * Reinitialize the search context for the $Volume/$VOLUME_NAME lookup. + */ + ntfs_attr_reinit_search_ctx(ctx); + if (ntfs_attr_lookup(AT_VOLUME_NAME, AT_UNNAMED, 0, 0, 0, NULL, 0, + ctx)) { + if (errno != ENOENT) { + ntfs_log_debug(FAILED); + ntfs_log_debug("Error: Lookup of $VOLUME_NAME attribute in " + "$Volume failed. This probably means " + "something is corrupt. Run chkdsk.\n"); + goto error_exit; + } + /* + * Attribute not present. This has been seen in the field. + * Treat this the same way as if the attribute was present but + * had zero length. + */ + vol->vol_name = ntfs_malloc(1); + if (!vol->vol_name) { + ntfs_log_debug(FAILED); + goto error_exit; + } + vol->vol_name[0] = '\0'; + } else { + a = ctx->attr; + /* Has to be resident. */ + if (a->non_resident) { + ntfs_log_debug(FAILED); + ntfs_log_debug("Error: Attribute $VOLUME_NAME must be " + "resident!\n"); + errno = EIO; + goto error_exit; + } + /* Get a pointer to the value of the attribute. */ + vname = (ntfschar*)(le16_to_cpu(a->value_offset) + (char*)a); + u = le32_to_cpu(a->value_length) / 2; + /* + * Convert Unicode volume name to current locale multibyte + * format. + */ + vol->vol_name = NULL; + if (ntfs_ucstombs(vname, u, &vol->vol_name, 0) == -1) { + ntfs_log_perror("Error: Volume name could not be converted " + "to current locale"); + ntfs_log_debug("Forcing name into ASCII by replacing " + "non-ASCII characters with underscores.\n"); + vol->vol_name = ntfs_malloc(u + 1); + if (!vol->vol_name) { + ntfs_log_debug(FAILED); + goto error_exit; + } + for (j = 0; j < (s32)u; j++) { + ntfschar uc = le16_to_cpu(vname[j]); + if (uc > 0xff) + uc = (ntfschar)'_'; + vol->vol_name[j] = (char)uc; + } + vol->vol_name[u] = '\0'; + } + } + ntfs_log_debug(OK); + ntfs_attr_put_search_ctx(ctx); + ctx = NULL; + /* Now load the attribute definitions from $AttrDef. */ + ntfs_log_debug("Loading $AttrDef... "); + ni = ntfs_inode_open(vol, FILE_AttrDef); + if (!ni) { + ntfs_log_debug(FAILED); + ntfs_log_perror("Failed to open inode"); + goto error_exit; + } + /* Get an ntfs attribute for $AttrDef/$DATA. */ + na = ntfs_attr_open(ni, AT_DATA, AT_UNNAMED, 0); + if (!na) { + ntfs_log_debug(FAILED); + ntfs_log_perror("Failed to open ntfs attribute"); + goto error_exit; + } + /* Check we don't overflow 32-bits. */ + if (na->data_size > 0xffffffffLL) { + ntfs_log_debug(FAILED); + ntfs_log_debug("Error: Attribute definition table is too big (max " + "32-bit allowed).\n"); + errno = EINVAL; + goto error_exit; + } + vol->attrdef_len = na->data_size; + vol->attrdef = ntfs_malloc(na->data_size); + if (!vol->attrdef) { + ntfs_log_debug(FAILED); + goto error_exit; + } + /* Read in the $DATA attribute value into the buffer. */ + l = ntfs_attr_pread(na, 0, na->data_size, vol->attrdef); + if (l != na->data_size) { + ntfs_log_debug(FAILED); + ntfs_log_debug("Amount of data read does not correspond to expected " + "length!\n"); + errno = EIO; + goto error_exit; + } + /* Done with the $AttrDef mft record. */ + ntfs_log_debug(OK); + ntfs_attr_close(na); + if (ntfs_inode_close(ni)) + ntfs_log_perror("Failed to close inode, leaking memory"); + /* + * Check for dirty logfile and hibernated Windows. + * We care only about read-write mounts. + */ + if (!(flags & MS_RDONLY)) { + if (ntfs_volume_check_logfile(vol) < 0) + goto error_exit; + if (ntfs_volume_check_hiberfile(vol) < 0) + goto error_exit; + } + + return vol; +io_error_exit: + errno = EIO; +error_exit: + eo = errno; + if (ctx) + ntfs_attr_put_search_ctx(ctx); + free(m); + free(m2); + __ntfs_volume_release(vol); + errno = eo; + return NULL; +} + +/** + * ntfs_mount - open ntfs volume + * @name: name of device/file to open + * @flags: optional mount flags + * + * This function mounts an ntfs volume. @name should contain the name of the + * device/file to mount as the ntfs volume. + * + * @flags is an optional second parameter. The same flags are used as for + * the mount system call (man 2 mount). Currently only the following flags + * are implemented: + * MS_RDONLY - mount volume read-only + * MS_NOATIME - do not update access time + * + * The function opens the device or file @name and verifies that it contains a + * valid bootsector. Then, it allocates an ntfs_volume structure and initializes + * some of the values inside the structure from the information stored in the + * bootsector. It proceeds to load the necessary system files and completes + * setting up the structure. + * + * Return the allocated volume structure on success and NULL on error with + * errno set to the error code. + * + * Note, that a copy is made of @name, and hence it can be discarded as + * soon as the function returns. + */ +ntfs_volume *ntfs_mount(const char *name __attribute__((unused)), + unsigned long flags __attribute__((unused))) +{ +#ifndef NO_NTFS_DEVICE_DEFAULT_IO_OPS + struct ntfs_device *dev; + ntfs_volume *vol; + + /* Allocate an ntfs_device structure. */ + dev = ntfs_device_alloc(name, 0, &ntfs_device_default_io_ops, NULL); + if (!dev) + return NULL; + /* Call ntfs_device_mount() to do the actual mount. */ + vol = ntfs_device_mount(dev, flags); + if (!vol) { + int eo = errno; + ntfs_device_free(dev); + errno = eo; + } + return vol; +#else + /* + * ntfs_mount() makes no sense if NO_NTFS_DEVICE_DEFAULT_IO_OPS is + * defined as there are no device operations available in libntfs in + * this case. + */ + errno = EOPNOTSUPP; + return NULL; +#endif +} + +/** + * ntfs_device_umount - close ntfs volume + * @vol: address of ntfs_volume structure of volume to close + * @force: if true force close the volume even if it is busy + * + * Deallocate all structures (including @vol itself) associated with the ntfs + * volume @vol. + * + * Note it is up to the caller to destroy the device associated with the volume + * being unmounted after this function returns. + * + * Return 0 on success. On error return -1 with errno set appropriately + * (most likely to one of EAGAIN, EBUSY or EINVAL). The EAGAIN error means that + * an operation is in progress and if you try the close later the operation + * might be completed and the close succeed. + * + * If @force is true (i.e. not zero) this function will close the volume even + * if this means that data might be lost. + * + * @vol must have previously been returned by a call to ntfs_device_mount(). + * + * @vol itself is deallocated and should no longer be dereferenced after this + * function returns success. If it returns an error then nothing has been done + * so it is safe to continue using @vol. + */ +int ntfs_device_umount(ntfs_volume *vol, + const BOOL force __attribute__((unused))) +{ + if (!vol) { + errno = EINVAL; + return -1; + } + __ntfs_volume_release(vol); + return 0; +} + +/** + * ntfs_umount - close ntfs volume + * @vol: address of ntfs_volume structure of volume to close + * @force: if true force close the volume even if it is busy + * + * Deallocate all structures (including @vol itself) associated with the ntfs + * volume @vol. + * + * Return 0 on success. On error return -1 with errno set appropriately + * (most likely to one of EAGAIN, EBUSY or EINVAL). The EAGAIN error means that + * an operation is in progress and if you try the close later the operation + * might be completed and the close succeed. + * + * If @force is true (i.e. not zero) this function will close the volume even + * if this means that data might be lost. + * + * @vol must have previously been returned by a call to ntfs_mount(). + * + * @vol itself is deallocated and should no longer be dereferenced after this + * function returns success. If it returns an error then nothing has been done + * so it is safe to continue using @vol. + */ +int ntfs_umount(ntfs_volume *vol, + const BOOL force __attribute__((unused))) +{ + struct ntfs_device *dev; + + if (!vol) { + errno = EINVAL; + return -1; + } + dev = vol->dev; + __ntfs_volume_release(vol); + ntfs_device_free(dev); + return 0; +} + +#ifdef HAVE_MNTENT_H + +#ifndef HAVE_REALPATH +/** + * realpath - If there is no realpath on the system + */ +static char *realpath(const char *path, char *resolved_path) +{ + strncpy(resolved_path, path, PATH_MAX); + resolved_path[PATH_MAX] = '\0'; + return resolved_path; +} +#endif + +/** + * ntfs_mntent_check - desc + * + * If you are wanting to use this, you actually wanted to use + * ntfs_check_if_mounted(), you just didn't realize. (-: + * + * See description of ntfs_check_if_mounted(), below. + */ +static int ntfs_mntent_check(const char *file, unsigned long *mnt_flags) +{ + struct mntent *mnt; + char *real_file = NULL, *real_fsname = NULL; + FILE *f; + int err = 0; + + real_file = ntfs_malloc(PATH_MAX + 1); + if (!real_file) + return -1; + real_fsname = ntfs_malloc(PATH_MAX + 1); + if (!real_fsname) { + err = errno; + goto exit; + } + if (!realpath(file, real_file)) { + err = errno; + goto exit; + } + if (!(f = setmntent(MOUNTED, "r"))) { + err = errno; + goto exit; + } + while ((mnt = getmntent(f))) { + if (!realpath(mnt->mnt_fsname, real_fsname)) + continue; + if (!strcmp(real_file, real_fsname)) + break; + } + endmntent(f); + if (!mnt) + goto exit; + *mnt_flags = NTFS_MF_MOUNTED; + if (!strcmp(mnt->mnt_dir, "/")) + *mnt_flags |= NTFS_MF_ISROOT; +#ifdef HAVE_HASMNTOPT + if (hasmntopt(mnt, "ro") && !hasmntopt(mnt, "rw")) + *mnt_flags |= NTFS_MF_READONLY; +#endif +exit: + free(real_file); + free(real_fsname); + if (err) { + errno = err; + return -1; + } + return 0; +} +#endif /* HAVE_MNTENT_H */ + +/** + * ntfs_check_if_mounted - check if an ntfs volume is currently mounted + * @file: device file to check + * @mnt_flags: pointer into which to return the ntfs mount flags (see volume.h) + * + * If the running system does not support the {set,get,end}mntent() calls, + * just return 0 and set *@mnt_flags to zero. + * + * When the system does support the calls, ntfs_check_if_mounted() first tries + * to find the device @file in /etc/mtab (or wherever this is kept on the + * running system). If it is not found, assume the device is not mounted and + * return 0 and set *@mnt_flags to zero. + * + * If the device @file is found, set the NTFS_MF_MOUNTED flags in *@mnt_flags. + * + * Further if @file is mounted as the file system root ("/"), set the flag + * NTFS_MF_ISROOT in *@mnt_flags. + * + * Finally, check if the file system is mounted read-only, and if so set the + * NTFS_MF_READONLY flag in *@mnt_flags. + * + * On success return 0 with *@mnt_flags set to the ntfs mount flags. + * + * On error return -1 with errno set to the error code. + */ +int ntfs_check_if_mounted(const char *file __attribute__((unused)), + unsigned long *mnt_flags) +{ + *mnt_flags = 0; +#ifdef HAVE_MNTENT_H + return ntfs_mntent_check(file, mnt_flags); +#else + return 0; +#endif +} + +/** + * ntfs_version_is_supported - check if NTFS version is supported. + * @vol: ntfs volume whose version we're interested in. + * + * The function checks if the NTFS volume version is known or not. + * Version 1.1 and 1.2 are used by Windows NT3.x and NT4. + * Version 2.x is used by Windows 2000 Betas. + * Version 3.0 is used by Windows 2000. + * Version 3.1 is used by Windows XP, Windows Server 2003 and Longhorn. + * + * Return 0 if NTFS version is supported otherwise -1 with errno set. + * + * The following error codes are defined: + * EOPNOTSUPP - Unknown NTFS version + * EINVAL - Invalid argument + */ +int ntfs_version_is_supported(ntfs_volume *vol) +{ + u8 major, minor; + + if (!vol) { + errno = EINVAL; + return -1; + } + + major = vol->major_ver; + minor = vol->minor_ver; + + if (NTFS_V1_1(major, minor) || NTFS_V1_2(major, minor)) + return 0; + + if (NTFS_V2_X(major, minor)) + return 0; + + if (NTFS_V3_0(major, minor) || NTFS_V3_1(major, minor)) + return 0; + + errno = EOPNOTSUPP; + return -1; +} + +/** + * ntfs_logfile_reset - "empty" $LogFile data attribute value + * @vol: ntfs volume whose $LogFile we intend to reset. + * + * Fill the value of the $LogFile data attribute, i.e. the contents of + * the file, with 0xff's, thus marking the journal as empty. + * + * FIXME(?): We might need to zero the LSN field of every single mft + * record as well. (But, first try without doing that and see what + * happens, since chkdsk might pickup the pieces and do it for us...) + * + * On success return 0. + * + * On error return -1 with errno set to the error code. + */ +int ntfs_logfile_reset(ntfs_volume *vol) +{ + ntfs_inode *ni; + ntfs_attr *na; + int eo; + + if (!vol) { + errno = EINVAL; + return -1; + } + + if ((ni = ntfs_inode_open(vol, FILE_LogFile)) == NULL) { + ntfs_log_perror("Failed to open inode FILE_LogFile."); + return -1; + } + + if ((na = ntfs_attr_open(ni, AT_DATA, AT_UNNAMED, 0)) == NULL) { + eo = errno; + ntfs_log_perror("Failed to open $FILE_LogFile/$DATA"); + goto error_exit; + } + + if (ntfs_empty_logfile(na)) { + eo = errno; + ntfs_log_perror("Failed to empty $FILE_LogFile/$DATA"); + ntfs_attr_close(na); + goto error_exit; + } + ntfs_attr_close(na); + return ntfs_inode_close(ni); + +error_exit: + ntfs_inode_close(ni); + errno = eo; + return -1; +} + +/** + * ntfs_volume_write_flags - set the flags of an ntfs volume + * @vol: ntfs volume where we set the volume flags + * @flags: new flags + * + * Set the on-disk volume flags in the mft record of $Volume and + * on volume @vol to @flags. + * + * Return 0 if successful and -1 if not with errno set to the error code. + */ +int ntfs_volume_write_flags(ntfs_volume *vol, const u16 flags) +{ + ATTR_RECORD *a; + VOLUME_INFORMATION *c; + ntfs_attr_search_ctx *ctx; + int ret = -1; /* failure */ + + if (!vol || !vol->vol_ni) { + errno = EINVAL; + return -1; + } + /* Get a pointer to the volume information attribute. */ + ctx = ntfs_attr_get_search_ctx(vol->vol_ni, NULL); + if (!ctx) { + ntfs_log_perror("Failed to allocate attribute search context"); + return -1; + } + if (ntfs_attr_lookup(AT_VOLUME_INFORMATION, AT_UNNAMED, 0, 0, 0, NULL, + 0, ctx)) { + ntfs_log_debug("Error: Attribute $VOLUME_INFORMATION was not found " + "in $Volume!\n"); + goto err_out; + } + a = ctx->attr; + /* Sanity check. */ + if (a->non_resident) { + ntfs_log_debug("Error: Attribute $VOLUME_INFORMATION must be " + "resident (and it isn't)!\n"); + errno = EIO; + goto err_out; + } + /* Get a pointer to the value of the attribute. */ + c = (VOLUME_INFORMATION*)(le16_to_cpu(a->value_offset) + (char*)a); + /* Sanity checks. */ + if ((char*)c + le32_to_cpu(a->value_length) > (char*)ctx->mrec + + le32_to_cpu(ctx->mrec->bytes_in_use) || + le16_to_cpu(a->value_offset) + + le32_to_cpu(a->value_length) > le32_to_cpu(a->length)) { + ntfs_log_debug("Error: Attribute $VOLUME_INFORMATION in $Volume is " + "corrupt!\n"); + errno = EIO; + goto err_out; + } + /* Set the volume flags. */ + vol->flags = c->flags = flags & VOLUME_FLAGS_MASK; + /* Write them to disk. */ + ntfs_inode_mark_dirty(vol->vol_ni); + if (ntfs_inode_sync(vol->vol_ni)) { + ntfs_log_perror("Error writing $Volume"); + goto err_out; + } + ret = 0; /* success */ +err_out: + ntfs_attr_put_search_ctx(ctx); + return ret; +} + diff --git a/libntfs-3g/win32_io.c b/libntfs-3g/win32_io.c new file mode 100644 index 00000000..90a7bda2 --- /dev/null +++ b/libntfs-3g/win32_io.c @@ -0,0 +1,1473 @@ +/* + * win32_io.c - A stdio-like disk I/O implementation for low-level disk access + * on Win32. Can access an NTFS volume while it is mounted. + * Originated from the Linux-NTFS project. + * + * Copyright (c) 2003-2004 Lode Leroy + * Copyright (c) 2003-2006 Anton Altaparmakov + * Copyright (c) 2004-2005 Yuval Fledel + * + * 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 + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program/include file 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_WINDOWS_H +#include +#endif +#include + +#ifdef HAVE_STDIO_H +#include +#endif +#ifdef HAVE_CTYPE_H +#include +#endif +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_FCNTL_H +#include +#endif + +/* Prevent volume.h from being be loaded, as it conflicts with winnt.h. */ +#define _NTFS_VOLUME_H +struct ntfs_volume; +typedef struct ntfs_volume ntfs_volume; + +#include "debug.h" +#include "types.h" +#include "device.h" + +#ifndef NTFS_BLOCK_SIZE +#define NTFS_BLOCK_SIZE 512 +#define NTFS_BLOCK_SIZE_BITS 9 +#endif + +#ifndef IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS +#define IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS 5636096 +#endif + +/* Windows 2k+ imports. */ +typedef HANDLE (WINAPI *LPFN_FINDFIRSTVOLUME)(LPTSTR, DWORD); +typedef BOOL (WINAPI *LPFN_FINDNEXTVOLUME)(HANDLE, LPTSTR, DWORD); +typedef BOOL (WINAPI *LPFN_FINDVOLUMECLOSE)(HANDLE); +typedef BOOL (WINAPI *LPFN_SETFILEPOINTEREX)(HANDLE, LARGE_INTEGER, + PLARGE_INTEGER, DWORD); + +static LPFN_FINDFIRSTVOLUME fnFindFirstVolume = NULL; +static LPFN_FINDNEXTVOLUME fnFindNextVolume = NULL; +static LPFN_FINDVOLUMECLOSE fnFindVolumeClose = NULL; +static LPFN_SETFILEPOINTEREX fnSetFilePointerEx = NULL; + +#ifdef UNICODE +#define FNPOSTFIX "W" +#else +#define FNPOSTFIX "A" +#endif + +/** + * struct win32_fd - + */ +typedef struct { + HANDLE handle; + s64 pos; /* Logical current position on the volume. */ + s64 part_start; + s64 part_length; + int part_hidden_sectors; + s64 geo_size, geo_cylinders; + DWORD geo_sectors, geo_heads; + HANDLE vol_handle; +} win32_fd; + +/** + * ntfs_w32error_to_errno - convert a win32 error code to the unix one + * @w32error: the win32 error code + * + * Limited to a relatively small but useful number of codes. + */ +static int ntfs_w32error_to_errno(unsigned int w32error) +{ + ntfs_log_trace("Converting w32error 0x%x.\n",w32error); + switch (w32error) { + case ERROR_INVALID_FUNCTION: + return EBADRQC; + case ERROR_FILE_NOT_FOUND: + case ERROR_PATH_NOT_FOUND: + case ERROR_INVALID_NAME: + return ENOENT; + case ERROR_TOO_MANY_OPEN_FILES: + return EMFILE; + case ERROR_ACCESS_DENIED: + return EACCES; + case ERROR_INVALID_HANDLE: + return EBADF; + case ERROR_NOT_ENOUGH_MEMORY: + return ENOMEM; + case ERROR_OUTOFMEMORY: + return ENOSPC; + case ERROR_INVALID_DRIVE: + case ERROR_BAD_UNIT: + return ENODEV; + case ERROR_WRITE_PROTECT: + return EROFS; + case ERROR_NOT_READY: + case ERROR_SHARING_VIOLATION: + return EBUSY; + case ERROR_BAD_COMMAND: + return EINVAL; + case ERROR_SEEK: + case ERROR_NEGATIVE_SEEK: + return ESPIPE; + case ERROR_NOT_SUPPORTED: + return EOPNOTSUPP; + case ERROR_BAD_NETPATH: + return ENOSHARE; + default: + /* generic message */ + return ENOMSG; + } +} + +/** + * libntfs_SetFilePointerEx - emulation for SetFilePointerEx() + * + * We use this to emulate SetFilePointerEx() when it is not present. This can + * happen since SetFilePointerEx() only exists in Win2k+. + */ +static BOOL WINAPI libntfs_SetFilePointerEx(HANDLE hFile, + LARGE_INTEGER liDistanceToMove, + PLARGE_INTEGER lpNewFilePointer, DWORD dwMoveMethod) +{ + liDistanceToMove.LowPart = SetFilePointer(hFile, + liDistanceToMove.LowPart, &liDistanceToMove.HighPart, + dwMoveMethod); + if (liDistanceToMove.LowPart == INVALID_SET_FILE_POINTER && + GetLastError() != NO_ERROR) { + if (lpNewFilePointer) + lpNewFilePointer->QuadPart = -1; + return FALSE; + } + if (lpNewFilePointer) + lpNewFilePointer->QuadPart = liDistanceToMove.QuadPart; + return TRUE; +} + +/** + * ntfs_device_win32_init_imports - initialize the function pointers + * + * The Find*Volume and SetFilePointerEx functions exist only on win2k+, as such + * we cannot just staticly import them. + * + * This function initializes the imports if the functions do exist and in the + * SetFilePointerEx case, we emulate the function ourselves if it is not + * present. + * + * Note: The values are cached, do be afraid to run it more than once. + */ +static void ntfs_device_win32_init_imports(void) +{ + HMODULE kernel32 = GetModuleHandle("kernel32"); + if (!kernel32) { + errno = ntfs_w32error_to_errno(GetLastError()); + ntfs_log_trace("kernel32.dll could not be imported.\n"); + } + if (!fnSetFilePointerEx) { + if (kernel32) + fnSetFilePointerEx = (LPFN_SETFILEPOINTEREX) + GetProcAddress(kernel32, + "SetFilePointerEx"); + /* + * If we did not get kernel32.dll or it is not Win2k+, emulate + * SetFilePointerEx(). + */ + if (!fnSetFilePointerEx) { + ntfs_log_debug("SetFilePonterEx() not found in " + "kernel32.dll: Enabling emulation.\n"); + fnSetFilePointerEx = libntfs_SetFilePointerEx; + } + } + /* Cannot do lookups if we could not get kernel32.dll... */ + if (!kernel32) + return; + if (!fnFindFirstVolume) + fnFindFirstVolume = (LPFN_FINDFIRSTVOLUME) + GetProcAddress(kernel32, "FindFirstVolume" + FNPOSTFIX); + if (!fnFindNextVolume) + fnFindNextVolume = (LPFN_FINDNEXTVOLUME) + GetProcAddress(kernel32, "FindNextVolume" + FNPOSTFIX); + if (!fnFindVolumeClose) + fnFindVolumeClose = (LPFN_FINDVOLUMECLOSE) + GetProcAddress(kernel32, "FindVolumeClose"); +} + +/** + * ntfs_device_unix_status_flags_to_win32 - convert unix->win32 open flags + * @flags: unix open status flags + * + * Supported flags are O_RDONLY, O_WRONLY and O_RDWR. + */ +static __inline__ int ntfs_device_unix_status_flags_to_win32(int flags) +{ + int win_mode; + + switch (flags & O_ACCMODE) { + case O_RDONLY: + win_mode = FILE_READ_DATA; + break; + case O_WRONLY: + win_mode = FILE_WRITE_DATA; + break; + case O_RDWR: + win_mode = FILE_READ_DATA | FILE_WRITE_DATA; + break; + default: + /* error */ + ntfs_log_trace("Unknown status flags.\n"); + win_mode = 0; + } + return win_mode; +} + + +/** + * ntfs_device_win32_simple_open_file - just open a file via win32 API + * @filename: name of the file to open + * @handle: pointer the a HANDLE in which to put the result + * @flags: unix open status flags + * @locking: will the function gain an exclusive lock on the file? + * + * Supported flags are O_RDONLY, O_WRONLY and O_RDWR. + * + * Return 0 if o.k. + * -1 if not, and errno set. In this case handle is trashed. + */ +static int ntfs_device_win32_simple_open_file(const char *filename, + HANDLE *handle, int flags, BOOL locking) +{ + *handle = CreateFile(filename, + ntfs_device_unix_status_flags_to_win32(flags), + locking ? 0 : (FILE_SHARE_WRITE | FILE_SHARE_READ), + NULL, OPEN_EXISTING, 0, NULL); + if (*handle == INVALID_HANDLE_VALUE) { + errno = ntfs_w32error_to_errno(GetLastError()); + ntfs_log_trace("CreateFile(%s) failed.\n", filename); + return -1; + } + return 0; +} + +/** + * ntfs_device_win32_lock - lock the volume + * @handle: a win32 HANDLE for a volume to lock + * + * Locking a volume means no one can access its contents. + * Exiting the process automatically unlocks the volume, except in old NT4s. + * + * Return 0 if o.k. + * -1 if not, and errno set. + */ +static int ntfs_device_win32_lock(HANDLE handle) +{ + DWORD i; + + if (!DeviceIoControl(handle, FSCTL_LOCK_VOLUME, NULL, 0, NULL, 0, &i, + NULL)) { + errno = ntfs_w32error_to_errno(GetLastError()); + ntfs_log_trace("Couldn't lock volume.\n"); + return -1; + } + ntfs_log_debug("Volume locked.\n"); + return 0; +} + +/** + * ntfs_device_win32_unlock - unlock the volume + * @handle: the win32 HANDLE which the volume was locked with + * + * Return 0 if o.k. + * -1 if not, and errno set. + */ +static int ntfs_device_win32_unlock(HANDLE handle) +{ + DWORD i; + + if (!DeviceIoControl(handle, FSCTL_UNLOCK_VOLUME, NULL, 0, NULL, 0, &i, + NULL)) { + errno = ntfs_w32error_to_errno(GetLastError()); + ntfs_log_trace("Couldn't unlock volume.\n"); + return -1; + } + ntfs_log_debug("Volume unlocked.\n"); + return 0; +} + +/** + * ntfs_device_win32_dismount - dismount a volume + * @handle: a win32 HANDLE for a volume to dismount + * + * Dismounting means the system will refresh the volume in the first change it + * gets. Usefull after altering the file structures. + * The volume must be locked by the current process while dismounting. + * A side effect is that the volume is also unlocked, but you must not rely om + * this. + * + * Return 0 if o.k. + * -1 if not, and errno set. + */ +static int ntfs_device_win32_dismount(HANDLE handle) +{ + DWORD i; + + if (!DeviceIoControl(handle, FSCTL_DISMOUNT_VOLUME, NULL, 0, NULL, 0, + &i, NULL)) { + errno = ntfs_w32error_to_errno(GetLastError()); + ntfs_log_trace("Couldn't dismount volume.\n"); + return -1; + } + ntfs_log_debug("Volume dismounted.\n"); + return 0; +} + +/** + * ntfs_device_win32_getsize - get file size via win32 API + * @handle: pointer the file HANDLE obtained via open + * + * Only works on ordinary files. + * + * Return The file size if o.k. + * -1 if not, and errno set. + */ +static s64 ntfs_device_win32_getsize(HANDLE handle) +{ + DWORD loword, hiword; + + loword = GetFileSize(handle, &hiword); + if (loword == INVALID_FILE_SIZE) { + errno = ntfs_w32error_to_errno(GetLastError()); + ntfs_log_trace("Couldn't get file size.\n"); + return -1; + } + return ((s64)hiword << 32) + loword; +} + +/** + * ntfs_device_win32_getdisklength - get disk size via win32 API + * @handle: pointer the file HANDLE obtained via open + * @argp: pointer to result buffer + * + * Only works on PhysicalDriveX type handles. + * + * Return The disk size if o.k. + * -1 if not, and errno set. + */ +static s64 ntfs_device_win32_getdisklength(HANDLE handle) +{ + GET_LENGTH_INFORMATION buf; + DWORD i; + + if (!DeviceIoControl(handle, IOCTL_DISK_GET_LENGTH_INFO, NULL, 0, &buf, + sizeof(buf), &i, NULL)) { + errno = ntfs_w32error_to_errno(GetLastError()); + ntfs_log_trace("Couldn't get disk length.\n"); + return -1; + } + ntfs_log_debug("Disk length: %lld.\n", buf.Length.QuadPart); + return buf.Length.QuadPart; +} + +/** + * ntfs_device_win32_getntfssize - get NTFS volume size via win32 API + * @handle: pointer the file HANDLE obtained via open + * @argp: pointer to result buffer + * + * Only works on NTFS volume handles. + * An annoying bug in windows is that an NTFS volume does not occupy the entire + * partition, namely not the last sector (which holds the backup boot sector, + * and normally not interesting). + * Use this function to get the length of the accessible space through a given + * volume handle. + * + * Return The volume size if o.k. + * -1 if not, and errno set. + */ +static s64 ntfs_device_win32_getntfssize(HANDLE handle) +{ + s64 rvl; +#ifdef FSCTL_GET_NTFS_VOLUME_DATA + DWORD i; + NTFS_VOLUME_DATA_BUFFER buf; + + if (!DeviceIoControl(handle, FSCTL_GET_NTFS_VOLUME_DATA, NULL, 0, &buf, + sizeof(buf), &i, NULL)) { + errno = ntfs_w32error_to_errno(GetLastError()); + ntfs_log_trace("Couldn't get NTFS volume length.\n"); + return -1; + } + rvl = buf.NumberSectors.QuadPart * buf.BytesPerSector; + ntfs_log_debug("NTFS volume length: 0x%llx.\n", (long long)rvl); +#else + errno = EINVAL; + rvl = -1; +#endif + return rvl; +} + +/** + * ntfs_device_win32_getgeo - get CHS information of a drive + * @handle: an open handle to the PhysicalDevice + * @fd: a win_fd structure that will be filled + * + * Return 0 if o.k. + * -1 if not, and errno set. + * + * In Windows NT+: fills size, sectors, and cylinders and sets heads to -1. + * In Windows XP+: fills size, sectors, cylinders, and heads. + * + * Note: In pre XP, this requires write permission, even though nothing is + * actually written. + * + * If fails, sets sectors, cylinders, heads, and size to -1. + */ +static int ntfs_device_win32_getgeo(HANDLE handle, win32_fd *fd) +{ + DWORD i; + BOOL rvl; + BYTE b[sizeof(DISK_GEOMETRY) + sizeof(DISK_PARTITION_INFO) + + sizeof(DISK_DETECTION_INFO) + 512]; + + rvl = DeviceIoControl(handle, IOCTL_DISK_GET_DRIVE_GEOMETRY_EX, NULL, + 0, &b, sizeof(b), &i, NULL); + if (rvl) { + ntfs_log_debug("GET_DRIVE_GEOMETRY_EX detected.\n"); + DISK_DETECTION_INFO *ddi = (PDISK_DETECTION_INFO) + (((PBYTE)(&((PDISK_GEOMETRY_EX)b)->Data)) + + (((PDISK_PARTITION_INFO) + (&((PDISK_GEOMETRY_EX)b)->Data))-> + SizeOfPartitionInfo)); + fd->geo_cylinders = ((DISK_GEOMETRY*)&b)->Cylinders.QuadPart; + fd->geo_sectors = ((DISK_GEOMETRY*)&b)->SectorsPerTrack; + fd->geo_size = ((DISK_GEOMETRY_EX*)&b)->DiskSize.QuadPart; + switch (ddi->DetectionType) { + case DetectInt13: + fd->geo_cylinders = ddi->Int13.MaxCylinders; + fd->geo_sectors = ddi->Int13.SectorsPerTrack; + fd->geo_heads = ddi->Int13.MaxHeads; + return 0; + case DetectExInt13: + fd->geo_cylinders = ddi->ExInt13.ExCylinders; + fd->geo_sectors = ddi->ExInt13.ExSectorsPerTrack; + fd->geo_heads = ddi->ExInt13.ExHeads; + return 0; + case DetectNone: + default: + break; + } + } else + fd->geo_heads = -1; + rvl = DeviceIoControl(handle, IOCTL_DISK_GET_DRIVE_GEOMETRY, NULL, 0, + &b, sizeof(b), &i, NULL); + if (rvl) { + ntfs_log_debug("GET_DRIVE_GEOMETRY detected.\n"); + fd->geo_cylinders = ((DISK_GEOMETRY*)&b)->Cylinders.QuadPart; + fd->geo_sectors = ((DISK_GEOMETRY*)&b)->SectorsPerTrack; + fd->geo_size = fd->geo_cylinders * fd->geo_sectors * + ((DISK_GEOMETRY*)&b)->TracksPerCylinder * + ((DISK_GEOMETRY*)&b)->BytesPerSector; + return 0; + } + errno = ntfs_w32error_to_errno(GetLastError()); + ntfs_log_trace("Couldn't retrieve disk geometry.\n"); + fd->geo_cylinders = -1; + fd->geo_sectors = -1; + fd->geo_size = -1; + return -1; +} + +/** + * ntfs_device_win32_open_file - open a file via win32 API + * @filename: name of the file to open + * @fd: pointer to win32 file device in which to put the result + * @flags: unix open status flags + * + * Return 0 if o.k. + * -1 if not, and errno set. + */ +static __inline__ int ntfs_device_win32_open_file(char *filename, win32_fd *fd, + int flags) +{ + HANDLE handle; + + if (ntfs_device_win32_simple_open_file(filename, &handle, flags, + FALSE)) { + /* open error */ + return -1; + } + /* fill fd */ + fd->handle = handle; + fd->part_start = 0; + fd->part_length = ntfs_device_win32_getsize(handle); + fd->pos = 0; + fd->part_hidden_sectors = -1; + fd->geo_size = -1; /* used as a marker that this is a file */ + fd->vol_handle = INVALID_HANDLE_VALUE; + return 0; +} + +/** + * ntfs_device_win32_open_drive - open a drive via win32 API + * @drive_id: drive to open + * @fd: pointer to win32 file device in which to put the result + * @flags: unix open status flags + * + * return 0 if o.k. + * -1 if not, and errno set. + */ +static __inline__ int ntfs_device_win32_open_drive(int drive_id, win32_fd *fd, + int flags) +{ + HANDLE handle; + int err; + char filename[MAX_PATH]; + + sprintf(filename, "\\\\.\\PhysicalDrive%d", drive_id); + if ((err = ntfs_device_win32_simple_open_file(filename, &handle, flags, + TRUE))) { + /* open error */ + return err; + } + /* store the drive geometry */ + ntfs_device_win32_getgeo(handle, fd); + /* Just to be sure */ + if (fd->geo_size == -1) + fd->geo_size = ntfs_device_win32_getdisklength(handle); + /* fill fd */ + fd->handle = handle; + fd->part_start = 0; + fd->part_length = fd->geo_size; + fd->pos = 0; + fd->part_hidden_sectors = -1; + fd->vol_handle = INVALID_HANDLE_VALUE; + return 0; +} + +/** + * ntfs_device_win32_open_volume_for_partition - find and open a volume + * + * Windows NT/2k/XP handles volumes instead of partitions. + * This function gets the partition details and return an open volume handle. + * That volume is the one whose only physical location on disk is the described + * partition. + * + * The function required Windows 2k/XP, otherwise it fails (gracefully). + * + * Return success: a valid open volume handle. + * fail : INVALID_HANDLE_VALUE + */ +static HANDLE ntfs_device_win32_open_volume_for_partition(unsigned int drive_id, + s64 part_offset, s64 part_length, int flags) +{ + HANDLE vol_find_handle; + TCHAR vol_name[MAX_PATH]; + + /* Make sure all the required imports exist. */ + if (!fnFindFirstVolume || !fnFindNextVolume || !fnFindVolumeClose) { + ntfs_log_trace("Required dll imports not found.\n"); + return INVALID_HANDLE_VALUE; + } + /* Start iterating through volumes. */ + ntfs_log_trace("Entering with drive_id=%d, part_offset=%lld, " + "path_length=%lld, flags=%d.\n", drive_id, + (unsigned long long)part_offset, + (unsigned long long)part_length, flags); + vol_find_handle = fnFindFirstVolume(vol_name, MAX_PATH); + /* If a valid handle could not be aquired, reply with "don't know". */ + if (vol_find_handle == INVALID_HANDLE_VALUE) { + ntfs_log_trace("FindFirstVolume failed.\n"); + return INVALID_HANDLE_VALUE; + } + do { + int vol_name_length; + HANDLE handle; + + /* remove trailing '/' from vol_name */ +#ifdef UNICODE + vol_name_length = wcslen(vol_name); +#else + vol_name_length = strlen(vol_name); +#endif + if (vol_name_length>0) + vol_name[vol_name_length-1]=0; + + ntfs_log_debug("Processing %s.\n", vol_name); + /* open the file */ + handle = CreateFile(vol_name, + ntfs_device_unix_status_flags_to_win32(flags), + FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, + OPEN_EXISTING, 0, NULL); + if (handle != INVALID_HANDLE_VALUE) { + DWORD bytesReturned; +#define EXTENTS_SIZE sizeof(VOLUME_DISK_EXTENTS) + 9 * sizeof(DISK_EXTENT) + char extents[EXTENTS_SIZE]; + + /* Check physical locations. */ + if (DeviceIoControl(handle, + IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, + NULL, 0, extents, EXTENTS_SIZE, + &bytesReturned, NULL)) { + if (((VOLUME_DISK_EXTENTS *)extents)-> + NumberOfDiskExtents == 1) { + DISK_EXTENT *extent = &(( + VOLUME_DISK_EXTENTS *) + extents)->Extents[0]; + if ((extent->DiskNumber==drive_id) && + (extent->StartingOffset. + QuadPart==part_offset) + && (extent-> + ExtentLength.QuadPart + == part_length)) { + /* + * Eureka! (Archimedes, 287 BC, + * "I have found it!") + */ + fnFindVolumeClose( + vol_find_handle); + return handle; + } + } + } + } else + ntfs_log_trace("getExtents() Failed.\n"); + } while (fnFindNextVolume(vol_find_handle, vol_name, MAX_PATH)); + /* End of iteration through volumes. */ + ntfs_log_trace("Closing, volume was not found.\n"); + fnFindVolumeClose(vol_find_handle); + return INVALID_HANDLE_VALUE; +} + +/** + * ntfs_device_win32_find_partition - locates partition details by id. + * @handle: HANDLE to the PhysicalDrive + * @partition_id: the partition number to locate + * @part_offset: pointer to where to put the offset to the partition + * @part_length: pointer to where to put the length of the partition + * @hidden_sectors: pointer to where to put the hidden sectors + * + * This function requires an open PhysicalDrive handle and a partition_id. + * If a partition with the required id is found on the supplied device, + * the partition attributes are returned back. + * + * Returns: TRUE if found, and sets the output parameters. + * FALSE if not and errno is set to the error code. + */ +static BOOL ntfs_device_win32_find_partition(HANDLE handle, DWORD partition_id, + s64 *part_offset, s64 *part_length, int *hidden_sectors) +{ + DRIVE_LAYOUT_INFORMATION *drive_layout; + unsigned int err, buf_size, part_count; + DWORD i; + + /* + * There is no way to know the required buffer, so if the ioctl fails, + * try doubling the buffer size each time until the ioctl succeeds. + */ + part_count = 8; + do { + buf_size = sizeof(DRIVE_LAYOUT_INFORMATION) + + part_count * sizeof(PARTITION_INFORMATION); + drive_layout = malloc(buf_size); + if (!drive_layout) { + errno = ENOMEM; + return FALSE; + } + if (DeviceIoControl(handle, IOCTL_DISK_GET_DRIVE_LAYOUT, NULL, + 0, (BYTE*)drive_layout, buf_size, &i, NULL)) + break; + err = GetLastError(); + free(drive_layout); + if (err != ERROR_INSUFFICIENT_BUFFER) { + ntfs_log_trace("GetDriveLayout failed.\n"); + errno = ntfs_w32error_to_errno(err); + return FALSE; + } + ntfs_log_debug("More than %u partitions.\n", part_count); + part_count <<= 1; + if (part_count > 512) { + ntfs_log_trace("GetDriveLayout failed: More than 512 " + "partitions?\n"); + errno = ENOBUFS; + return FALSE; + } + } while (1); + for (i = 0; i < drive_layout->PartitionCount; i++) { + if (drive_layout->PartitionEntry[i].PartitionNumber == + partition_id) { + *part_offset = drive_layout->PartitionEntry[i]. + StartingOffset.QuadPart; + *part_length = drive_layout->PartitionEntry[i]. + PartitionLength.QuadPart; + *hidden_sectors = drive_layout->PartitionEntry[i]. + HiddenSectors; + free(drive_layout); + return TRUE; + } + } + free(drive_layout); + errno = ENOENT; + return FALSE; +} + +/** + * ntfs_device_win32_open_partition - open a partition via win32 API + * @drive_id: drive to open + * @partition_id: partition to open + * @fd: win32 file device to return + * @flags: unix open status flags + * + * Return 0 if o.k. + * -1 if not, and errno set. + * + * When fails, fd contents may have not been preserved. + */ +static int ntfs_device_win32_open_partition(int drive_id, + unsigned int partition_id, win32_fd *fd, int flags) +{ + s64 part_start, part_length; + HANDLE handle; + int err, hidden_sectors; + char drive_name[MAX_PATH]; + + sprintf(drive_name, "\\\\.\\PhysicalDrive%d", drive_id); + /* Open the entire device without locking, ask questions later */ + if ((err = ntfs_device_win32_simple_open_file(drive_name, &handle, + flags, FALSE))) { + /* error */ + return err; + } + if (ntfs_device_win32_find_partition(handle, partition_id, &part_start, + &part_length, &hidden_sectors)) { + s64 tmp; + HANDLE vol_handle = ntfs_device_win32_open_volume_for_partition( + drive_id, part_start, part_length, flags); + /* Store the drive geometry. */ + ntfs_device_win32_getgeo(handle, fd); + fd->handle = handle; + fd->pos = 0; + fd->part_start = part_start; + fd->part_length = part_length; + fd->part_hidden_sectors = hidden_sectors; + tmp = ntfs_device_win32_getntfssize(vol_handle); + if (tmp > 0) + fd->geo_size = tmp; + else + fd->geo_size = fd->part_length; + if (vol_handle != INVALID_HANDLE_VALUE) { + if (((flags & O_RDWR) == O_RDWR) && + ntfs_device_win32_lock(vol_handle)) { + CloseHandle(vol_handle); + CloseHandle(handle); + return -1; + } + fd->vol_handle = vol_handle; + } else { + if ((flags & O_RDWR) == O_RDWR) { + /* Access if read-write, no volume found. */ + ntfs_log_trace("Partitions containing Spanned/" + "Mirrored volumes are not " + "supported in R/W status " + "yet.\n"); + CloseHandle(handle); + errno = EOPNOTSUPP; + return -1; + } + fd->vol_handle = INVALID_HANDLE_VALUE; + } + return 0; + } else { + ntfs_log_debug("Partition %u not found on drive %d.\n", + partition_id, drive_id); + CloseHandle(handle); + errno = ENODEV; + return -1; + } +} + +/** + * ntfs_device_win32_open - open a device + * @dev: a pointer to the NTFS_DEVICE to open + * @flags: unix open status flags + * + * @dev->d_name must hold the device name, the rest is ignored. + * Supported flags are O_RDONLY, O_WRONLY and O_RDWR. + * + * If name is in format "(hd[0-9],[0-9])" then open a partition. + * If name is in format "(hd[0-9])" then open a volume. + * Otherwise open a file. + */ +static int ntfs_device_win32_open(struct ntfs_device *dev, int flags) +{ + int drive_id = 0, numparams; + unsigned int part = 0; + char drive_char; + win32_fd fd; + int err; + + if (NDevOpen(dev)) { + errno = EBUSY; + return -1; + } + ntfs_device_win32_init_imports(); + numparams = sscanf(dev->d_name, "/dev/hd%c%u", &drive_char, &part); + drive_id = toupper(drive_char) - 'A'; + switch (numparams) { + case 0: + ntfs_log_debug("win32_open(%s) -> file.\n", dev->d_name); + err = ntfs_device_win32_open_file(dev->d_name, &fd, flags); + break; + case 1: + ntfs_log_debug("win32_open(%s) -> drive %d.\n", dev->d_name, + drive_id); + err = ntfs_device_win32_open_drive(drive_id, &fd, flags); + break; + case 2: + ntfs_log_debug("win32_open(%s) -> drive %d, part %u.\n", + dev->d_name, drive_id, part); + err = ntfs_device_win32_open_partition(drive_id, part, &fd, + flags); + break; + default: + ntfs_log_debug("win32_open(%s) -> unknwon file format.\n", + dev->d_name); + err = -1; + } + if (err) + return err; + ntfs_log_debug("win32_open(%s) -> %p, offset 0x%llx.\n", dev->d_name, + dev, fd.part_start); + /* Setup our read-only flag. */ + if ((flags & O_RDWR) != O_RDWR) + NDevSetReadOnly(dev); + dev->d_private = malloc(sizeof(win32_fd)); + memcpy(dev->d_private, &fd, sizeof(win32_fd)); + NDevSetOpen(dev); + NDevClearDirty(dev); + return 0; +} + +/** + * ntfs_device_win32_seek - change current logical file position + * @dev: ntfs device obtained via ->open + * @offset: required offset from the whence anchor + * @whence: whence anchor specifying what @offset is relative to + * + * Return the new position on the volume on success and -1 on error with errno + * set to the error code. + * + * @whence may be one of the following: + * SEEK_SET - Offset is relative to file start. + * SEEK_CUR - Offset is relative to current position. + * SEEK_END - Offset is relative to end of file. + */ +static s64 ntfs_device_win32_seek(struct ntfs_device *dev, s64 offset, + int whence) +{ + s64 abs_ofs; + win32_fd *fd = (win32_fd *)dev->d_private; + + ntfs_log_trace("seek offset = 0x%llx, whence = %d.\n", offset, whence); + switch (whence) { + case SEEK_SET: + abs_ofs = offset; + break; + case SEEK_CUR: + abs_ofs = fd->pos + offset; + break; + case SEEK_END: + /* End of partition != end of disk. */ + if (fd->part_length == -1) { + ntfs_log_trace("Position relative to end of disk not " + "implemented.\n"); + errno = EOPNOTSUPP; + return -1; + } + abs_ofs = fd->part_length + offset; + break; + default: + ntfs_log_trace("Wrong mode %d.\n", whence); + errno = EINVAL; + return -1; + } + if (abs_ofs < 0 || abs_ofs > fd->part_length) { + ntfs_log_trace("Seeking outsize seekable area.\n"); + errno = EINVAL; + return -1; + } + fd->pos = abs_ofs; + return abs_ofs; +} + +/** + * ntfs_device_win32_pio - positioned low level i/o + * @fd: win32 device descriptor obtained via ->open + * @pos: at which position to do i/o from/to + * @count: how many bytes should be transfered + * @b: source/destination buffer + * @write: TRUE if write transfer and FALSE if read transfer + * + * On success returns the number of bytes transfered (can be < @count) and on + * error returns -1 and errno set. Transfer starts from position @pos on @fd. + * + * Notes: + * - @pos, @buf, and @count must be aligned to NTFS_BLOCK_SIZE. + * - When dealing with volumes, a single call must not span both volume + * and disk extents. + * - Does not use/set @fd->pos. + */ +static s64 ntfs_device_win32_pio(win32_fd *fd, const s64 pos, + const s64 count, void *b, const BOOL write) +{ + LARGE_INTEGER li; + HANDLE handle; + DWORD bt; + BOOL res; + + ntfs_log_trace("pos = 0x%llx, count = 0x%llx, direction = %s.\n", + (long long)pos, (long long)count, write ? "write" : + "read"); + li.QuadPart = pos; + if (fd->vol_handle != INVALID_HANDLE_VALUE && pos < fd->geo_size) { + ntfs_log_debug("Transfering via vol_handle.\n"); + handle = fd->vol_handle; + } else { + ntfs_log_debug("Transfering via handle.\n"); + handle = fd->handle; + li.QuadPart += fd->part_start; + } + if (!fnSetFilePointerEx(handle, li, NULL, FILE_BEGIN)) { + errno = ntfs_w32error_to_errno(GetLastError()); + ntfs_log_trace("SetFilePointer failed.\n"); + return -1; + } + if (write) + res = WriteFile(handle, b, count, &bt, NULL); + else + res = ReadFile(handle, b, count, &bt, NULL); + if (!res) { + errno = ntfs_w32error_to_errno(GetLastError()); + ntfs_log_trace("%sFile() failed.\n", write ? "Write" : "Read"); + return -1; + } + return bt; +} + +/** + * ntfs_device_win32_pread_simple - positioned simple read + * @fd: win32 device descriptor obtained via ->open + * @pos: at which position to read from + * @count: how many bytes should be read + * @b: a pointer to where to put the contents + * + * On success returns the number of bytes read (can be < @count) and on error + * returns -1 and errno set. Read starts from position @pos. + * + * Notes: + * - @pos, @buf, and @count must be aligned to NTFS_BLOCK_SIZE. + * - When dealing with volumes, a single call must not span both volume + * and disk extents. + * - Does not use/set @fd->pos. + */ +static inline s64 ntfs_device_win32_pread_simple(win32_fd *fd, const s64 pos, + const s64 count, void *b) +{ + return ntfs_device_win32_pio(fd, pos, count, b, FALSE); +} + +/** + * ntfs_device_win32_read - read bytes from an ntfs device + * @dev: ntfs device obtained via ->open + * @b: pointer to where to put the contents + * @count: how many bytes should be read + * + * On success returns the number of bytes actually read (can be < @count). + * On error returns -1 with errno set. + */ +static s64 ntfs_device_win32_read(struct ntfs_device *dev, void *b, s64 count) +{ + s64 old_pos, to_read, i, br = 0; + win32_fd *fd = (win32_fd *)dev->d_private; + BYTE *alignedbuffer; + int old_ofs, ofs; + + old_pos = fd->pos; + old_ofs = ofs = old_pos & (NTFS_BLOCK_SIZE - 1); + to_read = (ofs + count + NTFS_BLOCK_SIZE - 1) & + ~(s64)(NTFS_BLOCK_SIZE - 1); + /* Impose maximum of 2GB to be on the safe side. */ + if (to_read > 0x80000000) { + int delta = to_read - count; + to_read = 0x80000000; + count = to_read - delta; + } + ntfs_log_trace("fd = %p, b = %p, count = 0x%llx, pos = 0x%llx, " + "ofs = %i, to_read = 0x%llx.\n", fd, b, + (long long)count, (long long)old_pos, ofs, + (long long)to_read); + if (!((unsigned long)b & (NTFS_BLOCK_SIZE - 1)) && !old_ofs && + !(count & (NTFS_BLOCK_SIZE - 1))) + alignedbuffer = b; + else { + alignedbuffer = (BYTE *)VirtualAlloc(NULL, to_read, MEM_COMMIT, + PAGE_READWRITE); + if (!alignedbuffer) { + errno = ntfs_w32error_to_errno(GetLastError()); + ntfs_log_trace("VirtualAlloc failed for read.\n"); + return -1; + } + } + if (fd->vol_handle != INVALID_HANDLE_VALUE && old_pos < fd->geo_size) { + s64 vol_to_read = fd->geo_size - old_pos; + if (count > vol_to_read) { + br = ntfs_device_win32_pread_simple(fd, + old_pos & ~(s64)(NTFS_BLOCK_SIZE - 1), + ofs + vol_to_read, alignedbuffer); + if (br == -1) + goto read_error; + to_read -= br; + if (br < ofs) { + br = 0; + goto read_partial; + } + br -= ofs; + fd->pos += br; + ofs = fd->pos & (NTFS_BLOCK_SIZE - 1); + if (br != vol_to_read) + goto read_partial; + } + } + i = ntfs_device_win32_pread_simple(fd, + fd->pos & ~(s64)(NTFS_BLOCK_SIZE - 1), to_read, + alignedbuffer + br); + if (i == -1) { + if (br) + goto read_partial; + goto read_error; + } + if (i < ofs) + goto read_partial; + i -= ofs; + br += i; + if (br > count) + br = count; + fd->pos = old_pos + br; +read_partial: + if (alignedbuffer != b) { + memcpy((void*)b, alignedbuffer + old_ofs, br); + VirtualFree(alignedbuffer, 0, MEM_RELEASE); + } + return br; +read_error: + if (alignedbuffer != b) + VirtualFree(alignedbuffer, 0, MEM_RELEASE); + return -1; +} + +/** + * ntfs_device_win32_close - close an open ntfs deivce + * @dev: ntfs device obtained via ->open + * + * Return 0 if o.k. + * -1 if not, and errno set. Note if error fd->vol_handle is trashed. + */ +static int ntfs_device_win32_close(struct ntfs_device *dev) +{ + win32_fd *fd = (win32_fd *)dev->d_private; + BOOL rvl; + + ntfs_log_trace("Closing device %p.\n", dev); + if (!NDevOpen(dev)) { + errno = EBADF; + return -1; + } + if (fd->vol_handle != INVALID_HANDLE_VALUE) { + if (!NDevReadOnly(dev)) { + ntfs_device_win32_dismount(fd->vol_handle); + ntfs_device_win32_unlock(fd->vol_handle); + } + if (!CloseHandle(fd->vol_handle)) + ntfs_log_trace("CloseHandle() failed for volume.\n"); + } + rvl = CloseHandle(fd->handle); + free(fd); + if (!rvl) { + errno = ntfs_w32error_to_errno(GetLastError()); + ntfs_log_trace("CloseHandle() failed.\n"); + return -1; + } + return 0; +} + +/** + * ntfs_device_win32_sync - flush write buffers to disk + * @dev: ntfs device obtained via ->open + * + * Return 0 if o.k. + * -1 if not, and errno set. + * + * Note: Volume syncing works differently in windows. + * Disk cannot be synced in windows. + */ +static int ntfs_device_win32_sync(struct ntfs_device *dev) +{ + int err = 0; + BOOL to_clear = TRUE; + + if (!NDevReadOnly(dev) && NDevDirty(dev)) { + win32_fd *fd = (win32_fd *)dev->d_private; + + if ((fd->vol_handle != INVALID_HANDLE_VALUE) && + !FlushFileBuffers(fd->vol_handle)) { + to_clear = FALSE; + err = ntfs_w32error_to_errno(GetLastError()); + } + if (!FlushFileBuffers(fd->handle)) { + to_clear = FALSE; + if (!err) + err = ntfs_w32error_to_errno(GetLastError()); + } + if (!to_clear) { + ntfs_log_trace("Could not sync.\n"); + errno = err; + return -1; + } + NDevClearDirty(dev); + } + return 0; +} + +/** + * ntfs_device_win32_pwrite_simple - positioned simple write + * @fd: win32 device descriptor obtained via ->open + * @pos: at which position to write to + * @count: how many bytes should be written + * @b: a pointer to the data to write + * + * On success returns the number of bytes written and on error returns -1 and + * errno set. Write starts from position @pos. + * + * Notes: + * - @pos, @buf, and @count must be aligned to NTFS_BLOCK_SIZE. + * - When dealing with volumes, a single call must not span both volume + * and disk extents. + * - Does not use/set @fd->pos. + */ +static inline s64 ntfs_device_win32_pwrite_simple(win32_fd *fd, const s64 pos, + const s64 count, const void *b) +{ + return ntfs_device_win32_pio(fd, pos, count, (void *)b, TRUE); +} + +/** + * ntfs_device_win32_write - write bytes to an ntfs device + * @dev: ntfs device obtained via ->open + * @b: pointer to the data to write + * @count: how many bytes should be written + * + * On success returns the number of bytes actually written. + * On error returns -1 with errno set. + */ +static s64 ntfs_device_win32_write(struct ntfs_device *dev, const void *b, + s64 count) +{ + s64 old_pos, to_write, i, bw = 0; + win32_fd *fd = (win32_fd *)dev->d_private; + BYTE *alignedbuffer; + int old_ofs, ofs; + + old_pos = fd->pos; + old_ofs = ofs = old_pos & (NTFS_BLOCK_SIZE - 1); + to_write = (ofs + count + NTFS_BLOCK_SIZE - 1) & + ~(s64)(NTFS_BLOCK_SIZE - 1); + /* Impose maximum of 2GB to be on the safe side. */ + if (to_write > 0x80000000) { + int delta = to_write - count; + to_write = 0x80000000; + count = to_write - delta; + } + ntfs_log_trace("fd = %p, b = %p, count = 0x%llx, pos = 0x%llx, " + "ofs = %i, to_write = 0x%llx.\n", fd, b, + (long long)count, (long long)old_pos, ofs, + (long long)to_write); + if (NDevReadOnly(dev)) { + ntfs_log_trace("Can't write on a R/O device.\n"); + errno = EROFS; + return -1; + } + if (!count) + return 0; + NDevSetDirty(dev); + if (!((unsigned long)b & (NTFS_BLOCK_SIZE - 1)) && !old_ofs && + !(count & (NTFS_BLOCK_SIZE - 1))) + alignedbuffer = (BYTE *)b; + else { + s64 end; + + alignedbuffer = (BYTE *)VirtualAlloc(NULL, to_write, + MEM_COMMIT, PAGE_READWRITE); + if (!alignedbuffer) { + errno = ntfs_w32error_to_errno(GetLastError()); + ntfs_log_trace("VirtualAlloc failed for write.\n"); + return -1; + } + /* Read first sector if start of write not sector aligned. */ + if (ofs) { + i = ntfs_device_win32_pread_simple(fd, + old_pos & ~(s64)(NTFS_BLOCK_SIZE - 1), + NTFS_BLOCK_SIZE, alignedbuffer); + if (i != NTFS_BLOCK_SIZE) { + if (i >= 0) + errno = EIO; + goto write_error; + } + } + /* + * Read last sector if end of write not sector aligned and last + * sector is either not the same as the first sector or it is + * the same as the first sector but this has not been read in + * yet, i.e. the start of the write is sector aligned. + */ + end = old_pos + count; + if ((end & (NTFS_BLOCK_SIZE - 1)) && + ((to_write > NTFS_BLOCK_SIZE) || !ofs)) { + i = ntfs_device_win32_pread_simple(fd, + end & ~(s64)(NTFS_BLOCK_SIZE - 1), + NTFS_BLOCK_SIZE, alignedbuffer + + to_write - NTFS_BLOCK_SIZE); + if (i != NTFS_BLOCK_SIZE) { + if (i >= 0) + errno = EIO; + goto write_error; + } + } + /* Copy the data to be written into @alignedbuffer. */ + memcpy(alignedbuffer + ofs, b, count); + } + if (fd->vol_handle != INVALID_HANDLE_VALUE && old_pos < fd->geo_size) { + s64 vol_to_write = fd->geo_size - old_pos; + if (count > vol_to_write) { + bw = ntfs_device_win32_pwrite_simple(fd, + old_pos & ~(s64)(NTFS_BLOCK_SIZE - 1), + ofs + vol_to_write, alignedbuffer); + if (bw == -1) + goto write_error; + to_write -= bw; + if (bw < ofs) { + bw = 0; + goto write_partial; + } + bw -= ofs; + fd->pos += bw; + ofs = fd->pos & (NTFS_BLOCK_SIZE - 1); + if (bw != vol_to_write) + goto write_partial; + } + } + i = ntfs_device_win32_pwrite_simple(fd, + fd->pos & ~(s64)(NTFS_BLOCK_SIZE - 1), to_write, + alignedbuffer + bw); + if (i == -1) { + if (bw) + goto write_partial; + goto write_error; + } + if (i < ofs) + goto write_partial; + i -= ofs; + bw += i; + if (bw > count) + bw = count; + fd->pos = old_pos + bw; +write_partial: + if (alignedbuffer != b) + VirtualFree(alignedbuffer, 0, MEM_RELEASE); + return bw; +write_error: + bw = -1; + goto write_partial; +} + +/** + * ntfs_device_win32_stat - get a unix-like stat structure for an ntfs device + * @dev: ntfs device obtained via ->open + * @buf: pointer to the stat structure to fill + * + * Note: Only st_mode, st_size, and st_blocks are filled. + * + * Return 0 if o.k. + * -1 if not and errno set. in this case handle is trashed. + */ +static int ntfs_device_win32_stat(struct ntfs_device *dev, struct stat *buf) +{ + win32_fd *fd = (win32_fd *)dev->d_private; + mode_t st_mode; + + switch (GetFileType(fd->handle)) { + case FILE_TYPE_CHAR: + st_mode = S_IFCHR; + break; + case FILE_TYPE_DISK: + st_mode = S_IFBLK; + break; + case FILE_TYPE_PIPE: + st_mode = S_IFIFO; + break; + default: + st_mode = 0; + } + memset(buf, 0, sizeof(struct stat)); + buf->st_mode = st_mode; + buf->st_size = fd->part_length; + if (buf->st_size != -1) + buf->st_blocks = buf->st_size >> 9; + else + buf->st_size = 0; + return 0; +} + +/** + * ntfs_win32_hdio_getgeo - get drive geometry + * @dev: ntfs device obtained via ->open + * @argp: pointer to where to put the output + * + * Note: Works on windows NT/2k/XP only. + * + * Return 0 if o.k. + * -1 if not, and errno set. Note if error fd->handle is trashed. + */ +static __inline__ int ntfs_win32_hdio_getgeo(struct ntfs_device *dev, + struct hd_geometry *argp) +{ + win32_fd *fd = (win32_fd *)dev->d_private; + + argp->heads = fd->geo_heads; + argp->sectors = fd->geo_sectors; + argp->cylinders = fd->geo_cylinders; + argp->start = fd->part_hidden_sectors; + return 0; +} + +/** + * ntfs_win32_blksszget - get block device sector size + * @dev: ntfs device obtained via ->open + * @argp: pointer to where to put the output + * + * Note: Works on windows NT/2k/XP only. + * + * Return 0 if o.k. + * -1 if not, and errno set. Note if error fd->handle is trashed. + */ +static __inline__ int ntfs_win32_blksszget(struct ntfs_device *dev,int *argp) +{ + win32_fd *fd = (win32_fd *)dev->d_private; + DWORD bytesReturned; + DISK_GEOMETRY dg; + + if (DeviceIoControl(fd->handle, IOCTL_DISK_GET_DRIVE_GEOMETRY, NULL, 0, + &dg, sizeof(DISK_GEOMETRY), &bytesReturned, NULL)) { + /* success */ + *argp = dg.BytesPerSector; + return 0; + } + errno = ntfs_w32error_to_errno(GetLastError()); + ntfs_log_trace("GET_DRIVE_GEOMETRY failed.\n"); + return -1; +} + +static int ntfs_device_win32_ioctl(struct ntfs_device *dev, int request, + void *argp) +{ + win32_fd *fd = (win32_fd *)dev->d_private; + + ntfs_log_trace("win32_ioctl(%d) called.\n", request); + switch (request) { +#if defined(BLKGETSIZE) + case BLKGETSIZE: + ntfs_log_debug("BLKGETSIZE detected.\n"); + if (fd->part_length >= 0) { + *(int *)argp = (int)(fd->part_length / 512); + return 0; + } + errno = EOPNOTSUPP; + return -1; +#endif +#if defined(BLKGETSIZE64) + case BLKGETSIZE64: + ntfs_log_debug("BLKGETSIZE64 detected.\n"); + if (fd->part_length >= 0) { + *(s64 *)argp = fd->part_length; + return 0; + } + errno = EOPNOTSUPP; + return -1; +#endif +#ifdef HDIO_GETGEO + case HDIO_GETGEO: + ntfs_log_debug("HDIO_GETGEO detected.\n"); + return ntfs_win32_hdio_getgeo(dev, (struct hd_geometry *)argp); +#endif +#ifdef BLKSSZGET + case BLKSSZGET: + ntfs_log_debug("BLKSSZGET detected.\n"); + return ntfs_win32_blksszget(dev, (int *)argp); +#endif +#ifdef BLKBSZSET + case BLKBSZSET: + ntfs_log_debug("BLKBSZSET detected.\n"); + /* Nothing to do on Windows. */ + return 0; +#endif + default: + ntfs_log_debug("unimplemented ioctl %d.\n", request); + errno = EOPNOTSUPP; + return -1; + } +} + +static s64 ntfs_device_win32_pread(struct ntfs_device *dev, void *b, + s64 count, s64 offset) +{ + return ntfs_pread(dev, offset, count, b); +} + +static s64 ntfs_device_win32_pwrite(struct ntfs_device *dev, const void *b, + s64 count, s64 offset) +{ + return ntfs_pwrite(dev, offset, count, b); +} + +struct ntfs_device_operations ntfs_device_win32_io_ops = { + .open = ntfs_device_win32_open, + .close = ntfs_device_win32_close, + .seek = ntfs_device_win32_seek, + .read = ntfs_device_win32_read, + .write = ntfs_device_win32_write, + .pread = ntfs_device_win32_pread, + .pwrite = ntfs_device_win32_pwrite, + .sync = ntfs_device_win32_sync, + .stat = ntfs_device_win32_stat, + .ioctl = ntfs_device_win32_ioctl +}; diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 00000000..fa65babe --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,50 @@ +if REALLYSTATIC +AM_LIBS = $(top_builddir)/libntfs-3g/.libs/libntfs-3g.a +AM_LFLAGS = -static +STATIC_LINK = $(CC) $(AM_CFLAGS) $(CFLAGS) $(LDFLAGS) -o $@ +else +AM_LIBS = $(top_builddir)/libntfs-3g/libntfs-3g.la +AM_LFLAGS = $(all_libraries) +LIBTOOL_LINK = $(LIBTOOL) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(LDFLAGS) -o $@ +endif + +# Workaround to make REALLYSTATIC work with automake 1.5. +LINK=$(STATIC_LINK) $(LIBTOOL_LINK) + +man_MANS = ntfs-3g.8 + +MAINTAINERCLEANFILES = Makefile.in + +linux_ntfsincludedir = -I$(top_srcdir)/include/ntfs + +bin_PROGRAMS = ntfs-3g + +# Set the include path. +AM_CPPFLAGS = -I$(top_srcdir)/include/ntfs-3g $(all_includes) + +ntfs_3g_SOURCES = ntfs-3g.c utils.c utils.h +ntfs_3g_LDADD = $(AM_LIBS) $(FUSE_MODULE_LIBS) +ntfs_3g_LDFLAGS = $(AM_LFLAGS) +ntfs_3g_CFLAGS = $(FUSE_MODULE_CFLAGS) -DFUSE_USE_VERSION=25 + +# Extra targets + +strip: $(bin_PROGRAMS) + $(STRIP) $^ + +libs: + (cd ../libntfs-3g && $(MAKE) libs) || exit 1; + +install-exec-hook: + $(INSTALL) -d $(DESTDIR)/sbin + $(LN_S) -f $(bindir)/ntfs-3g $(DESTDIR)/sbin/mount.ntfs-3g + ldconfig + +install-data-hook: + $(INSTALL) -d $(DESTDIR)$(man8dir) + $(LN_S) -f ntfs-3g.8 $(DESTDIR)$(man8dir)/mount.ntfs-3g.8 + +uninstall-local: + $(RM) -f $(DESTDIR)/sbin/mount.ntfs-3g + $(RM) -f $(DESTDIR)$(man8dir)/mount.ntfs-3g.8 + diff --git a/src/ntfs-3g.8.in b/src/ntfs-3g.8.in new file mode 100644 index 00000000..69e787f0 --- /dev/null +++ b/src/ntfs-3g.8.in @@ -0,0 +1,198 @@ +.\" Copyright (c) 2005-2006 Yura Pakhuchiy. +.\" Copyright (c) 2005 Richard Russon. +.\" Copyright (c) 2006 Szabolcs Szakacsits. +.\" This file may be copied under the terms of the GNU Public License. +.\" +.TH NTFS-3G 8 "October 2006" "ntfs-3g @VERSION@" +.SH NAME +ntfs-3g \- Third Generation NTFS Driver +.SH SYNOPSIS +.B ntfs-3g +.I device mount_point +[\fB\-o options\fR] +.SH DESCRIPTION +\fBntfs-3g\fR is an NTFS driver, which can +create, remove, rename files, directories, hard links, and +streams; it can read and write files, including +streams and sparse files; it can handle special files like +symbolic links, devices, and FIFOs; moreover it can also read +compressed files. +.SH OPTIONS +Below is a summary of the options that \fBntfs-3g\fR accepts. +.TP +.B uid=, gid=, umask= +Provide default owner, group, and access mode mask. +These options work as documented in mount(8). By +default, the files and directories are owned by the user who +mounted the volume but everybody has full read, write and +executable access, moreover browse permission to any directory. +If you want to use the currently limited permission +handling then use these options together with the +.B default_permissions, +.B fmask +and +.B dmask +options. The usage of the +.B default_permissions +option is a must in such cases. +.TP +.B default_permissions +By default FUSE doesn't check file access permissions, the +filesystem is free to implement its access policy or leave it to +the underlying file access mechanism (e.g. in case of network +filesystems). This option enables permission checking, restricting +accesses based on file modes. This option is usually useful +together with the +.B allow_other +mount option. +.TP +.B fmask=, dmask= +Instead of specifying umask which applies both to +files and directories, fmask applies only to files and +mask only to directories. +.TP +.B ro +Mount filesystem read\-only. +.TP +.B locale= +You can set locale with this option which is often required to make +visible files with national charaters. It's useful if locale +environment variables are not set before partitions had been mounted +from /etc/fstab. +.TP +.B force +Force mount even if the volume is scheduled for consistency check. +Use this option with caution and preferably with the +.B ro +option. +.TP +.B show_sys_files +Show the system files in directory listings. +Otherwise the default behaviour is to hide the system files. +Please note that even when this option is specified, "$MFT" +may not be visible due to a glibc bug. +Furthermore, irrespectively of show_sys_files, all +files are accessible by name, for example you can always do +"ls \-l '$UpCase'". +.TP +.B allow_other +This option overrides the security measure restricting file access +to the user mounting the filesystem. This option is by default only +allowed to root, but this restriction can be removed with a +configuration option described in the previous section. +.TP +.B large_read +Issue large read requests. This can improve performance for some +filesystems, but can also degrade performance. This option is mostly +useful on 2.4.X kernels, as on 2.6 kernels requests size is +automatically determined for optimum performance. +.TP +.B max_read= +With this option the maximum size of read operations can be set. +The default is infinite. Note that the size of read requests is +limited anyway to 32 pages (which is 128kbyte on i386). +.TP +.B silent +Do nothing on chmod and chown operations, but do not return error. +This option is on by default. +.TP +.B no_def_opts +By default ntfs-3g acts as "silent,allow_other" was passed to it, +this option cancel this behaviour. +.TP +.B streams_interface= +This option controls how the user can access Alternate Data Streams (ADS) +or in other words, named data streams. It can be set +to, one of \fBnone\fR, \fBwindows\fR or \fBxattr\fR. If the option is set to +\fBnone\fR, the user will have no access to the named data streams. If it's set +to \fBwindows\fR, then the user can access them just like in Windows (eg. cat +file:stream). If it's set to \fBxattr\fR, then the named data streams are +mapped to xattrs and user can manipulate them using \fB{get,set}fattr\fR +utilities. The default is \fBnone\fR. +.TP +.B debug +Makes ntfs-3g to not detach from terminal and print a lot of debug output from +libntfs-3g and FUSE. +.TP +.B no_detach +Same as above but with less debug output. +.SH ALTERNATE DATA STREAMS (ADS) +All data on NTFS is stored in streams. Every file has exactly one unnamed +data stream and can have many named data streams. The size of a file is the +size of its unnamed data stream. By default, \fBntfs-3g\fR will only read +the unnamed data stream. +.PP +By using the options "streams_interface=windows", you will be able to read +any named data streams, simply by specifying the stream's name after a colon. +For example: +.RS +.sp +cat some.mp3:artist +.sp +.RE +Named data streams act like normals files, so you can read from them, write to +them and even delete them (using rm). You can list all the named data streams +a file has by getting the "ntfs.streams.list" extended attribute. +.SH EXAMPLES +Mount /dev/hda1 to /mnt/windows: +.RS +.sp +.B ntfs-3g /dev/hda1 /mnt/windows +.sp +.RE +Read\-only mount /dev/hda5 to /home/user/mnt and make user with uid 1000 +to be the owner of all files: +.RS +.sp +.B ntfs-3g /dev/hda5 /home/user/mnt \-o ro,uid=1000 +.sp +.RE +/etc/fstab entry for the above: +.RS +.sp +.B /dev/hda5 /home/user/mnt ntfs\-3g ro,uid=1000 0 0 +.sp +.RE +Unmount /mnt/windows: +.RS +.sp +.B umount /mnt/windows +.sp +.RE +You can also unmount /mnt/windows with fusermount: +.RS +.sp +.B fusermount \-u /mnt/windows +.sp +.RE +.SH KNOWN ISSUES +Please see +.RS +.sp +http://www.ntfs-3g.org/support.html +.sp +.RE +for all known issues. +If you would find a new one in the latest release of +this software then please send an email describing it +according to the above page. You can also contact the +development team on the ntfs\-3g\-devel@lists.sf.net email +address anytime. +.SH AUTHORS +.B ntfs-3g +was based on and a major improvement to ntfsmount and libntfs which were +written by Yura Pakhuchiy and the Linux-NTFS team. The improvements were +made, the ntfs-3g project was initiated and currently led by long time +Linux-NTFS team developer Szabolcs Szakacsits (szaka@sienet.hu) to revive +the stalled open source development and project management. + +.SH THANKS +Several people made heroic efforts, often over five or more +years which resulted the ntfs-3g driver. Most importantly they are +Anton Altaparmakov, Richard Russon, Szabolcs Szakacsits, Yura Pakhuchiy, +Yuval Fedel, and the author of the groundbreaking FUSE filesystem development +framework, Miklos Szeredi. +.SH SEE ALSO +.BR ntfsprogs (8), +.BR attr (5), +.BR getfattr (1) diff --git a/src/ntfs-3g.c b/src/ntfs-3g.c new file mode 100644 index 00000000..10e2e085 --- /dev/null +++ b/src/ntfs-3g.c @@ -0,0 +1,2047 @@ +/** + * ntfs-3g - Third Generation NTFS Driver + * + * Copyright (c) 2005-2006 Yura Pakhuchiy + * Copyright (c) 2005 Yuval Fledel + * Copyright (c) 2006 Szabolcs Szakacsits + * + * This file is originated from the Linux-NTFS project. + * + * 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" + +#include + +#if !defined(FUSE_VERSION) || (FUSE_VERSION < 25) +#error "***********************************************************" +#error "* *" +#error "* Compilation requires at least FUSE version 2.5! *" +#error "* *" +#error "***********************************************************" +#endif + +#ifdef HAVE_STDIO_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_FCNTL_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_LOCALE_H +#include +#endif +#include +#ifdef HAVE_LIMITS_H +#include +#endif +#include +#include + +#ifdef HAVE_SETXATTR +#include +#endif + +#include "attrib.h" +#include "inode.h" +#include "volume.h" +#include "dir.h" +#include "unistr.h" +#include "layout.h" +#include "index.h" +#include "utils.h" +#include "version.h" +#include "ntfstime.h" +#include "misc.h" + +#ifndef PATH_MAX +#define PATH_MAX 4096 +#endif + +typedef struct { + fuse_fill_dir_t filler; + void *buf; +} ntfs_fuse_fill_context_t; + +typedef enum { + NF_STREAMS_INTERFACE_NONE, /* No access to named data streams. */ + NF_STREAMS_INTERFACE_XATTR, /* Map named data streams to xattrs. */ + NF_STREAMS_INTERFACE_WINDOWS, /* "file:stream" interface. */ +} ntfs_fuse_streams_interface; + +typedef struct { + ntfs_volume *vol; + int state; + long free_clusters; + long free_mft; + unsigned int uid; + unsigned int gid; + unsigned int fmask; + unsigned int dmask; + ntfs_fuse_streams_interface streams; + BOOL ro; + BOOL show_sys_files; + BOOL silent; + BOOL force; + BOOL debug; + BOOL noatime; + BOOL no_detach; +} ntfs_fuse_context_t; + +typedef enum { + NF_FreeClustersOutdate = (1 << 0), /* Information about amount of + free clusters is outdated. */ + NF_FreeMFTOutdate = (1 << 1), /* Information about amount of + free MFT records is outdated. */ +} ntfs_fuse_state_bits; + +static struct options { + char *mnt_point; /* Mount point */ + char *options; /* Mount options */ + char *device; /* Device to mount */ + int quiet; /* Less output */ + int verbose; /* Extra output */ +} opts; + +static const char *EXEC_NAME = "ntfs-3g"; +static char def_opts[] = "silent,allow_other,"; +static ntfs_fuse_context_t *ctx; +static u32 ntfs_sequence; + +static __inline__ void ntfs_fuse_mark_free_space_outdated(void) +{ + /* Mark information about free MFT record and clusters outdated. */ + ctx->state |= (NF_FreeClustersOutdate | NF_FreeMFTOutdate); +} + +/** + * ntfs_fuse_is_named_data_stream - check path to be to named data stream + * @path: path to check + * + * Returns 1 if path is to named data stream or 0 otherwise. + */ +static __inline__ int ntfs_fuse_is_named_data_stream(const char *path) +{ + if (strchr(path, ':') && ctx->streams == NF_STREAMS_INTERFACE_WINDOWS) + return 1; + return 0; +} + +static long ntfs_fuse_get_nr_free_mft_records(ntfs_volume *vol, s64 numof_inode) +{ + u8 *buf; + long nr_free = 0; + s64 br, total = 0; + + if (!(ctx->state & NF_FreeMFTOutdate)) + return ctx->free_mft; + buf = ntfs_malloc(vol->cluster_size); + if (!buf) + return -errno; + while (1) { + int i, j; + + br = ntfs_attr_pread(vol->mftbmp_na, total, + vol->cluster_size, buf); + if (br <= 0) + break; + total += br; + for (i = 0; i < br; i++) + for (j = 0; j < 8; j++) { + + if (--numof_inode < 0) + break; + + if (!((buf[i] >> j) & 1)) + nr_free++; + } + } + free(buf); + if (!total || br < 0) + return -errno; + ctx->free_mft = nr_free; + ctx->state &= ~(NF_FreeMFTOutdate); + return nr_free; +} + +static long ntfs_fuse_get_nr_free_clusters(ntfs_volume *vol) +{ + u8 *buf; + long nr_free = 0; + s64 br, total = 0; + + if (!(ctx->state & NF_FreeClustersOutdate)) + return ctx->free_clusters; + buf = ntfs_malloc(vol->cluster_size); + if (!buf) + return -errno; + while (1) { + int i, j; + + br = ntfs_attr_pread(vol->lcnbmp_na, total, + vol->cluster_size, buf); + if (br <= 0) + break; + total += br; + for (i = 0; i < br; i++) + for (j = 0; j < 8; j++) + if (!((buf[i] >> j) & 1)) + nr_free++; + } + free(buf); + if (!total || br < 0) + return -errno; + ctx->free_clusters = nr_free; + ctx->state &= ~(NF_FreeClustersOutdate); + return nr_free; +} + +/** + * ntfs_fuse_statfs - return information about mounted NTFS volume + * @path: ignored (but fuse requires it) + * @sfs: statfs structure in which to return the information + * + * Return information about the mounted NTFS volume @sb in the statfs structure + * pointed to by @sfs (this is initialized with zeros before ntfs_statfs is + * called). We interpret the values to be correct of the moment in time at + * which we are called. Most values are variable otherwise and this isn't just + * the free values but the totals as well. For example we can increase the + * total number of file nodes if we run out and we can keep doing this until + * there is no more space on the volume left at all. + * + * This code based on ntfs_statfs from ntfs kernel driver. + * + * Returns 0 on success or -errno on error. + */ +static int ntfs_fuse_statfs(const char *path __attribute__((unused)), + struct statvfs *sfs) +{ + long size, delta_bits; + u64 allocated_inodes; + ntfs_volume *vol; + + vol = ctx->vol; + if (!vol) + return -ENODEV; + + /* Optimal transfer block size. */ + sfs->f_bsize = vol->cluster_size; + sfs->f_frsize = vol->cluster_size; + /* + * Total data blocks in file system in units of f_bsize and since + * inodes are also stored in data blocs ($MFT is a file) this is just + * the total clusters. + */ + sfs->f_blocks = vol->nr_clusters; + + /* Free data blocks in file system in units of f_bsize. */ + size = ntfs_fuse_get_nr_free_clusters(vol); + if (size < 0) + size = 0; + + /* Free blocks avail to non-superuser, same as above on NTFS. */ + sfs->f_bavail = sfs->f_bfree = size; + + /* Free inodes on the free space */ + delta_bits = vol->cluster_size_bits - vol->mft_record_size_bits; + if (delta_bits >= 0) + size <<= delta_bits; + else + size >>= -delta_bits; + + /* Number of inodes in file system (at this point in time). */ + allocated_inodes = vol->mft_na->data_size >> vol->mft_record_size_bits; + sfs->f_files = allocated_inodes + size; + + /* Free inodes in fs (based on current total count). */ + size = ntfs_fuse_get_nr_free_mft_records(vol, allocated_inodes) + size; + if (size < 0) + size = 0; + sfs->f_ffree = size; + sfs->f_favail = 0; + + /* Maximum length of filenames. */ + sfs->f_namemax = NTFS_MAX_NAME_LEN; + return 0; +} + +/** + * ntfs_fuse_parse_path - split path to path and stream name. + * @org_path: path to split + * @path: pointer to buffer in which parsed path saved + * @stream_name: pointer to buffer where stream name in unicode saved + * + * This function allocates buffers for @*path and @*stream, user must free them + * after use. + * + * Return values: + * <0 Error occurred, return -errno; + * 0 No stream name, @*stream is not allocated and set to AT_UNNAMED. + * >0 Stream name length in unicode characters. + */ +static int ntfs_fuse_parse_path(const char *org_path, char **path, + ntfschar **stream_name) +{ + char *stream_name_mbs; + int res; + + stream_name_mbs = strdup(org_path); + if (!stream_name_mbs) + return -errno; + if (ctx->streams == NF_STREAMS_INTERFACE_WINDOWS) { + *path = strsep(&stream_name_mbs, ":"); + if (stream_name_mbs) { + *stream_name = NULL; + res = ntfs_mbstoucs(stream_name_mbs, stream_name, 0); + if (res < 0) + return -errno; + return res; + } + } else + *path = stream_name_mbs; + *stream_name = AT_UNNAMED; + return 0; +} + +static int ntfs_fuse_getattr(const char *org_path, struct stat *stbuf) +{ + int res = 0; + ntfs_inode *ni; + ntfs_attr *na; + ntfs_volume *vol; + char *path = NULL; + ntfschar *stream_name; + int stream_name_len; + + vol = ctx->vol; + stream_name_len = ntfs_fuse_parse_path(org_path, &path, &stream_name); + if (stream_name_len < 0) + return stream_name_len; + memset(stbuf, 0, sizeof(struct stat)); + ni = ntfs_pathname_to_inode(vol, NULL, path); + if (!ni) { + res = -errno; + goto exit; + } + if (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY && !stream_name_len) { + /* Directory. */ + stbuf->st_mode = S_IFDIR | (0777 & ~ctx->dmask); + na = ntfs_attr_open(ni, AT_INDEX_ALLOCATION, NTFS_INDEX_I30, 4); + if (na) { + stbuf->st_size = na->data_size; + stbuf->st_blocks = na->allocated_size >> + vol->sector_size_bits; + ntfs_attr_close(na); + } else { + stbuf->st_size = 0; + stbuf->st_blocks = 0; + } + stbuf->st_nlink = 1; /* Needed for correct find work. */ + } else { + /* Regular or Interix (INTX) file. */ + stbuf->st_mode = S_IFREG; + stbuf->st_size = ni->data_size; + stbuf->st_blocks = ni->allocated_size >> vol->sector_size_bits; + stbuf->st_nlink = le16_to_cpu(ni->mrec->link_count); + if (ni->flags & FILE_ATTR_SYSTEM || stream_name_len) { + na = ntfs_attr_open(ni, AT_DATA, stream_name, + stream_name_len); + if (!na) { + if (stream_name_len) + res = -ENOENT; + goto exit; + } + if (stream_name_len) { + stbuf->st_size = na->data_size; + stbuf->st_blocks = na->allocated_size >> + vol->sector_size_bits; + } + /* Check whether it's Interix FIFO or socket. */ + if (!(ni->flags & FILE_ATTR_HIDDEN) && + !stream_name_len) { + /* FIFO. */ + if (na->data_size == 0) + stbuf->st_mode = S_IFIFO; + /* Socket link. */ + if (na->data_size == 1) + stbuf->st_mode = S_IFSOCK; + } + /* + * Check whether it's Interix symbolic link, block or + * character device. + */ + if (na->data_size <= sizeof(INTX_FILE_TYPES) + sizeof( + ntfschar) * MAX_PATH && na->data_size > + sizeof(INTX_FILE_TYPES) && + !stream_name_len) { + INTX_FILE *intx_file; + + intx_file = ntfs_malloc(na->data_size); + if (!intx_file) { + res = -errno; + ntfs_attr_close(na); + goto exit; + } + if (ntfs_attr_pread(na, 0, na->data_size, + intx_file) != na->data_size) { + res = -errno; + free(intx_file); + ntfs_attr_close(na); + goto exit; + } + if (intx_file->magic == INTX_BLOCK_DEVICE && + na->data_size == offsetof( + INTX_FILE, device_end)) { + stbuf->st_mode = S_IFBLK; + stbuf->st_rdev = makedev(le64_to_cpu( + intx_file->major), + le64_to_cpu( + intx_file->minor)); + } + if (intx_file->magic == INTX_CHARACTER_DEVICE && + na->data_size == offsetof( + INTX_FILE, device_end)) { + stbuf->st_mode = S_IFCHR; + stbuf->st_rdev = makedev(le64_to_cpu( + intx_file->major), + le64_to_cpu( + intx_file->minor)); + } + if (intx_file->magic == INTX_SYMBOLIC_LINK) + stbuf->st_mode = S_IFLNK; + free(intx_file); + } + ntfs_attr_close(na); + } + stbuf->st_mode |= (0777 & ~ctx->fmask); + } + stbuf->st_uid = ctx->uid; + stbuf->st_gid = ctx->gid; + stbuf->st_ino = ni->mft_no; + stbuf->st_atime = ni->last_access_time; + stbuf->st_ctime = ni->last_mft_change_time; + stbuf->st_mtime = ni->last_data_change_time; +exit: + if (ni) + ntfs_inode_close(ni); + free(path); + if (stream_name_len) + free(stream_name); + return res; +} + +static int ntfs_fuse_readlink(const char *org_path, char *buf, size_t buf_size) +{ + char *path; + ntfschar *stream_name; + ntfs_inode *ni = NULL; + ntfs_attr *na = NULL; + INTX_FILE *intx_file = NULL; + int stream_name_len, res = 0; + + /* Get inode. */ + stream_name_len = ntfs_fuse_parse_path(org_path, &path, &stream_name); + if (stream_name_len < 0) + return stream_name_len; + if (stream_name_len > 0) { + res = -EINVAL; + goto exit; + } + ni = ntfs_pathname_to_inode(ctx->vol, NULL, path); + if (!ni) { + res = -errno; + goto exit; + } + /* Sanity checks. */ + if (!(ni->flags & FILE_ATTR_SYSTEM)) { + res = -EINVAL; + goto exit; + } + na = ntfs_attr_open(ni, AT_DATA, AT_UNNAMED, 0); + if (!na) { + res = -errno; + goto exit; + } + if (na->data_size <= sizeof(INTX_FILE_TYPES)) { + res = -EINVAL; + goto exit; + } + if (na->data_size > sizeof(INTX_FILE_TYPES) + + sizeof(ntfschar) * MAX_PATH) { + res = -ENAMETOOLONG; + goto exit; + } + /* Receive file content. */ + intx_file = ntfs_malloc(na->data_size); + if (!intx_file) { + res = -errno; + goto exit; + } + if (ntfs_attr_pread(na, 0, na->data_size, intx_file) != na->data_size) { + res = -errno; + goto exit; + } + /* Sanity check. */ + if (intx_file->magic != INTX_SYMBOLIC_LINK) { + res = -EINVAL; + goto exit; + } + /* Convert link from unicode to local encoding. */ + if (ntfs_ucstombs(intx_file->target, (na->data_size - + offsetof(INTX_FILE, target)) / sizeof(ntfschar), + &buf, buf_size) < 0) { + res = -errno; + goto exit; + } +exit: + if (intx_file) + free(intx_file); + if (na) + ntfs_attr_close(na); + if (ni) + ntfs_inode_close(ni); + free(path); + if (stream_name_len) + free(stream_name); + return res; +} + +static int ntfs_fuse_filler(ntfs_fuse_fill_context_t *fill_ctx, + const ntfschar *name, const int name_len, const int name_type, + const s64 pos __attribute__((unused)), const MFT_REF mref, + const unsigned dt_type __attribute__((unused))) +{ + char *filename = NULL; + + if (name_type == FILE_NAME_DOS) + return 0; + if (ntfs_ucstombs(name, name_len, &filename, 0) < 0) { + ntfs_log_error("Skipping unrepresentable filename (inode %lld):" + " %s\n", MREF(mref), strerror(errno)); + return 0; + } + if (ntfs_fuse_is_named_data_stream(filename)) { + ntfs_log_error("Unable to access '%s' (inode %lld) with " + "current named streams access interface.\n", + filename, MREF(mref)); + free(filename); + return 0; + } + if (MREF(mref) == FILE_root || MREF(mref) >= FILE_first_user || + ctx->show_sys_files) + fill_ctx->filler(fill_ctx->buf, filename, NULL, 0); + free(filename); + return 0; +} + +static int ntfs_fuse_readdir(const char *path, void *buf, + fuse_fill_dir_t filler, off_t offset __attribute__((unused)), + struct fuse_file_info *fi __attribute__((unused))) +{ + ntfs_fuse_fill_context_t fill_ctx; + ntfs_volume *vol; + ntfs_inode *ni; + s64 pos = 0; + int err = 0; + + vol = ctx->vol; + fill_ctx.filler = filler; + fill_ctx.buf = buf; + ni = ntfs_pathname_to_inode(vol, NULL, path); + if (!ni) + return -errno; + if (ntfs_readdir(ni, &pos, &fill_ctx, + (ntfs_filldir_t)ntfs_fuse_filler)) + err = -errno; + ntfs_inode_close(ni); + return err; +} + +static int ntfs_fuse_open(const char *org_path, + struct fuse_file_info *fi __attribute__((unused))) +{ + ntfs_volume *vol; + ntfs_inode *ni; + ntfs_attr *na; + int res = 0; + char *path = NULL; + ntfschar *stream_name; + int stream_name_len; + + stream_name_len = ntfs_fuse_parse_path(org_path, &path, &stream_name); + if (stream_name_len < 0) + return stream_name_len; + vol = ctx->vol; + ni = ntfs_pathname_to_inode(vol, NULL, path); + if (ni) { + na = ntfs_attr_open(ni, AT_DATA, stream_name, stream_name_len); + if (na) { + if (NAttrEncrypted(na)) + res = -EACCES; + ntfs_attr_close(na); + } else + res = -errno; + ntfs_inode_close(ni); + } else + res = -errno; + free(path); + if (stream_name_len) + free(stream_name); + return res; +} + +static int ntfs_fuse_read(const char *org_path, char *buf, size_t size, + off_t offset, struct fuse_file_info *fi __attribute__((unused))) +{ + ntfs_volume *vol; + ntfs_inode *ni = NULL; + ntfs_attr *na = NULL; + char *path = NULL; + ntfschar *stream_name; + int stream_name_len, res, total = 0; + + stream_name_len = ntfs_fuse_parse_path(org_path, &path, &stream_name); + if (stream_name_len < 0) + return stream_name_len; + vol = ctx->vol; + ni = ntfs_pathname_to_inode(vol, NULL, path); + if (!ni) { + res = -errno; + goto exit; + } + na = ntfs_attr_open(ni, AT_DATA, stream_name, stream_name_len); + if (!na) { + res = -errno; + goto exit; + } + if (offset + size > na->data_size) + size = na->data_size - offset; + while (size) { + res = ntfs_attr_pread(na, offset, size, buf); + if (res < (s64)size) + ntfs_log_error("ntfs_attr_pread returned less bytes " + "than requested.\n"); + if (res <= 0) { + res = -errno; + goto exit; + } + size -= res; + offset += res; + total += res; + } + res = total; +exit: + if (na) + ntfs_attr_close(na); + if (ni && ntfs_inode_close(ni)) + ntfs_log_perror("Failed to close inode"); + free(path); + if (stream_name_len) + free(stream_name); + return res; +} + +static int ntfs_fuse_write(const char *org_path, const char *buf, size_t size, + off_t offset, struct fuse_file_info *fi __attribute__((unused))) +{ + ntfs_volume *vol; + ntfs_inode *ni = NULL; + ntfs_attr *na = NULL; + char *path = NULL; + ntfschar *stream_name; + int stream_name_len, res, total = 0; + + stream_name_len = ntfs_fuse_parse_path(org_path, &path, &stream_name); + if (stream_name_len < 0) { + res = stream_name_len; + goto out; + } + vol = ctx->vol; + ni = ntfs_pathname_to_inode(vol, NULL, path); + if (!ni) { + res = -errno; + goto exit; + } + na = ntfs_attr_open(ni, AT_DATA, stream_name, stream_name_len); + if (!na) { + res = -errno; + goto exit; + } + while (size) { + res = ntfs_attr_pwrite(na, offset, size, buf); + if (res < (s64)size) + ntfs_log_error("ntfs_attr_pwrite returned less bytes " + "than requested.\n"); + if (res <= 0) { + res = -errno; + goto exit; + } + size -= res; + offset += res; + total += res; + } + res = total; +exit: + ntfs_fuse_mark_free_space_outdated(); + if (na) + ntfs_attr_close(na); + if (ni && ntfs_inode_close(ni)) + ntfs_log_perror("Failed to close inode"); + free(path); + if (stream_name_len) + free(stream_name); +out: + return res; +} + +static int ntfs_fuse_truncate(const char *org_path, off_t size) +{ + ntfs_volume *vol; + ntfs_inode *ni = NULL; + ntfs_attr *na; + int res; + char *path = NULL; + ntfschar *stream_name; + int stream_name_len; + + stream_name_len = ntfs_fuse_parse_path(org_path, &path, &stream_name); + if (stream_name_len < 0) + return stream_name_len; + vol = ctx->vol; + ni = ntfs_pathname_to_inode(vol, NULL, path); + if (!ni) { + res = -errno; + goto exit; + } + na = ntfs_attr_open(ni, AT_DATA, stream_name, stream_name_len); + if (!na) { + res = -errno; + goto exit; + } + res = ntfs_attr_truncate(na, size); + // FIXME: check the usage and the importance of the return value + if (res) + res = -1; + ntfs_fuse_mark_free_space_outdated(); + ntfs_attr_close(na); +exit: + if (ni && ntfs_inode_close(ni)) + ntfs_log_perror("Failed to close inode"); + free(path); + if (stream_name_len) + free(stream_name); + return res; +} + +static int ntfs_fuse_chmod(const char *path, + mode_t mode __attribute__((unused))) +{ + if (ntfs_fuse_is_named_data_stream(path)) + return -EINVAL; /* n/a for named data streams. */ + if (ctx->silent) + return 0; + return -EOPNOTSUPP; +} + +static int ntfs_fuse_chown(const char *path, uid_t uid, gid_t gid) +{ + if (ntfs_fuse_is_named_data_stream(path)) + return -EINVAL; /* n/a for named data streams. */ + if (ctx->silent) + return 0; + if (uid == ctx->uid && gid == ctx->gid) + return 0; + return -EOPNOTSUPP; +} + +static int ntfs_fuse_create(const char *org_path, dev_t type, dev_t dev, + const char *target) +{ + char *name; + ntfschar *uname = NULL, *utarget = NULL; + ntfs_inode *dir_ni = NULL, *ni; + char *path; + int res = 0, uname_len, utarget_len; + + path = strdup(org_path); + if (!path) + return -errno; + /* Generate unicode filename. */ + name = strrchr(path, '/'); + name++; + uname_len = ntfs_mbstoucs(name, &uname, 0); + if (uname_len < 0) { + res = -errno; + goto exit; + } + /* Open parent directory. */ + *name = 0; + dir_ni = ntfs_pathname_to_inode(ctx->vol, NULL, path); + if (!dir_ni) { + res = -errno; + goto exit; + } + /* Create object specified in @type. */ + switch (type) { + case S_IFCHR: + case S_IFBLK: + ni = ntfs_create_device(dir_ni, uname, uname_len, type, + dev); + break; + case S_IFLNK: + utarget_len = ntfs_mbstoucs(target, &utarget, 0); + if (utarget_len < 0) { + res = -errno; + goto exit; + } + ni = ntfs_create_symlink(dir_ni, uname, uname_len, + utarget, utarget_len); + break; + default: + ni = ntfs_create(dir_ni, uname, uname_len, type); + break; + } + if (ni) + ntfs_inode_close(ni); + else + res = -errno; +exit: + free(uname); + if (dir_ni) + ntfs_inode_close(dir_ni); + if (utarget) + free(utarget); + free(path); + return res; +} + +static int ntfs_fuse_create_stream(const char *path, + ntfschar *stream_name, const int stream_name_len) +{ + ntfs_inode *ni; + int res = 0; + + ni = ntfs_pathname_to_inode(ctx->vol, NULL, path); + if (!ni) { + res = -errno; + if (res == -ENOENT) { + /* + * If such file does not exist, create it and try once + * again to add stream to it. + */ + res = ntfs_fuse_create(path, S_IFREG, 0, NULL); + if (!res) + return ntfs_fuse_create_stream(path, + stream_name, stream_name_len); + else + res = -errno; + } + return res; + } + if (ntfs_attr_add(ni, AT_DATA, stream_name, stream_name_len, NULL, 0)) + res = -errno; + if (ntfs_inode_close(ni)) + ntfs_log_perror("Failed to close inode"); + return res; +} + +static int ntfs_fuse_mknod(const char *org_path, mode_t mode, dev_t dev) +{ + char *path = NULL; + ntfschar *stream_name; + int stream_name_len; + int res = 0; + + stream_name_len = ntfs_fuse_parse_path(org_path, &path, &stream_name); + if (stream_name_len < 0) + return stream_name_len; + if (stream_name_len && !S_ISREG(mode)) { + res = -EINVAL; + goto exit; + } + if (!stream_name_len) + res = ntfs_fuse_create(path, mode & S_IFMT, dev, NULL); + else + res = ntfs_fuse_create_stream(path, stream_name, + stream_name_len); + ntfs_fuse_mark_free_space_outdated(); +exit: + free(path); + if (stream_name_len) + free(stream_name); + return res; +} + +static int ntfs_fuse_symlink(const char *to, const char *from) +{ + if (ntfs_fuse_is_named_data_stream(from)) + return -EINVAL; /* n/a for named data streams. */ + ntfs_fuse_mark_free_space_outdated(); + return ntfs_fuse_create(from, S_IFLNK, 0, to); +} + +static int ntfs_fuse_link(const char *old_path, const char *new_path) +{ + char *name; + ntfschar *uname = NULL; + ntfs_inode *dir_ni = NULL, *ni; + char *path; + int res = 0, uname_len; + + if (ntfs_fuse_is_named_data_stream(old_path)) + return -EINVAL; /* n/a for named data streams. */ + if (ntfs_fuse_is_named_data_stream(new_path)) + return -EINVAL; /* n/a for named data streams. */ + path = strdup(new_path); + if (!path) + return -errno; + /* Open file for which create hard link. */ + ni = ntfs_pathname_to_inode(ctx->vol, NULL, old_path); + if (!ni) { + res = -errno; + goto exit; + } + /* Generate unicode filename. */ + name = strrchr(path, '/'); + name++; + uname_len = ntfs_mbstoucs(name, &uname, 0); + if (uname_len < 0) { + res = -errno; + goto exit; + } + /* Open parent directory. */ + *name = 0; + dir_ni = ntfs_pathname_to_inode(ctx->vol, NULL, path); + if (!dir_ni) { + res = -errno; + goto exit; + } + ntfs_fuse_mark_free_space_outdated(); + /* Create hard link. */ + if (ntfs_link(ni, dir_ni, uname, uname_len)) + res = -errno; +exit: + if (ni) + ntfs_inode_close(ni); + free(uname); + if (dir_ni) + ntfs_inode_close(dir_ni); + free(path); + return res; +} + +static int ntfs_fuse_rm(const char *org_path) +{ + char *name; + ntfschar *uname = NULL; + ntfs_inode *dir_ni = NULL, *ni; + char *path; + int res = 0, uname_len; + + path = strdup(org_path); + if (!path) + return -errno; + /* Open object for delete. */ + ni = ntfs_pathname_to_inode(ctx->vol, NULL, path); + if (!ni) { + res = -errno; + goto exit; + } + /* Generate unicode filename. */ + name = strrchr(path, '/'); + name++; + uname_len = ntfs_mbstoucs(name, &uname, 0); + if (uname_len < 0) { + res = -errno; + goto exit; + } + /* Open parent directory. */ + *name = 0; + dir_ni = ntfs_pathname_to_inode(ctx->vol, NULL, path); + if (!dir_ni) { + res = -errno; + goto exit; + } + /* Delete object. */ + if (ntfs_delete(ni, dir_ni, uname, uname_len)) + res = -errno; + ni = NULL; +exit: + if (ni) + ntfs_inode_close(ni); + free(uname); + if (dir_ni) + ntfs_inode_close(dir_ni); + free(path); + return res; +} + +static int ntfs_fuse_rm_stream(const char *path, ntfschar *stream_name, + const int stream_name_len) +{ + ntfs_inode *ni; + ntfs_attr *na; + int res = 0; + + ni = ntfs_pathname_to_inode(ctx->vol, NULL, path); + if (!ni) + return -errno; + na = ntfs_attr_open(ni, AT_DATA, stream_name, stream_name_len); + if (!na) { + res = -errno; + goto exit; + } + if (ntfs_attr_rm(na)) { + res = -errno; + ntfs_attr_close(na); + } +exit: + if (ntfs_inode_close(ni)) + ntfs_log_perror("Failed to close inode"); + return res; +} + +static int ntfs_fuse_unlink(const char *org_path) +{ + char *path = NULL; + ntfschar *stream_name; + int stream_name_len; + int res = 0; + + stream_name_len = ntfs_fuse_parse_path(org_path, &path, &stream_name); + if (stream_name_len < 0) + return stream_name_len; + if (!stream_name_len) + res = ntfs_fuse_rm(path); + else + res = ntfs_fuse_rm_stream(path, stream_name, stream_name_len); + ntfs_fuse_mark_free_space_outdated(); + free(path); + if (stream_name_len) + free(stream_name); + return res; +} + +static int ntfs_fuse_safe_rename(const char *old_path, + const char *new_path, + const char *tmp) +{ + int ret; + + ntfs_log_trace("Entering"); + + ret = ntfs_fuse_link(new_path, tmp); + if (ret) + return ret; + + ret = ntfs_fuse_unlink(new_path); + if (!ret) { + + ret = ntfs_fuse_link(old_path, new_path); + if (ret) + goto restore; + + ret = ntfs_fuse_unlink(old_path); + if (ret) { + if (ntfs_fuse_unlink(new_path)) + goto err; + goto restore; + } + } + + goto cleanup; +restore: + if (ntfs_fuse_link(tmp, new_path)) { +err: + ntfs_log_perror("Rename failed. Existing file '%s' was renamed " + "to '%s'", new_path, tmp); + } else { +cleanup: + ntfs_fuse_unlink(tmp); + } + return ret; +} + +static int ntfs_fuse_rename_existing_dest(const char *old_path, const char *new_path) +{ + int ret, len; + char *tmp; + const char *ext = ".ntfs-3g-"; + + ntfs_log_trace("Entering"); + + len = strlen(new_path) + strlen(ext) + 10 + 1; /* wc(str(2^32)) + \0 */ + tmp = ntfs_malloc(len); + if (!tmp) + return -errno; + + ret = snprintf(tmp, len, "%s%s%010d", new_path, ext, ++ntfs_sequence); + if (ret != len - 1) { + ntfs_log_error("snprintf failed: %d != %d\n", ret, len - 1); + ret = -EOVERFLOW; + } else + ret = ntfs_fuse_safe_rename(old_path, new_path, tmp); + + free(tmp); + return ret; +} + +static int ntfs_fuse_rename(const char *old_path, const char *new_path) +{ + int ret, stream_name_len; + char *path = NULL; + ntfschar *stream_name; + ntfs_inode *ni; + + ntfs_log_debug("rename: old: '%s' new: '%s'\n", old_path, new_path); + + /* + * FIXME: Rename should be atomic. + */ + stream_name_len = ntfs_fuse_parse_path(new_path, &path, &stream_name); + if (stream_name_len < 0) + return stream_name_len; + + ni = ntfs_pathname_to_inode(ctx->vol, NULL, path); + if (ni) { + ret = ntfs_check_empty_dir(ni); + if (ret < 0) { + ret = -errno; + ntfs_inode_close(ni); + goto out; + } + + ntfs_inode_close(ni); + + ret = ntfs_fuse_rename_existing_dest(old_path, new_path); + goto out; + } + + ret = ntfs_fuse_link(old_path, new_path); + if (ret) + goto out; + + ret = ntfs_fuse_unlink(old_path); + if (ret) + ntfs_fuse_unlink(new_path); +out: + free(path); + if (stream_name_len) + free(stream_name); + return ret; +} + +static int ntfs_fuse_mkdir(const char *path, + mode_t mode __attribute__((unused))) +{ + if (ntfs_fuse_is_named_data_stream(path)) + return -EINVAL; /* n/a for named data streams. */ + ntfs_fuse_mark_free_space_outdated(); + return ntfs_fuse_create(path, S_IFDIR, 0, NULL); +} + +static int ntfs_fuse_rmdir(const char *path) +{ + if (ntfs_fuse_is_named_data_stream(path)) + return -EINVAL; /* n/a for named data streams. */ + ntfs_fuse_mark_free_space_outdated(); + return ntfs_fuse_rm(path); +} + +static int ntfs_fuse_utime(const char *path, struct utimbuf *buf) +{ + ntfs_inode *ni; + + if (ntfs_fuse_is_named_data_stream(path)) + return -EINVAL; /* n/a for named data streams. */ + ni = ntfs_pathname_to_inode(ctx->vol, NULL, path); + if (!ni) + return -errno; + if (buf) { + ni->last_access_time = buf->actime; + ni->last_data_change_time = buf->modtime; + ni->last_mft_change_time = buf->modtime; + } else { + time_t now; + + now = time(NULL); + ni->last_access_time = now; + ni->last_data_change_time = now; + ni->last_mft_change_time = now; + } + NInoFileNameSetDirty(ni); + NInoSetDirty(ni); + if (ntfs_inode_close(ni)) + ntfs_log_perror("Failed to close inode"); + return 0; +} + +#ifdef HAVE_SETXATTR + +static const char nf_ns_xattr_preffix[] = "user."; +static const int nf_ns_xattr_preffix_len = 5; + +static int ntfs_fuse_listxattr(const char *path, char *list, size_t size) +{ + ntfs_attr_search_ctx *actx = NULL; + ntfs_volume *vol; + ntfs_inode *ni; + char *to = list; + int ret = 0; + + if (ctx->streams != NF_STREAMS_INTERFACE_XATTR) + return -EOPNOTSUPP; + vol = ctx->vol; + if (!vol) + return -ENODEV; + ni = ntfs_pathname_to_inode(vol, NULL, path); + if (!ni) + return -errno; + actx = ntfs_attr_get_search_ctx(ni, NULL); + if (!actx) { + ret = -errno; + ntfs_inode_close(ni); + goto exit; + } + while (!ntfs_attr_lookup(AT_DATA, NULL, 0, CASE_SENSITIVE, + 0, NULL, 0, actx)) { + char *tmp_name = NULL; + int tmp_name_len; + + if (!actx->attr->name_length) + continue; + tmp_name_len = ntfs_ucstombs((ntfschar *)((u8*)actx->attr + + le16_to_cpu(actx->attr->name_offset)), + actx->attr->name_length, &tmp_name, 0); + if (tmp_name_len < 0) { + ret = -errno; + goto exit; + } + ret += tmp_name_len + nf_ns_xattr_preffix_len + 1; + if (size) { + if ((size_t)ret <= size) { + strcpy(to, nf_ns_xattr_preffix); + to += nf_ns_xattr_preffix_len; + strncpy(to, tmp_name, tmp_name_len); + to += tmp_name_len; + *to = 0; + to++; + } else { + free(tmp_name); + ret = -ERANGE; + goto exit; + } + } + free(tmp_name); + } + if (errno != ENOENT) + ret = -errno; +exit: + if (actx) + ntfs_attr_put_search_ctx(actx); + ntfs_inode_close(ni); + ntfs_log_debug("return %d\n", ret); + return ret; +} + +static int ntfs_fuse_getxattr_windows(const char *path, const char *name, + char *value, size_t size) +{ + ntfs_attr_search_ctx *actx = NULL; + ntfs_volume *vol; + ntfs_inode *ni; + char *to = value; + int ret = 0; + + if (strcmp(name, "ntfs.streams.list")) + return -EOPNOTSUPP; + vol = ctx->vol; + if (!vol) + return -ENODEV; + ni = ntfs_pathname_to_inode(vol, NULL, path); + if (!ni) + return -errno; + actx = ntfs_attr_get_search_ctx(ni, NULL); + if (!actx) { + ret = -errno; + ntfs_inode_close(ni); + goto exit; + } + while (!ntfs_attr_lookup(AT_DATA, NULL, 0, CASE_SENSITIVE, + 0, NULL, 0, actx)) { + char *tmp_name = NULL; + int tmp_name_len; + + if (!actx->attr->name_length) + continue; + tmp_name_len = ntfs_ucstombs((ntfschar *)((u8*)actx->attr + + le16_to_cpu(actx->attr->name_offset)), + actx->attr->name_length, &tmp_name, 0); + if (tmp_name_len < 0) { + ret = -errno; + goto exit; + } + if (ret) + ret++; /* For space delimiter. */ + ret += tmp_name_len; + if (size) { + if ((size_t)ret <= size) { + /* Don't add space to the beginning of line. */ + if (to != value) { + *to = ' '; + to++; + } + strncpy(to, tmp_name, tmp_name_len); + to += tmp_name_len; + } else { + free(tmp_name); + ret = -ERANGE; + goto exit; + } + } + free(tmp_name); + } + if (errno != ENOENT) + ret = -errno; +exit: + if (actx) + ntfs_attr_put_search_ctx(actx); + ntfs_inode_close(ni); + return ret; +} + +static int ntfs_fuse_getxattr(const char *path, const char *name, + char *value, size_t size) +{ + ntfs_volume *vol; + ntfs_inode *ni; + ntfs_attr *na = NULL; + ntfschar *lename = NULL; + int res, lename_len; + + if (ctx->streams == NF_STREAMS_INTERFACE_WINDOWS) + return ntfs_fuse_getxattr_windows(path, name, value, size); + if (ctx->streams != NF_STREAMS_INTERFACE_XATTR) + return -EOPNOTSUPP; + if (strncmp(name, nf_ns_xattr_preffix, nf_ns_xattr_preffix_len) || + strlen(name) == (size_t)nf_ns_xattr_preffix_len) + return -ENODATA; + vol = ctx->vol; + if (!vol) + return -ENODEV; + ni = ntfs_pathname_to_inode(vol, NULL, path); + if (!ni) + return -errno; + lename_len = ntfs_mbstoucs(name + nf_ns_xattr_preffix_len, &lename, 0); + if (lename_len == -1) { + res = -errno; + goto exit; + } + na = ntfs_attr_open(ni, AT_DATA, lename, lename_len); + if (!na) { + res = -ENODATA; + goto exit; + } + if (size) { + if (size >= na->data_size) { + res = ntfs_attr_pread(na, 0, na->data_size, value); + if (res != na->data_size) + res = -errno; + } else + res = -ERANGE; + } else + res = na->data_size; +exit: + if (na) + ntfs_attr_close(na); + free(lename); + if (ntfs_inode_close(ni)) + ntfs_log_perror("Failed to close inode"); + return res; +} + +static int ntfs_fuse_setxattr(const char *path, const char *name, + const char *value, size_t size, int flags) +{ + ntfs_volume *vol; + ntfs_inode *ni; + ntfs_attr *na = NULL; + ntfschar *lename = NULL; + int res, lename_len; + + if (ctx->streams != NF_STREAMS_INTERFACE_XATTR) + return -EOPNOTSUPP; + if (strncmp(name, nf_ns_xattr_preffix, nf_ns_xattr_preffix_len) || + strlen(name) == (size_t)nf_ns_xattr_preffix_len) + return -EACCES; + vol = ctx->vol; + if (!vol) + return -ENODEV; + ni = ntfs_pathname_to_inode(vol, NULL, path); + if (!ni) + return -errno; + lename_len = ntfs_mbstoucs(name + nf_ns_xattr_preffix_len, &lename, 0); + if (lename_len == -1) { + res = -errno; + goto exit; + } + na = ntfs_attr_open(ni, AT_DATA, lename, lename_len); + if (na && flags == XATTR_CREATE) { + res = -EEXIST; + goto exit; + } + ntfs_fuse_mark_free_space_outdated(); + if (!na) { + if (flags == XATTR_REPLACE) { + res = -ENODATA; + goto exit; + } + if (ntfs_attr_add(ni, AT_DATA, lename, lename_len, NULL, 0)) { + res = -errno; + goto exit; + } + na = ntfs_attr_open(ni, AT_DATA, lename, lename_len); + if (!na) { + res = -errno; + goto exit; + } + } + res = ntfs_attr_pwrite(na, 0, size, value); + if (res != (s64) size) + res = -errno; + else + res = 0; +exit: + if (na) + ntfs_attr_close(na); + free(lename); + if (ntfs_inode_close(ni)) + ntfs_log_perror("Failed to close inode"); + return res; +} + +static int ntfs_fuse_removexattr(const char *path, const char *name) +{ + ntfs_volume *vol; + ntfs_inode *ni; + ntfs_attr *na = NULL; + ntfschar *lename = NULL; + int res = 0, lename_len; + + + if (ctx->streams != NF_STREAMS_INTERFACE_XATTR) + return -EOPNOTSUPP; + if (strncmp(name, nf_ns_xattr_preffix, nf_ns_xattr_preffix_len) || + strlen(name) == (size_t)nf_ns_xattr_preffix_len) + return -ENODATA; + vol = ctx->vol; + if (!vol) + return -ENODEV; + ni = ntfs_pathname_to_inode(vol, NULL, path); + if (!ni) + return -errno; + lename_len = ntfs_mbstoucs(name + nf_ns_xattr_preffix_len, &lename, 0); + if (lename_len == -1) { + res = -errno; + goto exit; + } + na = ntfs_attr_open(ni, AT_DATA, lename, lename_len); + if (!na) { + res = -ENODATA; + goto exit; + } + ntfs_fuse_mark_free_space_outdated(); + if (ntfs_attr_rm(na)) + res = -errno; + else + na = NULL; +exit: + if (na) + ntfs_attr_close(na); + free(lename); + if (ntfs_inode_close(ni)) + ntfs_log_perror("Failed to close inode"); + return res; +} + +#endif /* HAVE_SETXATTR */ + +static void ntfs_fuse_destroy(void) +{ + if (!ctx) + return; + + if (ctx->vol) { + ntfs_log_info("Unmounting %s (%s)\n", opts.device, + ctx->vol->vol_name); + if (ntfs_umount(ctx->vol, FALSE)) + ntfs_log_perror("Failed to unmount volume"); + } + free(ctx); + ctx = NULL; + free(opts.device); +} + +static void ntfs_fuse_destroy2(void *unused __attribute__((unused))) +{ + ntfs_fuse_destroy(); +} + +static struct fuse_operations ntfs_fuse_oper = { + .getattr = ntfs_fuse_getattr, + .readlink = ntfs_fuse_readlink, + .readdir = ntfs_fuse_readdir, + .open = ntfs_fuse_open, + .read = ntfs_fuse_read, + .write = ntfs_fuse_write, + .truncate = ntfs_fuse_truncate, + .statfs = ntfs_fuse_statfs, + .chmod = ntfs_fuse_chmod, + .chown = ntfs_fuse_chown, + .mknod = ntfs_fuse_mknod, + .symlink = ntfs_fuse_symlink, + .link = ntfs_fuse_link, + .unlink = ntfs_fuse_unlink, + .rename = ntfs_fuse_rename, + .mkdir = ntfs_fuse_mkdir, + .rmdir = ntfs_fuse_rmdir, + .utime = ntfs_fuse_utime, + .destroy = ntfs_fuse_destroy2, +#ifdef HAVE_SETXATTR + .getxattr = ntfs_fuse_getxattr, + .setxattr = ntfs_fuse_setxattr, + .removexattr = ntfs_fuse_removexattr, + .listxattr = ntfs_fuse_listxattr, +#endif /* HAVE_SETXATTR */ +}; + +static int ntfs_fuse_init(void) +{ + ctx = ntfs_malloc(sizeof(ntfs_fuse_context_t)); + if (!ctx) + return -1; + + *ctx = (ntfs_fuse_context_t) { + .state = NF_FreeClustersOutdate | NF_FreeMFTOutdate, + .uid = geteuid(), + .gid = getegid(), + .fmask = 0, + .dmask = 0, + .streams = NF_STREAMS_INTERFACE_NONE, + }; + return 0; +} + +static int ntfs_fuse_mount(const char *device) +{ + ntfs_volume *vol; + + vol = utils_mount_volume(device, ((ctx->ro) ? MS_RDONLY : 0) | + ((ctx->noatime) ? MS_NOATIME : 0), ctx->force); + if (!vol) + return -1; + + ctx->vol = vol; + return 0; +} + +static void signal_handler(int arg __attribute__((unused))) +{ + fuse_exit((fuse_get_context())->fuse); +} + +static char *parse_mount_options(const char *org_options) +{ + char *options, *s, *opt, *val, *ret; + BOOL no_def_opts = FALSE; + + /* + * +7 for "fsname=". + * +1 for comma. + * +1 for null-terminator. + * +PATH_MAX for resolved by realpath() device name. + */ + ret = ntfs_malloc(strlen(def_opts) + strlen(org_options) + 9 + PATH_MAX); + if (!ret) + return NULL; + + *ret = 0; + options = strdup(org_options); + if (!options) { + ntfs_log_perror("strdup failed"); + return NULL; + } + + /* + * FIXME: Due to major performance hit and interference + * issues, always use the 'noatime' options for now. + */ + ctx->noatime = TRUE; + strcat(ret, "noatime,"); + + ctx->silent = TRUE; + + s = options; + while ((val = strsep(&s, ","))) { + opt = strsep(&val, "="); + if (!strcmp(opt, "ro")) { /* Read-only mount. */ + if (val) { + ntfs_log_error("'ro' option should not have " + "value.\n"); + goto err_exit; + } + ctx->ro = TRUE; + strcat(ret, "ro,"); + } else if (!strcmp(opt, "noatime")) { + if (val) { + ntfs_log_error("'noatime' option should not " + "have value.\n"); + goto err_exit; + } + } else if (!strcmp(opt, "fake_rw")) { + if (val) { + ntfs_log_error("'fake_rw' option should not " + "have value.\n"); + goto err_exit; + } + ctx->ro = TRUE; + } else if (!strcmp(opt, "fsname")) { /* Filesystem name. */ + /* + * We need this to be able to check whether filesystem + * mounted or not. + */ + ntfs_log_error("'fsname' is unsupported option.\n"); + goto err_exit; + } else if (!strcmp(opt, "no_def_opts")) { + if (val) { + ntfs_log_error("'no_def_opts' option should " + "not have value.\n"); + goto err_exit; + } + no_def_opts = TRUE; /* Don't add default options. */ + } else if (!strcmp(opt, "umask")) { + if (!val) { + ntfs_log_error("'umask' option should have " + "value.\n"); + goto err_exit; + } + sscanf(val, "%o", &ctx->fmask); + ctx->dmask = ctx->fmask; + } else if (!strcmp(opt, "fmask")) { + if (!val) { + ntfs_log_error("'fmask' option should have " + "value.\n"); + goto err_exit; + } + sscanf(val, "%o", &ctx->fmask); + } else if (!strcmp(opt, "dmask")) { + if (!val) { + ntfs_log_error("'dmask' option should have " + "value.\n"); + goto err_exit; + } + sscanf(val, "%o", &ctx->dmask); + } else if (!strcmp(opt, "uid")) { + if (!val) { + ntfs_log_error("'uid' option should have " + "value.\n"); + goto err_exit; + } + sscanf(val, "%i", &ctx->uid); + } else if (!strcmp(opt, "gid")) { + if (!val) { + ntfs_log_error("'gid' option should have " + "value.\n"); + goto err_exit; + } + sscanf(val, "%i", &ctx->gid); + } else if (!strcmp(opt, "show_sys_files")) { + if (val) { + ntfs_log_error("'show_sys_files' option should " + "not have value.\n"); + goto err_exit; + } + ctx->show_sys_files = TRUE; + } else if (!strcmp(opt, "silent")) { + if (val) { + ntfs_log_error("'silent' option should " + "not have value.\n"); + goto err_exit; + } + ctx->silent = TRUE; + } else if (!strcmp(opt, "force")) { + if (val) { + ntfs_log_error("'force' option should not " + "have value.\n"); + goto err_exit; + } + ctx->force = TRUE; + } else if (!strcmp(opt, "locale")) { + if (!val) { + ntfs_log_error("'locale' option should have " + "value.\n"); + goto err_exit; + } + if (!setlocale(LC_ALL, val)) + ntfs_log_error("Couldn't set locale to %s thus " + "you may not see properly or " + "at all some files.\n", val); + } else if (!strcmp(opt, "streams_interface")) { + if (!val) { + ntfs_log_error("'streams_interface' option " + "should have value.\n"); + goto err_exit; + } + if (!strcmp(val, "none")) + ctx->streams = NF_STREAMS_INTERFACE_NONE; + else if (!strcmp(val, "xattr")) + ctx->streams = NF_STREAMS_INTERFACE_XATTR; + else if (!strcmp(val, "windows")) + ctx->streams = NF_STREAMS_INTERFACE_WINDOWS; + else { + ntfs_log_error("Invalid named data streams " + "access interface.\n"); + goto err_exit; + } + } else if (!strcmp(opt, "noauto")) { + /* Don't pass noauto option to fuse. */ + } else if (!strcmp(opt, "debug")) { + if (val) { + ntfs_log_error("'debug' option should not have " + "value.\n"); + goto err_exit; + } + ctx->debug = TRUE; + ntfs_log_set_levels(NTFS_LOG_LEVEL_DEBUG); + ntfs_log_set_levels(NTFS_LOG_LEVEL_TRACE); + } else if (!strcmp(opt, "no_detach")) { + if (val) { + ntfs_log_error("'no_detach' option should not " + "have value.\n"); + goto err_exit; + } + ctx->no_detach = TRUE; + } else if (!strcmp(opt, "remount")) { + ntfs_log_error("Remounting is not supported at present." + " You have to umount volume and then " + "mount it once again.\n"); + goto err_exit; + } else { /* Probably FUSE option. */ + strcat(ret, opt); + if (val) { + strcat(ret, "="); + strcat(ret, val); + } + strcat(ret, ","); + } + } + if (!no_def_opts) + strcat(ret, def_opts); + strcat(ret, "fsname="); + strcat(ret, opts.device); +exit: + free(options); + return ret; +err_exit: + free(ret); + ret = NULL; + goto exit; +} + +static void usage(void) +{ + ntfs_log_info("\n%s %s - Third Generation NTFS Driver\n\n", + EXEC_NAME, VERSION); + ntfs_log_info("Copyright (C) 2005-2006 Yura Pakhuchiy\n"); + ntfs_log_info("Copyright (C) 2006 Szabolcs Szakacsits\n\n"); + ntfs_log_info("Usage: %s device mount_point [-o options]\n\n", + EXEC_NAME); + ntfs_log_info("Options: ro, force, default_permissions, umask, " + "uid, gid, fmask, dmask, \n\t" + " locale, show_sys_files, no_def_opts, " + "streams_interface.\n\t" + " Please see the details in the manual.\n\n"); + ntfs_log_info("%s\n", ntfs_home); +} + +#ifndef HAVE_REALPATH +/* If there is no realpath() on the system, provide a dummy one. */ +static char *realpath(const char *path, char *resolved_path) +{ + strncpy(resolved_path, path, PATH_MAX); + resolved_path[PATH_MAX] = '\0'; + return resolved_path; +} +#endif + +/** + * parse_options - Read and validate the programs command line + * + * Read the command line, verify the syntax and parse the options. + * This function is very long, but quite simple. + * + * Return: 1 Success + * 0 Error, one or more problems + */ +static int parse_options(int argc, char *argv[]) +{ + int err = 0, help = 0; + int c = -1; + + static const char *sopt = "-o:h?qv"; + static const struct option lopt[] = { + { "options", required_argument, NULL, 'o' }, + { "help", no_argument, NULL, 'h' }, + { "quiet", no_argument, NULL, 'q' }, + { "verbose", no_argument, NULL, 'v' }, + { NULL, 0, NULL, 0 } + }; + + opterr = 0; /* We'll handle the errors, thank you. */ + + opts.mnt_point = NULL; + opts.options = NULL; + opts.device = NULL; + + while ((c = getopt_long(argc, argv, sopt, lopt, NULL)) != -1) { + switch (c) { + case 1: /* A non-option argument */ + if (!opts.device) { + opts.device = ntfs_malloc(PATH_MAX + 1); + if (!opts.device) { + err++; + break; + } + /* We don't want relative path in /etc/mtab. */ + if (optarg[0] != '/') { + if (!realpath(optarg, opts.device)) { + ntfs_log_perror("Cannot mount " + "'%s'", optarg); + free(opts.device); + opts.device = NULL; + err++; + break; + } + } else + strcpy(opts.device, optarg); + } else if (!opts.mnt_point) + opts.mnt_point = optarg; + else { + ntfs_log_error("You must specify exactly one " + "device and exactly one mount " + "point.\n"); + err++; + } + break; + case 'o': + if (!opts.options) + opts.options = optarg; + else { + ntfs_log_error("You must specify exactly one " + "set of options.\n"); + err++; + } + break; + case 'h': + case '?': + help++; + break; + case 'q': + opts.quiet++; + break; + case 'v': + opts.verbose++; + break; + default: + ntfs_log_error("Unknown option '%s'.\n", + argv[optind - 1]); + err++; + break; + } + } + + if (help) { + opts.quiet = 0; + } else { + if (!opts.device) { + ntfs_log_error("No device specified.\n"); + err++; + } + + if (opts.quiet && opts.verbose) { + ntfs_log_error("You may not use --quiet and --verbose " + "at the same time.\n"); + err++; + } + } + + if (help || err) + usage(); + + return (!help && !err); +} + +static void load_fuse_module(int force) +{ + struct stat st; + int exist; + const char *load_fuse_cmd = "/sbin/modprobe fuse"; + + exist = !stat("/dev/fuse", &st); + + if (!force && exist) + return; + + if (!exist) { + if (errno != ENOENT) { + ntfs_log_perror("Failed to stat /dev/fuse"); + return; + } + + if (mknod("/dev/fuse", S_IFCHR | 0666, makedev(10, 229))) { + ntfs_log_perror("Failed to create /dev/fuse"); + return; + } + } + + if (stat("/sbin/modprobe", &st) == -1) + load_fuse_cmd = "modprobe fuse"; + + if (getuid() == 0) + system(load_fuse_cmd); +} + +int main(int argc, char *argv[]) +{ + char *parsed_options; + struct fuse_args margs = FUSE_ARGS_INIT(0, NULL); + struct fuse *fh; + int ffd = 0; + + utils_set_locale(); + ntfs_log_set_handler(ntfs_log_handler_stderr); + signal(SIGINT, signal_handler); + signal(SIGTERM, signal_handler); + + if (!parse_options(argc, argv)) + return 1; + + if (ntfs_fuse_init()) + return 2; + + parsed_options = parse_mount_options(opts.options ? opts.options : ""); + if (!parsed_options) { + ntfs_fuse_destroy(); + return 3; + } + + if (ntfs_fuse_mount(opts.device)) { + free(parsed_options); + ntfs_fuse_destroy(); + return 4; + } + + /* Libfuse can't always find fusermount, so let's help it. */ + if (setenv("PATH", ":/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin", 0)) + ntfs_log_perror("WARNING: Failed to set $PATH\n"); + + load_fuse_module(0); + + if ((fuse_opt_add_arg(&margs, "") == -1 || + fuse_opt_add_arg(&margs, "-o") == -1 || + fuse_opt_add_arg(&margs, parsed_options) == -1)) { + ntfs_log_error("Failed to set FUSE options.\n"); + goto mount_failed; + } + + ffd = fuse_mount(opts.mnt_point, &margs); + if (ffd == -1) { + if (errno == ENOENT || errno == ENODEV) { + ntfs_log_error("Retrying mount ...\n"); + load_fuse_module(1); + ffd = fuse_mount(opts.mnt_point, &margs); + } + if (ffd == -1) { +mount_failed: + ntfs_log_error("Failed to mount NTFS\n"); + fuse_opt_free_args(&margs); + free(parsed_options); + ntfs_fuse_destroy(); + return 5; + } else + ntfs_log_info("Successful mount\n"); + } + fuse_opt_free_args(&margs); + free(parsed_options); + fh = (struct fuse *)1; /* Cast anything except NULL to handle errors. */ + margs = (struct fuse_args)FUSE_ARGS_INIT(0, NULL); + if (fuse_opt_add_arg(&margs, "") == -1 || + fuse_opt_add_arg(&margs, "-o") == -1) + fh = NULL; + if (!ctx->debug && !ctx->no_detach) { + if (fuse_opt_add_arg(&margs, "use_ino,kernel_cache") == -1) + fh = NULL; + } else { + if (fuse_opt_add_arg(&margs, "use_ino,debug") == -1) + fh = NULL; + } + if (fh) + fh = fuse_new(ffd, &margs , &ntfs_fuse_oper, + sizeof(ntfs_fuse_oper)); + fuse_opt_free_args(&margs); + if (!fh) { + ntfs_log_error("fuse_new failed.\n"); + close(ffd); + fuse_unmount(opts.mnt_point); + ntfs_fuse_destroy(); + return 6; + } + if (!ctx->no_detach) { + if (daemon(0, ctx->debug)) + ntfs_log_error("Failed to daemonize.\n"); + else if (!ctx->debug) { +#ifndef DEBUG + ntfs_log_set_handler(ntfs_log_handler_syslog); + /* Override default libntfs identify. */ + openlog(EXEC_NAME, LOG_PID, LOG_DAEMON); +#endif + } + } + ntfs_log_info("Version %s\n", VERSION); + ntfs_log_info("Mounted %s (%s, label \"%s\", NTFS %d.%d)\n", + opts.device, (ctx->ro) ? "Read-Only" : "Read-Write", + ctx->vol->vol_name, ctx->vol->major_ver, + ctx->vol->minor_ver); + fuse_loop(fh); + + fuse_destroy(fh); + close(ffd); + fuse_unmount(opts.mnt_point); + ntfs_fuse_destroy(); + return 0; +} diff --git a/src/utils.c b/src/utils.c new file mode 100644 index 00000000..58df3d06 --- /dev/null +++ b/src/utils.c @@ -0,0 +1,966 @@ +/** + * utils.c - Originated from the Linux-NTFS project. + * + * Copyright (c) 2002-2005 Richard Russon + * Copyright (c) 2003-2006 Anton Altaparmakov + * Copyright (c) 2003 Lode Leroy + * + * A set of shared functions for ntfs utilities + * + * 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 + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_STDIO_H +#include +#endif +#ifdef HAVE_STDARG_H +#include +#endif +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_SYS_TYPES_H +#include +#endif +#ifdef HAVE_SYS_STAT_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif +#ifdef HAVE_LOCALE_H +#include +#endif +#ifdef HAVE_LIBINTL_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_LIMITS_H +#include +#endif +#ifdef HAVE_CTYPE_H +#include +#endif + +#include "utils.h" +#include "types.h" +#include "volume.h" +#include "debug.h" +#include "dir.h" +#include "version.h" +#include "logging.h" +#include "misc.h" + +const char *ntfs_home = + "Ntfs-3g news, support and information: http://www.ntfs-3g.org\n"; +const char *ntfs_gpl = "This program is free software, released under the GNU " + "General Public License\nand you are welcome to redistribute it under " + "certain conditions. It comes with\nABSOLUTELY NO WARRANTY; for " + "details read the GNU General Public License to be\nfound in the file " + "\"COPYING\" distributed with this program, or online at:\n" + "http://www.gnu.org/copyleft/gpl.html\n"; + +static const char *invalid_ntfs_msg = +"The device '%s' doesn't have a valid NTFS.\n" +"Maybe you selected the wrong device? Or the whole disk instead of a\n" +"partition (e.g. /dev/hda, not /dev/hda1)? Or the other way around?\n"; + +static const char *corrupt_volume_msg = +"NTFS is inconsistent. Run chkdsk /f on Windows then reboot it TWICE!\n" +"The usage of the /f parameter is very IMPORTANT! No modification was\n" +"made to NTFS by this software.\n"; + +static const char *hibernated_volume_msg = +"The NTFS partition is hibernated. Please resume Windows and turned it \n" +"off properly, so mounting could be done safely.\n"; + +static const char *unclean_journal_msg = +"Mount is denied because the NTFS journal file is unclean. Choices are:\n" +" A) Shutdown Windows properly.\n" +" B) Click the 'Safely Remove Hardware' icon in the Windows taskbar\n" +" notification area before disconnecting the device.\n" +" C) Use 'Eject' from Windows Explorer to safely remove the device.\n" +" D) If you ran chkdsk previously then boot Windows again which will\n" +" automatically initialize the journal.\n" +" E) Run 'ntfsfix' on Linux which will reset the NTFS journal.\n" +" F) Mount the volume read-only by using the 'ro' mount option.\n"; + +static const char *opened_volume_msg = +"Mount is denied because the NTFS volume is already exclusively opened.\n" +"The volume may be already mounted, or another software may use it which\n" +"could be identified for example by the help of the 'fuser' command.\n"; + +/** + * utils_set_locale + */ +int utils_set_locale(void) +{ + const char *locale; + + locale = setlocale(LC_ALL, ""); + if (!locale) { + locale = setlocale(LC_ALL, NULL); + ntfs_log_error("Couldn't set local environment, using default '%s'.\n", locale); + return 1; + } else { + return 0; + } +} + +ntfs_volume *utils_mount_volume(const char *volume, unsigned long flags, + BOOL force) +{ + ntfs_volume *vol; + + vol = ntfs_mount(volume, flags); + if (!vol) { + + ntfs_log_perror("Failed to mount '%s'", volume); + + if (errno == EINVAL) + ntfs_log_error(invalid_ntfs_msg, volume); + else if (errno == EIO) + ntfs_log_error("%s", corrupt_volume_msg); + else if (errno == EPERM) + ntfs_log_error("%s", hibernated_volume_msg); + else if (errno == EOPNOTSUPP) + ntfs_log_error("%s", unclean_journal_msg); + else if (errno == EBUSY) + ntfs_log_error("%s", opened_volume_msg); + + return NULL; + } + + if (vol->flags & VOLUME_IS_DIRTY) { + if (!force) { + ntfs_log_error("Volume is scheduled for check.\nPlease " + "boot into Windows TWICE, or use the " + "'force' mount option.\n"); + ntfs_umount(vol, FALSE); + + return NULL; + } else + ntfs_log_error("WARNING: Dirty volume mount was forced " + "by the 'force' mount option.\n"); + } + + return vol; +} + +/** + * utils_parse_size - Convert a string representing a size + * @value: String to be parsed + * @size: Parsed size + * @scale: Whether or not to allow a suffix to scale the value + * + * Read a string and convert it to a number. Strings may be suffixed to scale + * them. Any number without a suffix is assumed to be in bytes. + * + * Suffix Description Multiple + * [tT] Terabytes 10^12 + * [gG] Gigabytes 10^9 + * [mM] Megabytes 10^6 + * [kK] Kilobytes 10^3 + * + * Notes: + * Only the first character of the suffix is read. + * The multipliers are decimal thousands, not binary: 1000, not 1024. + * If parse_size fails, @size will not be changed + * + * Return: 1 Success + * 0 Error, the string was malformed + */ +int utils_parse_size(const char *value, s64 *size, BOOL scale) +{ + long long result; + char *suffix = NULL; + + if (!value || !size) { + errno = EINVAL; + return 0; + } + + result = strtoll(value, &suffix, 0); + if (result < 0 || errno == ERANGE) { + ntfs_log_error("Invalid size '%s'.\n", value); + return 0; + } + + if (!suffix) { + ntfs_log_error("Internal error, strtoll didn't return a suffix.\n"); + return 0; + } + + if (scale) { + switch (suffix[0]) { + case 't': case 'T': result *= 1000; + case 'g': case 'G': result *= 1000; + case 'm': case 'M': result *= 1000; + case 'k': case 'K': result *= 1000; + case '-': case 0: + break; + default: + ntfs_log_error("Invalid size suffix '%s'. Use T, G, M, or K.\n", suffix); + return 0; + } + } else { + if ((suffix[0] != '-') && (suffix[0] != 0)) { + ntfs_log_error("Invalid number '%.*s'.\n", (int)(suffix - value + 1), value); + return 0; + } + } + + *size = result; + return 1; +} + +/** + * utils_parse_range - Convert a string representing a range of numbers + * @string: The string to be parsed + * @start: The beginning of the range will be stored here + * @finish: The end of the range will be stored here + * + * Read a string of the form n-m. If the lower end is missing, zero will be + * substituted. If the upper end is missing LONG_MAX will be used. If the + * string cannot be parsed correctly, @start and @finish will not be changed. + * + * Return: 1 Success, a valid string was found + * 0 Error, the string was not a valid range + */ +int utils_parse_range(const char *string, s64 *start, s64 *finish, BOOL scale) +{ + s64 a, b; + char *middle; + + if (!string || !start || !finish) { + errno = EINVAL; + return 0; + } + + middle = strchr(string, '-'); + if (string == middle) { + ntfs_log_debug("Range has no beginning, defaulting to 0.\n"); + a = 0; + } else { + if (!utils_parse_size(string, &a, scale)) + return 0; + } + + if (middle) { + if (middle[1] == 0) { + b = LONG_MAX; // XXX ULLONG_MAX + ntfs_log_debug("Range has no end, defaulting to %lld.\n", b); + } else { + if (!utils_parse_size(middle+1, &b, scale)) + return 0; + } + } else { + b = a; + } + + ntfs_log_debug("Range '%s' = %lld - %lld\n", string, a, b); + + *start = a; + *finish = b; + return 1; +} + +/** + * find_attribute - Find an attribute of the given type + * @type: An attribute type, e.g. AT_FILE_NAME + * @ctx: A search context, created using ntfs_get_attr_search_ctx + * + * Using the search context to keep track, find the first/next occurrence of a + * given attribute type. + * + * N.B. This will return a pointer into @mft. As long as the search context + * has been created without an inode, it won't overflow the buffer. + * + * Return: Pointer Success, an attribute was found + * NULL Error, no matching attributes were found + */ +ATTR_RECORD * find_attribute(const ATTR_TYPES type, ntfs_attr_search_ctx *ctx) +{ + if (!ctx) { + errno = EINVAL; + return NULL; + } + + if (ntfs_attr_lookup(type, NULL, 0, 0, 0, NULL, 0, ctx) != 0) { + ntfs_log_debug("find_attribute didn't find an attribute of type: 0x%02x.\n", type); + return NULL; /* None / no more of that type */ + } + + ntfs_log_debug("find_attribute found an attribute of type: 0x%02x.\n", type); + return ctx->attr; +} + +/** + * find_first_attribute - Find the first attribute of a given type + * @type: An attribute type, e.g. AT_FILE_NAME + * @mft: A buffer containing a raw MFT record + * + * Search through a raw MFT record for an attribute of a given type. + * The return value is a pointer into the MFT record that was supplied. + * + * N.B. This will return a pointer into @mft. The pointer won't stray outside + * the buffer, since we created the search context without an inode. + * + * Return: Pointer Success, an attribute was found + * NULL Error, no matching attributes were found + */ +ATTR_RECORD * find_first_attribute(const ATTR_TYPES type, MFT_RECORD *mft) +{ + ntfs_attr_search_ctx *ctx; + ATTR_RECORD *rec; + + if (!mft) { + errno = EINVAL; + return NULL; + } + + ctx = ntfs_attr_get_search_ctx(NULL, mft); + if (!ctx) { + ntfs_log_error("Couldn't create a search context.\n"); + return NULL; + } + + rec = find_attribute(type, ctx); + ntfs_attr_put_search_ctx(ctx); + if (rec) + ntfs_log_debug("find_first_attribute: found attr of type 0x%02x.\n", type); + else + ntfs_log_debug("find_first_attribute: didn't find attr of type 0x%02x.\n", type); + return rec; +} + +/** + * utils_inode_get_name + * + * using inode + * get filename + * add name to list + * get parent + * if parent is 5 (/) stop + * get inode of parent + */ +int utils_inode_get_name(ntfs_inode *inode, char *buffer, int bufsize) +{ + // XXX option: names = posix/win32 or dos + // flags: path, filename, or both + const int max_path = 20; + + ntfs_volume *vol; + ntfs_attr_search_ctx *ctx; + ATTR_RECORD *rec; + FILE_NAME_ATTR *attr; + int name_space; + MFT_REF parent = FILE_root; + char *names[max_path + 1];// XXX ntfs_malloc? and make max bigger? + int i, len, offset = 0; + + if (!inode || !buffer) { + errno = EINVAL; + return 0; + } + + vol = inode->vol; + + //ntfs_log_debug("sizeof(char*) = %d, sizeof(names) = %d\n", sizeof(char*), sizeof(names)); + memset(names, 0, sizeof(names)); + + for (i = 0; i < max_path; i++) { + + ctx = ntfs_attr_get_search_ctx(inode, NULL); + if (!ctx) { + ntfs_log_error("Couldn't create a search context.\n"); + return 0; + } + + //ntfs_log_debug("i = %d, inode = %p (%lld)\n", i, inode, inode->mft_no); + + name_space = 4; + while ((rec = find_attribute(AT_FILE_NAME, ctx))) { + /* We know this will always be resident. */ + attr = (FILE_NAME_ATTR *) ((char *) rec + le16_to_cpu(rec->value_offset)); + + if (attr->file_name_type > name_space) { //XXX find the ... + continue; + } + + name_space = attr->file_name_type; + parent = le64_to_cpu(attr->parent_directory); + + if (names[i]) { + free(names[i]); + names[i] = NULL; + } + + if (ntfs_ucstombs(attr->file_name, attr->file_name_length, + &names[i], 0) < 0) { + char *temp; + ntfs_log_error("Couldn't translate filename to current locale.\n"); + temp = ntfs_malloc(30); + if (!temp) + return 0; + snprintf(temp, 30, "", (unsigned + long long)inode->mft_no); + names[i] = temp; + } + + //ntfs_log_debug("names[%d] %s\n", i, names[i]); + //ntfs_log_debug("parent = %lld\n", MREF(parent)); + } + + ntfs_attr_put_search_ctx(ctx); + + if (i > 0) /* Don't close the original inode */ + ntfs_inode_close(inode); + + if (MREF(parent) == FILE_root) { /* The root directory, stop. */ + //ntfs_log_debug("inode 5\n"); + break; + } + + inode = ntfs_inode_open(vol, parent); + if (!inode) { + ntfs_log_error("Couldn't open inode %llu.\n", + (unsigned long long)MREF(parent)); + break; + } + } + + if (i >= max_path) { + /* If we get into an infinite loop, we'll end up here. */ + ntfs_log_error("The directory structure is too deep (over %d) nested directories.\n", max_path); + return 0; + } + + /* Assemble the names in the correct order. */ + for (i = max_path; i >= 0; i--) { + if (!names[i]) + continue; + + len = snprintf(buffer + offset, bufsize - offset, "%c%s", PATH_SEP, names[i]); + if (len >= (bufsize - offset)) { + ntfs_log_error("Pathname was truncated.\n"); + break; + } + + offset += len; + } + + /* Free all the allocated memory */ + for (i = 0; i < max_path; i++) + free(names[i]); + + ntfs_log_debug("Pathname: %s\n", buffer); + + return 1; +} + +/** + * utils_attr_get_name + */ +int utils_attr_get_name(ntfs_volume *vol, ATTR_RECORD *attr, char *buffer, int bufsize) +{ + int len, namelen; + char *name; + ATTR_DEF *attrdef; + + // flags: attr, name, or both + if (!attr || !buffer) { + errno = EINVAL; + return 0; + } + + attrdef = ntfs_attr_find_in_attrdef(vol, attr->type); + if (attrdef) { + name = NULL; + namelen = ntfs_ucsnlen(attrdef->name, sizeof(attrdef->name)); + if (ntfs_ucstombs(attrdef->name, namelen, &name, 0) < 0) { + ntfs_log_error("Couldn't translate attribute type to current locale.\n"); + // ? + return 0; + } + len = snprintf(buffer, bufsize, "%s", name); + } else { + ntfs_log_error("Unknown attribute type 0x%02x\n", attr->type); + len = snprintf(buffer, bufsize, ""); + } + + if (len >= bufsize) { + ntfs_log_error("Attribute type was truncated.\n"); + return 0; + } + + if (!attr->name_length) { + return 0; + } + + buffer += len; + bufsize -= len; + + name = NULL; + namelen = attr->name_length; + if (ntfs_ucstombs((ntfschar *)((char *)attr + attr->name_offset), + namelen, &name, 0) < 0) { + ntfs_log_error("Couldn't translate attribute name to current locale.\n"); + // ? + len = snprintf(buffer, bufsize, ""); + return 0; + } + + len = snprintf(buffer, bufsize, "(%s)", name); + free(name); + + if (len >= bufsize) { + ntfs_log_error("Attribute name was truncated.\n"); + return 0; + } + + return 0; +} + +/** + * utils_cluster_in_use - Determine if a cluster is in use + * @vol: An ntfs volume obtained from ntfs_mount + * @lcn: The Logical Cluster Number to test + * + * The metadata file $Bitmap has one binary bit representing each cluster on + * disk. The bit will be set for each cluster that is in use. The function + * reads the relevant part of $Bitmap into a buffer and tests the bit. + * + * This function has a static buffer in which it caches a section of $Bitmap. + * If the lcn, being tested, lies outside the range, the buffer will be + * refreshed. + * + * Return: 1 Cluster is in use + * 0 Cluster is free space + * -1 Error occurred + */ +int utils_cluster_in_use(ntfs_volume *vol, long long lcn) +{ + static unsigned char buffer[512]; + static long long bmplcn = -sizeof(buffer) - 1; /* Which bit of $Bitmap is in the buffer */ + + int byte, bit; + ntfs_attr *attr; + + if (!vol) { + errno = EINVAL; + return -1; + } + + /* Does lcn lie in the section of $Bitmap we already have cached? */ + if ((lcn < bmplcn) || (lcn >= (bmplcn + (sizeof(buffer) << 3)))) { + ntfs_log_debug("Bit lies outside cache.\n"); + attr = ntfs_attr_open(vol->lcnbmp_ni, AT_DATA, AT_UNNAMED, 0); + if (!attr) { + ntfs_log_perror("Couldn't open $Bitmap"); + return -1; + } + + /* Mark the buffer as in use, in case the read is shorter. */ + memset(buffer, 0xFF, sizeof(buffer)); + bmplcn = lcn & (~((sizeof(buffer) << 3) - 1)); + + if (ntfs_attr_pread(attr, (bmplcn>>3), sizeof(buffer), buffer) < 0) { + ntfs_log_perror("Couldn't read $Bitmap"); + ntfs_attr_close(attr); + return -1; + } + + ntfs_log_debug("Reloaded bitmap buffer.\n"); + ntfs_attr_close(attr); + } + + bit = 1 << (lcn & 7); + byte = (lcn >> 3) & (sizeof(buffer) - 1); + ntfs_log_debug("cluster = %lld, bmplcn = %lld, byte = %d, bit = %d, in use %d\n", lcn, bmplcn, byte, bit, buffer[byte] & bit); + + return (buffer[byte] & bit); +} + +/** + * utils_mftrec_in_use - Determine if a MFT Record is in use + * @vol: An ntfs volume obtained from ntfs_mount + * @mref: MFT Reference (inode number) + * + * The metadata file $BITMAP has one binary bit representing each record in the + * MFT. The bit will be set for each record that is in use. The function + * reads the relevant part of $BITMAP into a buffer and tests the bit. + * + * This function has a static buffer in which it caches a section of $BITMAP. + * If the mref, being tested, lies outside the range, the buffer will be + * refreshed. + * + * Return: 1 MFT Record is in use + * 0 MFT Record is unused + * -1 Error occurred + */ +int utils_mftrec_in_use(ntfs_volume *vol, MFT_REF mref) +{ + static u8 buffer[512]; + static s64 bmpmref = -sizeof(buffer) - 1; /* Which bit of $BITMAP is in the buffer */ + + int byte, bit; + + if (!vol) { + errno = EINVAL; + return -1; + } + + ntfs_log_trace("entering\n"); + /* Does mref lie in the section of $Bitmap we already have cached? */ + if (((s64)MREF(mref) < bmpmref) || ((s64)MREF(mref) >= (bmpmref + + (sizeof(buffer) << 3)))) { + ntfs_log_debug("Bit lies outside cache.\n"); + + /* Mark the buffer as not in use, in case the read is shorter. */ + memset(buffer, 0, sizeof(buffer)); + bmpmref = mref & (~((sizeof(buffer) << 3) - 1)); + + if (ntfs_attr_pread(vol->mftbmp_na, (bmpmref>>3), sizeof(buffer), buffer) < 0) { + ntfs_log_perror("Couldn't read $MFT/$BITMAP"); + return -1; + } + + ntfs_log_debug("Reloaded bitmap buffer.\n"); + } + + bit = 1 << (mref & 7); + byte = (mref >> 3) & (sizeof(buffer) - 1); + ntfs_log_debug("cluster = %lld, bmpmref = %lld, byte = %d, bit = %d, in use %d\n", mref, bmpmref, byte, bit, buffer[byte] & bit); + + return (buffer[byte] & bit); +} + +/** + * __metadata + */ +static int __metadata(ntfs_volume *vol, u64 num) +{ + if (num <= FILE_UpCase) + return 1; + if (!vol) + return -1; + if ((vol->major_ver == 3) && (num == FILE_Extend)) + return 1; + + return 0; +} + +/** + * utils_is_metadata - Determine if an inode represents a metadata file + * @inode: An ntfs inode to be tested + * + * A handful of files in the volume contain filesystem data - metadata. + * They can be identified by their inode number (offset in MFT/$DATA) or by + * their parent. + * + * Return: 1 inode is a metadata file + * 0 inode is not a metadata file + * -1 Error occurred + */ +int utils_is_metadata(ntfs_inode *inode) +{ + ntfs_volume *vol; + ATTR_RECORD *rec; + FILE_NAME_ATTR *attr; + MFT_RECORD *file; + u64 num; + + if (!inode) { + errno = EINVAL; + return -1; + } + + vol = inode->vol; + if (!vol) + return -1; + + num = inode->mft_no; + if (__metadata(vol, num) == 1) + return 1; + + file = inode->mrec; + if (file && (file->base_mft_record != 0)) { + num = MREF(file->base_mft_record); + if (__metadata(vol, num) == 1) + return 1; + } + file = inode->mrec; + + rec = find_first_attribute(AT_FILE_NAME, inode->mrec); + if (!rec) + return -1; + + /* We know this will always be resident. */ + attr = (FILE_NAME_ATTR *) ((char *) rec + le16_to_cpu(rec->value_offset)); + + num = MREF(attr->parent_directory); + if ((num != FILE_root) && (__metadata(vol, num) == 1)) + return 1; + + return 0; +} + +/** + * utils_dump_mem - Display a block of memory in hex and ascii + * @buf: Buffer to be displayed + * @start: Offset into @buf to start from + * @length: Number of bytes to display + * @flags: Options to change the style of the output + * + * Display a block of memory in a tradition hex-dump manner. + * Optionally the ascii part can be turned off. + * + * The flags, described fully in utils.h, default to 0 (DM_DEFAULTS). + * Examples are: DM_INDENT (indent the output by one tab); DM_RED (colour the + * output); DM_NO_ASCII (only print the hex values). + */ +void utils_dump_mem(void *buf, int start, int length, int flags) +{ + int off, i, s, e, col; + u8 *mem = buf; + + s = start & ~15; // round down + e = (start + length + 15) & ~15; // round up + + for (off = s; off < e; off += 16) { + col = 30; + if (flags & DM_RED) + col += 1; + if (flags & DM_GREEN) + col += 2; + if (flags & DM_BLUE) + col += 4; + if (flags & DM_INDENT) + ntfs_log_debug("\t"); + if (flags & DM_BOLD) + ntfs_log_debug("\e[01m"); + if (flags & (DM_RED | DM_BLUE | DM_GREEN | DM_BOLD)) + ntfs_log_debug("\e[%dm", col); + if (off == s) + ntfs_log_debug("%6.6x ", start); + else + ntfs_log_debug("%6.6x ", off); + + for (i = 0; i < 16; i++) { + if ((i == 8) && (!(flags & DM_NO_DIVIDER))) + ntfs_log_debug(" -"); + if (((off+i) >= start) && ((off+i) < (start+length))) + ntfs_log_debug(" %02X", mem[off+i]); + else + ntfs_log_debug(" "); + } + if (!(flags & DM_NO_ASCII)) { + ntfs_log_debug(" "); + for (i = 0; i < 16; i++) { + if (((off+i) < start) || ((off+i) >= (start+length))) + ntfs_log_debug(" "); + else if (isprint(mem[off + i])) + ntfs_log_debug("%c", mem[off + i]); + else + ntfs_log_debug("."); + } + } + if (flags & (DM_RED | DM_BLUE | DM_GREEN | DM_BOLD)) + ntfs_log_debug("\e[0m"); + ntfs_log_debug("\n"); + } +} + + +/** + * mft_get_search_ctx + */ +struct mft_search_ctx * mft_get_search_ctx(ntfs_volume *vol) +{ + struct mft_search_ctx *ctx; + + if (!vol) { + errno = EINVAL; + return NULL; + } + + ctx = calloc(1, sizeof *ctx); + + ctx->mft_num = -1; + ctx->vol = vol; + + return ctx; +} + +/** + * mft_put_search_ctx + */ +void mft_put_search_ctx(struct mft_search_ctx *ctx) +{ + if (!ctx) + return; + if (ctx->inode) + ntfs_inode_close(ctx->inode); + free(ctx); +} + +/** + * mft_next_record + */ +int mft_next_record(struct mft_search_ctx *ctx) +{ + s64 nr_mft_records; + ATTR_RECORD *attr10 = NULL; + ATTR_RECORD *attr20 = NULL; + ATTR_RECORD *attr80 = NULL; + ntfs_attr_search_ctx *attr_ctx; + + if (!ctx) { + errno = EINVAL; + return -1; + } + + if (ctx->inode) { + ntfs_inode_close(ctx->inode); + ctx->inode = NULL; + } + + nr_mft_records = ctx->vol->mft_na->initialized_size >> + ctx->vol->mft_record_size_bits; + + for (ctx->mft_num++; (s64)ctx->mft_num < nr_mft_records; ctx->mft_num++) { + int in_use; + + ctx->flags_match = 0; + in_use = utils_mftrec_in_use(ctx->vol, (MFT_REF) ctx->mft_num); + if (in_use == -1) { + ntfs_log_error("Error reading inode %llu. Aborting.\n", + (unsigned long long)ctx->mft_num); + return -1; + } + + if (in_use) { + ctx->flags_match |= FEMR_IN_USE; + + ctx->inode = ntfs_inode_open(ctx->vol, (MFT_REF) ctx->mft_num); + if (ctx->inode == NULL) { + ntfs_log_error("Error reading inode %llu.\n", (unsigned + long long) ctx->mft_num); + continue; + } + + attr10 = find_first_attribute(AT_STANDARD_INFORMATION, ctx->inode->mrec); + attr20 = find_first_attribute(AT_ATTRIBUTE_LIST, ctx->inode->mrec); + attr80 = find_first_attribute(AT_DATA, ctx->inode->mrec); + + if (attr10) + ctx->flags_match |= FEMR_BASE_RECORD; + else + ctx->flags_match |= FEMR_NOT_BASE_RECORD; + + if (attr20) + ctx->flags_match |= FEMR_BASE_RECORD; + + if (attr80) + ctx->flags_match |= FEMR_FILE; + + if (ctx->flags_search & FEMR_DIR) { + attr_ctx = ntfs_attr_get_search_ctx(ctx->inode, NULL); + if (attr_ctx) { + if (ntfs_attr_lookup(AT_INDEX_ROOT, NTFS_INDEX_I30, 4, 0, 0, NULL, 0, attr_ctx) == 0) + ctx->flags_match |= FEMR_DIR; + + ntfs_attr_put_search_ctx(attr_ctx); + } else { + ntfs_log_error("Couldn't create a search context.\n"); + return -1; + } + } + + switch (utils_is_metadata(ctx->inode)) { + case 1: ctx->flags_match |= FEMR_METADATA; break; + case 0: ctx->flags_match |= FEMR_NOT_METADATA; break; + default: + ctx->flags_match |= FEMR_NOT_METADATA; break; + //ntfs_log_error("Error reading inode %lld.\n", ctx->mft_num); + //return -1; + } + + } else { // !in_use + ntfs_attr *mft; + + ctx->flags_match |= FEMR_NOT_IN_USE; + + ctx->inode = calloc(1, sizeof(*ctx->inode)); + if (!ctx->inode) { + ntfs_log_error("Out of memory. Aborting.\n"); + return -1; + } + + ctx->inode->mft_no = ctx->mft_num; + ctx->inode->vol = ctx->vol; + ctx->inode->mrec = ntfs_malloc(ctx->vol->mft_record_size); + if (!ctx->inode->mrec) { + free(ctx->inode); // == ntfs_inode_close + return -1; + } + + mft = ntfs_attr_open(ctx->vol->mft_ni, AT_DATA, + AT_UNNAMED, 0); + if (!mft) { + ntfs_log_perror("Couldn't open $MFT/$DATA"); + // free / close + return -1; + } + + if (ntfs_attr_pread(mft, ctx->vol->mft_record_size * ctx->mft_num, ctx->vol->mft_record_size, ctx->inode->mrec) < ctx->vol->mft_record_size) { + ntfs_log_perror("Couldn't read MFT Record %llu", + (unsigned long long) ctx->mft_num); + // free / close + ntfs_attr_close(mft); + return -1; + } + + ntfs_attr_close(mft); + } + + if (ctx->flags_match & ctx->flags_search) { + break; + } + + if (ntfs_inode_close(ctx->inode)) { + ntfs_log_error("Error closing inode %llu.\n", + (unsigned long long)ctx->mft_num); + return -errno; + } + + ctx->inode = NULL; + } + + return (ctx->inode == NULL); +} + + diff --git a/src/utils.h b/src/utils.h new file mode 100644 index 00000000..527dd435 --- /dev/null +++ b/src/utils.h @@ -0,0 +1,99 @@ +/* + * utils.h - Originated from the Linux-NTFS project. + * + * Copyright (c) 2002-2005 Richard Russon + * Copyright (c) 2004 Anton Altaparmakov + * + * A set of shared functions for ntfs utilities + * + * 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 + */ + +#ifndef _NTFS_UTILS_H_ +#define _NTFS_UTILS_H_ + +#include "config.h" + +#include "types.h" +#include "layout.h" +#include "volume.h" + +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_STDARG_H +#include +#endif + +extern const char *ntfs_home; +extern const char *ntfs_gpl; + +int utils_set_locale(void); +int utils_parse_size(const char *value, s64 *size, BOOL scale); +int utils_parse_range(const char *string, s64 *start, s64 *finish, BOOL scale); +int utils_inode_get_name(ntfs_inode *inode, char *buffer, int bufsize); +int utils_attr_get_name(ntfs_volume *vol, ATTR_RECORD *attr, char *buffer, int bufsize); +int utils_cluster_in_use(ntfs_volume *vol, long long lcn); +int utils_mftrec_in_use(ntfs_volume *vol, MFT_REF mref); +int utils_is_metadata(ntfs_inode *inode); +void utils_dump_mem(void *buf, int start, int length, int flags); + +ATTR_RECORD * find_attribute(const ATTR_TYPES type, ntfs_attr_search_ctx *ctx); +ATTR_RECORD * find_first_attribute(const ATTR_TYPES type, MFT_RECORD *mft); + +int utils_valid_device(const char *name, int force); +ntfs_volume * utils_mount_volume(const char *device, unsigned long flags, BOOL force); + +/** + * defines... + * if *not in use* then the other flags are ignored? + */ +#define FEMR_IN_USE (1 << 0) +#define FEMR_NOT_IN_USE (1 << 1) +#define FEMR_FILE (1 << 2) // $DATA +#define FEMR_DIR (1 << 3) // $INDEX_ROOT, "$I30" +#define FEMR_METADATA (1 << 4) +#define FEMR_NOT_METADATA (1 << 5) +#define FEMR_BASE_RECORD (1 << 6) +#define FEMR_NOT_BASE_RECORD (1 << 7) +#define FEMR_ALL_RECORDS 0xFF + +/** + * struct mft_search_ctx + */ +struct mft_search_ctx { + int flags_search; + int flags_match; + ntfs_inode *inode; + ntfs_volume *vol; + u64 mft_num; +}; + +struct mft_search_ctx * mft_get_search_ctx(ntfs_volume *vol); +void mft_put_search_ctx(struct mft_search_ctx *ctx); +int mft_next_record(struct mft_search_ctx *ctx); + +// Flags for dump mem +#define DM_DEFAULTS 0 +#define DM_NO_ASCII (1 << 0) +#define DM_NO_DIVIDER (1 << 1) +#define DM_INDENT (1 << 2) +#define DM_RED (1 << 3) +#define DM_GREEN (1 << 4) +#define DM_BLUE (1 << 5) +#define DM_BOLD (1 << 6) + +#endif /* _NTFS_UTILS_H_ */