148 files changed, 141286 insertions, 0 deletions
diff --git a/libntfs-3g/attrib.c b/libntfs-3g/attrib.c new file mode 100755 index 0000000..12ea369 --- a/dev/null +++ b/libntfs-3g/attrib.c @@ -0,0 +1,5913 @@ +/** + * attrib.c - Attribute handling code. Originated from the Linux-NTFS project. + * + * Copyright (c) 2000-2010 Anton Altaparmakov + * Copyright (c) 2002-2005 Richard Russon + * Copyright (c) 2002-2008 Szabolcs Szakacsits + * Copyright (c) 2004-2007 Yura Pakhuchiy + * Copyright (c) 2007-2010 Jean-Pierre Andre + * Copyright (c) 2010 Erik Larsson + * + * 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 <stdio.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#ifdef HAVE_LIMITS_H +#include <limits.h> +#endif + +#include "param.h" +#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" +#include "efs.h" + +#define STANDARD_COMPRESSION_UNIT 4 +#define MAX_COMPRESSION_CLUSTER_SIZE 4096 + +ntfschar AT_UNNAMED[] = { const_cpu_to_le16('\0') }; +ntfschar STREAM_SDS[] = { const_cpu_to_le16('$'), + const_cpu_to_le16('S'), + const_cpu_to_le16('D'), + const_cpu_to_le16('S'), + const_cpu_to_le16('\0') }; + +ntfschar TXF_DATA[] = { const_cpu_to_le16('$'), + const_cpu_to_le16('T'), + const_cpu_to_le16('X'), + const_cpu_to_le16('F'), + const_cpu_to_le16('_'), + const_cpu_to_le16('D'), + const_cpu_to_le16('A'), + const_cpu_to_le16('T'), + const_cpu_to_le16('A'), + const_cpu_to_le16('\0') }; + +static int NAttrFlag(ntfs_attr *na, FILE_ATTR_FLAGS flag) +{ + if (na->type == AT_DATA && na->name == AT_UNNAMED) + return (na->ni->flags & flag); + return 0; +} + +static void NAttrSetFlag(ntfs_attr *na, FILE_ATTR_FLAGS flag) +{ + if (na->type == AT_DATA && na->name == AT_UNNAMED) + na->ni->flags |= flag; + else + ntfs_log_trace("Denied setting flag %d for not unnamed data " + "attribute\n", flag); +} + +static void NAttrClearFlag(ntfs_attr *na, FILE_ATTR_FLAGS flag) +{ + if (na->type == AT_DATA && na->name == AT_UNNAMED) + na->ni->flags &= ~flag; +} + +#define GenNAttrIno(func_name, flag) \ +int NAttr##func_name(ntfs_attr *na) { return NAttrFlag (na, flag); } \ +void NAttrSet##func_name(ntfs_attr *na) { NAttrSetFlag (na, flag); } \ +void NAttrClear##func_name(ntfs_attr *na){ NAttrClearFlag(na, flag); } + +GenNAttrIno(Compressed, FILE_ATTR_COMPRESSED) +GenNAttrIno(Encrypted, FILE_ATTR_ENCRYPTED) +GenNAttrIno(Sparse, FILE_ATTR_SPARSE_FILE) + +/** + * 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 ATTR_FLAGS data_flags, + 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)) { + na->data_flags = data_flags; + if (non_resident) + NAttrSetNonResident(na); + if (data_flags & ATTR_COMPRESSION_MASK) + 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 ((data_flags & ATTR_COMPRESSION_MASK) || 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 = NULL; + ntfschar *newname = NULL; + ATTR_RECORD *a; + BOOL cs; + + ntfs_log_enter("Entering for inode %lld, attr 0x%x.\n", + (unsigned long long)ni->mft_no, type); + + if (!ni || !ni->vol || !ni->mrec) { + errno = EINVAL; + goto out; + } + na = ntfs_calloc(sizeof(ntfs_attr)); + if (!na) + goto out; + if (name && name != AT_UNNAMED && name != NTFS_INDEX_I30) { + name = ntfs_ucsndup(name, name_len); + if (!name) + goto err_out; + newname = name; + } + + ctx = ntfs_attr_get_search_ctx(ni, NULL); + if (!ctx) + goto err_out; + + if (ntfs_attr_lookup(type, name, name_len, 0, 0, NULL, 0, ctx)) + goto put_err_out; + + a = ctx->attr; + + if (!name) { + if (a->name_length) { + name = ntfs_ucsndup((ntfschar*)((u8*)a + le16_to_cpu( + a->name_offset)), a->name_length); + if (!name) + goto put_err_out; + newname = name; + name_len = a->name_length; + } else { + name = AT_UNNAMED; + name_len = 0; + } + } + + __ntfs_attr_init(na, ni, type, name, name_len); + + /* + * 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; + + if ((type == AT_DATA) && !a->initialized_size) { + /* + * Define/redefine the compression state if stream is + * empty, based on the compression mark on parent + * directory (for unnamed data streams) or on current + * inode (for named data streams). The compression mark + * may change any time, the compression state can only + * change when stream is wiped out. + */ + a->flags &= ~ATTR_COMPRESSION_MASK; + if ((na->ni->flags & FILE_ATTR_COMPRESSED) + && (ni->vol->cluster_size <= MAX_COMPRESSION_CLUSTER_SIZE)) + a->flags |= ATTR_IS_COMPRESSED; + } + + cs = a->flags & (ATTR_IS_COMPRESSED | ATTR_IS_SPARSE); + + if (na->type == AT_DATA && na->name == AT_UNNAMED && + ((!(a->flags & ATTR_IS_SPARSE) != !NAttrSparse(na)) || + (!(a->flags & ATTR_IS_ENCRYPTED) != !NAttrEncrypted(na)))) { + errno = EIO; + ntfs_log_perror("Inode %lld has corrupt attribute flags " + "(0x%x <> 0x%x)",(unsigned long long)ni->mft_no, + a->flags, na->ni->flags); + goto put_err_out; + } + + if (a->non_resident) { + if ((a->flags & ATTR_COMPRESSION_MASK) + && !a->compression_unit) { + errno = EIO; + ntfs_log_perror("Compressed inode %lld attr 0x%x has " + "no compression unit", + (unsigned long long)ni->mft_no, type); + goto put_err_out; + } + ntfs_attr_init(na, TRUE, a->flags, + 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, + 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); +out: + ntfs_log_leave("\n"); + return na; + +put_err_out: + ntfs_attr_put_search_ctx(ctx); +err_out: + free(newname); + free(na); + na = NULL; + goto out; +} + +/** + * 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 + && na->name != STREAM_SDS) + 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 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 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 ret = -1; + + ntfs_log_enter("Entering for inode %llu, attr 0x%x.\n", + (unsigned long long)na->ni->mft_no, na->type); + + ctx = ntfs_attr_get_search_ctx(na->ni, NULL); + if (!ctx) + goto out; + + /* 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("First extent of inode %llu " + "attribute has non-zero lowest_vcn", + (unsigned long long)na->ni->mft_no); + 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 %llu has corrupt attribute list", + (unsigned long long)na->ni->mft_no); + 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("Failed to load full runlist: inode: %llu " + "highest_vcn: 0x%llx last_vcn: 0x%llx", + (unsigned long long)na->ni->mft_no, + (long long)highest_vcn, (long long)last_vcn); + goto err_out; + } + if (errno == ENOENT) + ret = 0; +err_out: + ntfs_attr_put_search_ctx(ctx); +out: + ntfs_log_leave("\n"); + return ret; +} + +/** + * 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_i - see description at ntfs_attr_pread() + */ +static s64 ntfs_attr_pread_i(ntfs_attr *na, const s64 pos, s64 count, void *b) +{ + s64 br, to_read, ofs, total, total2, max_read, max_init; + ntfs_volume *vol; + runlist_element *rl; + u16 efs_padding_length; + + /* Sanity checking arguments is done in ntfs_attr_pread(). */ + + if ((na->data_flags & ATTR_COMPRESSION_MASK) && NAttrNonResident(na)) { + if ((na->data_flags & ATTR_COMPRESSION_MASK) + == ATTR_IS_COMPRESSED) + return ntfs_compressed_attr_pread(na, pos, count, b); + else { + /* compression mode not supported */ + errno = EOPNOTSUPP; + return -1; + } + } + /* + * Encrypted non-resident attributes are not supported. We return + * access denied, which is what Windows NT4 does, too. + * However, allow if mounted with efs_raw option + */ + vol = na->ni->vol; + if (!vol->efs_raw && NAttrEncrypted(na) && NAttrNonResident(na)) { + errno = EACCES; + return -1; + } + + if (!count) + return 0; + /* + * Truncate reads beyond end of attribute, + * but round to next 512 byte boundary for encrypted + * attributes with efs_raw mount option + */ + max_read = na->data_size; + max_init = na->initialized_size; + if (na->ni->vol->efs_raw + && (na->data_flags & ATTR_IS_ENCRYPTED) + && NAttrNonResident(na)) { + if (na->data_size != na->initialized_size) { + ntfs_log_error("uninitialized encrypted file not supported\n"); + errno = EINVAL; + return -1; + } + max_init = max_read = ((na->data_size + 511) & ~511) + 2; + } + if (pos + count > max_read) { + if (pos >= max_read) + return 0; + count = max_read - 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; + ntfs_log_perror("%s: Sanity check failed", __FUNCTION__); + 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 > max_init) { + if (pos >= max_init) { + memset(b, 0, count); + return count; + } + total2 = pos + count - max_init; + count -= total2; + memset((u8*)b + count, 0, total2); + } + /* + * for encrypted non-resident attributes with efs_raw set + * the last two bytes aren't read from disk but contain + * the number of padding bytes so original size can be + * restored + */ + if (na->ni->vol->efs_raw && + (na->data_flags & ATTR_IS_ENCRYPTED) && + ((pos + count) > max_init-2)) { + efs_padding_length = 511 - ((na->data_size - 1) & 511); + if (pos+count == max_init) { + if (count == 1) { + *((u8*)b+count-1) = (u8)(efs_padding_length >> 8); + count--; + total2++; + } else { + *(u16*)((u8*)b+count-2) = cpu_to_le16(efs_padding_length); + count -= 2; + total2 +=2; + } + } else { + *((u8*)b+count-1) = (u8)(efs_padding_length & 0xff); + count--; + 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; + ntfs_log_perror("%s: Failed to find VCN #1", __FUNCTION__); + } + 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; + ntfs_log_perror("%s: Failed to find VCN #2", + __FUNCTION__); + } + goto rl_err_out; + } + /* Needed for case when runs merged. */ + ofs = pos + total - (rl->vcn << vol->cluster_size_bits); + } + if (!rl->length) { + errno = EIO; + ntfs_log_perror("%s: Zero run length", __FUNCTION__); + goto rl_err_out; + } + if (rl->lcn < (LCN)0) { + if (rl->lcn != (LCN)LCN_HOLE) { + ntfs_log_perror("%s: Bad run (%lld)", + __FUNCTION__, + (long long)rl->lcn); + 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 %lld bytes from vcn %lld, lcn %lld, ofs" + " %lld.\n", (long long)to_read, (long long)rl->vcn, + (long long )rl->lcn, (long long)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; + } + if (br == to_read) + continue; + /* If the syscall was interrupted, try again. */ + if (br == (s64)-1 && errno == EINTR) + goto retry; + if (total) + return total; + if (!br) + errno = EIO; + ntfs_log_perror("%s: ntfs_pread failed", __FUNCTION__); + return -1; + } + /* Finally, return the number of bytes read. */ + return total + total2; +rl_err_out: + if (total) + return total; + errno = EIO; + return -1; +} + +/** + * 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 ret; + + if (!na || !na->ni || !na->ni->vol || !b || pos < 0 || count < 0) { + errno = EINVAL; + ntfs_log_perror("%s: na=%p b=%p pos=%lld count=%lld", + __FUNCTION__, na, b, (long long)pos, + (long long)count); + return -1; + } + + ntfs_log_enter("Entering for inode %lld attr 0x%x pos %lld count " + "%lld\n", (unsigned long long)na->ni->mft_no, + na->type, (long long)pos, (long long)count); + + ret = ntfs_attr_pread_i(na, pos, count, b); + + ntfs_log_leave("\n"); + return ret; +} + +static int ntfs_attr_fill_zero(ntfs_attr *na, s64 pos, s64 count) +{ + char *buf; + s64 written, size, end = pos + count; + s64 ofsi; + const runlist_element *rli; + ntfs_volume *vol; + int ret = -1; + + ntfs_log_trace("pos %lld, count %lld\n", (long long)pos, + (long long)count); + + if (!na || pos < 0 || count < 0) { + errno = EINVAL; + goto err_out; + } + + buf = ntfs_calloc(NTFS_BUF_SIZE); + if (!buf) + goto err_out; + + rli = na->rl; + ofsi = 0; + vol = na->ni->vol; + while (pos < end) { + while (rli->length && (ofsi + (rli->length << + vol->cluster_size_bits) <= pos)) { + ofsi += (rli->length << vol->cluster_size_bits); + rli++; + } + size = min(end - pos, NTFS_BUF_SIZE); + written = ntfs_rl_pwrite(vol, rli, ofsi, pos, size, buf); + if (written <= 0) { + ntfs_log_perror("Failed to zero space"); + goto err_free; + } + pos += written; + } + + ret = 0; +err_free: + free(buf); +err_out: + return ret; +} + +static int ntfs_attr_fill_hole(ntfs_attr *na, s64 count, s64 *ofs, + runlist_element **rl, VCN *update_from) +{ + s64 to_write; + s64 need; + 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); + + cur_vcn = (*rl)->vcn; + from_vcn = (*rl)->vcn + (*ofs >> vol->cluster_size_bits); + + ntfs_log_trace("count: %lld, cur_vcn: %lld, from: %lld, to: %lld, ofs: " + "%lld\n", (long long)count, (long long)cur_vcn, + (long long)from_vcn, (long long)to_write, (long long)*ofs); + + /* 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) { + /* + * avoid fragmenting a compressed file + * Windows does not do that, and that may + * not be desirable for files which can + * be updated + */ + if (na->data_flags & ATTR_COMPRESSION_MASK) + lcn_seek_from = rlc->lcn + rlc->length; + else + 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); + if (lcn_seek_from < -1) + lcn_seek_from = -1; + break; + } + } + } + + need = ((*ofs + to_write - 1) >> vol->cluster_size_bits) + + 1 + (*rl)->vcn - from_vcn; + if ((na->data_flags & ATTR_COMPRESSION_MASK) + && (need < na->compression_block_clusters)) { + /* + * for a compressed file, be sure to allocate the full hole. + * We may need space to decompress existing compressed data. + */ + rlc = ntfs_cluster_alloc(vol, (*rl)->vcn, (*rl)->length, + lcn_seek_from, DATA_ZONE); + } else + rlc = ntfs_cluster_alloc(vol, from_vcn, need, + lcn_seek_from, DATA_ZONE); + if (!rlc) + goto err_out; + + *rl = ntfs_runlists_merge(na->rl, rlc); + /* + * For a compressed attribute, we must be sure there is an + * available entry, so reserve it before it gets too late. + */ + if (*rl && (na->data_flags & ATTR_COMPRESSION_MASK)) + *rl = ntfs_rl_extend(*rl,1); + 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) { + /* Clear non-sparse region from @cur_vcn to @*ofs. */ + if (ntfs_attr_fill_zero(na, cur_vcn << vol->cluster_size_bits, + *ofs)) + goto err_out; + } + 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; +} + +static int stuff_hole(ntfs_attr *na, const s64 pos); + +/** + * 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; + s64 fullcount; + ntfs_attr_search_ctx *ctx = NULL; + runlist_element *rl; + s64 hole_end; + int eo; + int compressed_part; + struct { + unsigned int undo_initialized_size : 1; + unsigned int undo_data_size : 1; + } need_to = { 0, 0 }; + BOOL makingnonresident = FALSE; + BOOL wasnonresident = FALSE; + BOOL compressed; + + ntfs_log_enter("Entering for inode %lld, attr 0x%x, pos 0x%llx, count " + "0x%llx.\n", (long long)na->ni->mft_no, na->type, + (long long)pos, (long long)count); + + if (!na || !na->ni || !na->ni->vol || !b || pos < 0 || count < 0) { + errno = EINVAL; + ntfs_log_perror("%s", __FUNCTION__); + goto errno_set; + } + vol = na->ni->vol; + compressed = (na->data_flags & ATTR_COMPRESSION_MASK) + != const_cpu_to_le16(0); + /* + * Encrypted attributes are only supported in raw mode. We return + * access denied, which is what Windows NT4 does, too. + * Moreover a file cannot be both encrypted and compressed. + */ + if ((na->data_flags & ATTR_IS_ENCRYPTED) + && (compressed || !vol->efs_raw)) { + errno = EACCES; + goto errno_set; + } + /* + * Fill the gap, when writing beyond the end of a compressed + * file. This will make recursive calls + */ + if (compressed + && (na->type == AT_DATA) + && (pos > na->initialized_size) + && stuff_hole(na,pos)) + goto errno_set; + /* If this is a compressed attribute it needs special treatment. */ + wasnonresident = NAttrNonResident(na) != 0; + makingnonresident = wasnonresident /* yes : already changed */ + && !pos && (count == na->initialized_size); + /* + * Writing to compressed files is currently restricted + * to appending data. However we have to accept + * recursive write calls to make the attribute non resident. + * These are writing at position 0 up to initialized_size. + * Compression is also restricted to data streams. + * Only ATTR_IS_COMPRESSED compression mode is supported. + */ + if (compressed + && ((na->type != AT_DATA) + || ((na->data_flags & ATTR_COMPRESSION_MASK) + != ATTR_IS_COMPRESSED) + || ((pos != na->initialized_size) + && (pos || (count != na->initialized_size))))) { + // TODO: Implement writing compressed attributes! (AIA) + errno = EOPNOTSUPP; + goto errno_set; + } + + if (!count) + goto out; + /* for a compressed file, get prepared to reserve a full block */ + fullcount = count; + /* 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; + } + /* resizing may change the compression mode */ + compressed = (na->data_flags & ATTR_COMPRESSION_MASK) + != const_cpu_to_le16(0); + need_to.undo_data_size = 1; + } + /* + * For compressed data, a single full block was allocated + * to deal with compression, possibly in a previous call. + * We are not able to process several blocks because + * some clusters are freed after compression and + * new allocations have to be done before proceeding, + * so truncate the requested count if needed (big buffers). + */ + if (compressed) { + fullcount = na->data_size - pos; + if (count > fullcount) + count = fullcount; + } + 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)) { + ntfs_log_perror("%s: lookup failed", __FUNCTION__); + 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; + ntfs_log_perror("%s: Sanity check failed", __FUNCTION__); + 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) + */ + ntfs_log_perror("%s: failed to write mft record", __FUNCTION__); + 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; + /* + * For a compressed attribute, we must be sure there is an + * available entry, and, when reopening a compressed file, + * we may need to split a hole. So reserve the entries + * before it gets too late. + */ + if (compressed) { + na->rl = ntfs_rl_extend(na->rl,2); + if (!na->rl) + 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) + if (ntfs_attr_fill_zero(na, na->initialized_size, + pos - na->initialized_size)) + goto err_out; + + ctx->attr->initialized_size = cpu_to_sle64(pos + count); + /* fix data_size for compressed files */ + if (compressed) + ctx->attr->data_size = ctx->attr->initialized_size; + 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; +#if CACHE_NIDATA_SIZE + if (na->ni->mrec->flags & MFT_RECORD_IS_DIRECTORY + ? na->type == AT_INDEX_ROOT && na->name == NTFS_INDEX_I30 + : na->type == AT_DATA && na->name == AT_UNNAMED) { + na->ni->data_size = na->data_size; + na->ni->allocated_size = na->allocated_size; + set_nino_flag(na->ni,KnownSize); + } +#endif + 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; + ntfs_log_perror("%s: Failed to find VCN #3", __FUNCTION__); + } + goto err_out; + } + ofs = pos - (rl->vcn << vol->cluster_size_bits); + /* + * Determine if there is compressed data in the current + * compression block (when appending to an existing file). + * If so, decompression will be needed, and the full block + * must be allocated to be identified as uncompressed. + * This comes in two variants, depending on whether + * compression has saved at least one cluster. + * The compressed size can never be over full size by + * more than 485 (maximum for 15 compression blocks + * compressed to 4098 and the last 3640 bytes compressed + * to 3640 + 3640/8 = 4095, with 15*2 + 4095 - 3640 = 485) + * This is less than the smallest cluster, so the hole is + * is never beyond the cluster next to the position of + * the first uncompressed byte to write. + */ + compressed_part = 0; + if (compressed) { + if ((rl->lcn == (LCN)LCN_HOLE) + && wasnonresident) { + if (rl->length < na->compression_block_clusters) + compressed_part + = na->compression_block_clusters + - rl->length; + else { + compressed_part + = na->compression_block_clusters; + if (rl->length > na->compression_block_clusters) { + rl[2].lcn = rl[1].lcn; + rl[2].vcn = rl[1].vcn; + rl[2].length = rl[1].length; + rl[1].vcn -= compressed_part; + rl[1].lcn = LCN_HOLE; + rl[1].length = compressed_part; + rl[0].length -= compressed_part; + ofs -= rl->length << vol->cluster_size_bits; + rl++; + } + } + /* normal hole filling will do later */ + } else + if ((rl->lcn >= 0) && (rl[1].lcn == (LCN)LCN_HOLE)) { + s64 xofs; + + if (wasnonresident) + compressed_part = na->compression_block_clusters + - rl[1].length; + rl++; + xofs = 0; + if (ntfs_attr_fill_hole(na, + rl->length << vol->cluster_size_bits, + &xofs, &rl, &update_from)) + goto err_out; + /* the fist allocated cluster was not merged */ + if (!xofs) + rl--; + } + } + /* + * 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. + */ + for (hole_end = 0; count; rl++, ofs = 0, hole_end = 0) { + if (rl->lcn == LCN_RL_NOT_MAPPED) { + rl = ntfs_attr_find_vcn(na, rl->vcn); + if (!rl) { + if (errno == ENOENT) { + errno = EIO; + ntfs_log_perror("%s: Failed to find VCN" + " #4", __FUNCTION__); + } + goto rl_err_out; + } + /* Needed for case when runs merged. */ + ofs = pos + total - (rl->vcn << vol->cluster_size_bits); + } + if (!rl->length) { + errno = EIO; + ntfs_log_perror("%s: Zero run length", __FUNCTION__); + goto rl_err_out; + } + if (rl->lcn < (LCN)0) { + hole_end = rl->vcn + rl->length; + + if (rl->lcn != (LCN)LCN_HOLE) { + errno = EIO; + ntfs_log_perror("%s: Unexpected LCN (%lld)", + __FUNCTION__, + (long long)rl->lcn); + goto rl_err_out; + } + if (ntfs_attr_fill_hole(na, fullcount, &ofs, &rl, + &update_from)) + goto err_out; + } + if (compressed) { + while (rl->length + && (ofs >= (rl->length << vol->cluster_size_bits))) { + ofs -= rl->length << vol->cluster_size_bits; + rl++; + } + } + + /* 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", (long long)to_write, (long long)rl->vcn, + (long long)rl->lcn, (long long)ofs); + if (!NVolReadOnly(vol)) { + + s64 wpos = (rl->lcn << vol->cluster_size_bits) + ofs; + s64 wend = (rl->vcn << vol->cluster_size_bits) + ofs + to_write; + u32 bsize = vol->cluster_size; + /* Byte size needed to zero fill a cluster */ + s64 rounding = ((wend + bsize - 1) & ~(s64)(bsize - 1)) - wend; + /** + * Zero fill to cluster boundary if we're writing at the + * end of the attribute or into an ex-sparse cluster. + * This will cause the kernel not to seek and read disk + * blocks during write(2) to fill the end of the buffer + * which increases write speed by 2-10 fold typically. + * + * This is done even for compressed files, because + * data is generally first written uncompressed. + */ + if (rounding && ((wend == na->initialized_size) || + (wend < (hole_end << vol->cluster_size_bits)))){ + + char *cb; + + rounding += to_write; + + cb = ntfs_malloc(rounding); + if (!cb) + goto err_out; + + memcpy(cb, b, to_write); + memset(cb + to_write, 0, rounding - to_write); + + if (compressed) { + written = ntfs_compressed_pwrite(na, + rl, wpos, ofs, to_write, + rounding, b, compressed_part); + } else { + written = ntfs_pwrite(vol->dev, wpos, + rounding, cb); + if (written == rounding) + written = to_write; + } + + free(cb); + } else { + if (compressed) { + written = ntfs_compressed_pwrite(na, + rl, wpos, ofs, to_write, + to_write, b, compressed_part); + } else + written = ntfs_pwrite(vol->dev, wpos, + to_write, b); + } + } else + written = to_write; + /* If everything ok, update progress counters and continue. */ + if (written > 0) { + total += written; + count -= written; + fullcount -= written; + b = (const u8*)b + written; + } + if (written != to_write) { + /* Partial write cannot be dealt with, stop there */ + /* If the syscall was interrupted, try again. */ + if (written == (s64)-1 && errno == EINTR) + goto retry; + if (!written) + errno = EIO; + goto rl_err_out; + } + compressed_part = 0; + } +done: + if (ctx) + ntfs_attr_put_search_ctx(ctx); + /* Update mapping pairs if needed. */ + if ((update_from != -1) + || (compressed && !makingnonresident)) + if (ntfs_attr_update_mapping_pairs(na, 0 /*update_from*/)) { + /* + * FIXME: trying to recover by goto rl_err_out; + * could cause driver hang by infinite looping. + */ + total = -1; + goto out; + } +out: + ntfs_log_leave("\n"); + 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) + */ + 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; +} + +int ntfs_attr_pclose(ntfs_attr *na) +{ + s64 ofs; + int failed; + BOOL ok = TRUE; + VCN update_from = -1; + ntfs_volume *vol; + ntfs_attr_search_ctx *ctx = NULL; + runlist_element *rl; + int eo; + s64 hole; + int compressed_part; + BOOL compressed; + + ntfs_log_enter("Entering for inode 0x%llx, attr 0x%x.\n", + na->ni->mft_no, na->type); + + if (!na || !na->ni || !na->ni->vol) { + errno = EINVAL; + ntfs_log_perror("%s", __FUNCTION__); + goto errno_set; + } + vol = na->ni->vol; + compressed = (na->data_flags & ATTR_COMPRESSION_MASK) + != const_cpu_to_le16(0); + /* + * 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 not a compressed attribute get out */ + /* same if it is resident */ + if (!compressed || !NAttrNonResident(na)) + goto out; + + /* + * For a compressed attribute, we must be sure there is an + * available entry, so reserve it before it gets too late. + */ + if (ntfs_attr_map_whole_runlist(na)) + goto err_out; + na->rl = ntfs_rl_extend(na->rl,1); + if (!na->rl) + goto err_out; + /* Find the runlist element containing the terminal vcn. */ + rl = ntfs_attr_find_vcn(na, (na->initialized_size - 1) >> vol->cluster_size_bits); + if (!rl) { + /* + * If the vcn is not present it is an out of bounds write. + * However, we have already written the last byte uncompressed, + * so getting this here must be an error of some kind. + */ + if (errno == ENOENT) { + errno = EIO; + ntfs_log_perror("%s: Failed to find VCN #5", __FUNCTION__); + } + 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. + */ + compressed_part = 0; + if ((rl->lcn >= 0) && (rl[1].lcn == (LCN)LCN_HOLE)) + compressed_part + = na->compression_block_clusters - rl[1].length; + else + if (rl->lcn == (LCN)LCN_HOLE) { + if (rl->length < na->compression_block_clusters) + compressed_part + = na->compression_block_clusters + - rl->length; + else + compressed_part + = na->compression_block_clusters; + } + /* done, if the last block set was compressed */ + if (compressed_part) + goto out; + + ofs = na->initialized_size - (rl->vcn << vol->cluster_size_bits); + + if (rl->lcn == LCN_RL_NOT_MAPPED) { + rl = ntfs_attr_find_vcn(na, rl->vcn); + if (!rl) { + if (errno == ENOENT) { + errno = EIO; + ntfs_log_perror("%s: Failed to find VCN" + " #6", __FUNCTION__); + } + goto rl_err_out; + } + /* Needed for case when runs merged. */ + ofs = na->initialized_size - (rl->vcn << vol->cluster_size_bits); + } + if (!rl->length) { + errno = EIO; + ntfs_log_perror("%s: Zero run length", __FUNCTION__); + goto rl_err_out; + } + if (rl->lcn < (LCN)0) { + hole = rl->vcn + rl->length; + if (rl->lcn != (LCN)LCN_HOLE) { + errno = EIO; + ntfs_log_perror("%s: Unexpected LCN (%lld)", + __FUNCTION__, + (long long)rl->lcn); + goto rl_err_out; + } + + if (ntfs_attr_fill_hole(na, (s64)0, &ofs, &rl, &update_from)) + goto err_out; + } + while (rl->length + && (ofs >= (rl->length << vol->cluster_size_bits))) { + ofs -= rl->length << vol->cluster_size_bits; + rl++; + } + +retry: + failed = 0; + if (!NVolReadOnly(vol)) { + failed = ntfs_compressed_close(na, rl, ofs); +#if CACHE_NIDATA_SIZE + if (na->ni->mrec->flags & MFT_RECORD_IS_DIRECTORY + ? na->type == AT_INDEX_ROOT && na->name == NTFS_INDEX_I30 + : na->type == AT_DATA && na->name == AT_UNNAMED) { + na->ni->data_size = na->data_size; + na->ni->allocated_size = na->allocated_size; + set_nino_flag(na->ni,KnownSize); + } +#endif + } + if (failed) { + /* If the syscall was interrupted, try again. */ + if (errno == EINTR) + goto retry; + else + goto rl_err_out; + } + if (ctx) + ntfs_attr_put_search_ctx(ctx); + /* Update mapping pairs if needed. */ + if (ntfs_attr_update_mapping_pairs(na, 0 /*update_from*/)) { + /* + * FIXME: trying to recover by goto rl_err_out; + * could cause driver hang by infinite looping. + */ + ok = FALSE; + goto out; + } +out: + ntfs_log_leave("\n"); + return (!ok); +rl_err_out: + /* + * need not restore old sizes, only compressed_size + * can have changed. It has been set according to + * the current runlist while updating the mapping pairs, + * and must be kept consistent with the runlists. + */ +err_out: + eo = errno; + if (ctx) + ntfs_attr_put_search_ctx(ctx); + /* Update mapping pairs if needed. */ + ntfs_attr_update_mapping_pairs(na, 0 /*update_from*/); + errno = eo; +errno_set: + ok = FALSE; + 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; + ntfs_log_perror("%s", __FUNCTION__); + 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. */ + ntfs_log_perror("%s #1", __FUNCTION__); + if (!i) + return err; + bk_cnt = i; + break; + } + } + /* Write the prepared data. */ + written = ntfs_attr_pwrite(na, pos, bk_cnt * bk_size, src); + if (written <= 0) { + ntfs_log_perror("%s: written=%lld", __FUNCTION__, + (long long)written); + } + /* 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; + ntfs_log_perror("%s", __FUNCTION__); + 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 { + register int rc; + if (name && ((rc = ntfs_names_full_collate(name, + name_len, (ntfschar*)((char*)a + + le16_to_cpu(a->name_offset)), + a->name_length, ic, + upcase, upcase_len)))) { + /* + * If @name collates before a->name, + * there is no matching attribute. + */ + if (rc < 0) { + errno = ENOENT; + return -1; + } + /* If the strings are not equal, continue search. */ + 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; + } + } + } + errno = EIO; + ntfs_log_perror("%s: Corrupt inode (%lld)", __FUNCTION__, + ctx->ntfs_ino ? (long long)ctx->ntfs_ino->mft_no : -1); + 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 %lld, 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; + ntfs_log_perror("%s", __FUNCTION__); + 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! */ + errno = EIO; + ntfs_log_error("Attribute list wasn't found"); + 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 { + int rc; + + if (name && ((rc = ntfs_names_full_collate(name, + name_len, al_name, al_name_len, ic, + vol->upcase, vol->upcase_len)))) { + + /* + * If @name collates before al_name, + * there is no matching attribute. + */ + if (rc < 0) + goto not_found; + /* If the strings are not equal, continue search. */ + 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_error("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) + 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; + } + errno = EIO; + ntfs_log_perror("Inode is corrupt (%lld)", (long long)base_ni->mft_no); + 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; + int ret = -1; + + ntfs_log_enter("Entering for attribute type 0x%x\n", type); + + 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; + ntfs_log_perror("%s", __FUNCTION__); + goto out; + } + + 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) + ret = ntfs_attr_find(type, name, name_len, ic, val, val_len, ctx); + else + ret = ntfs_external_attr_find(type, name, name_len, ic, + lowest_vcn, val, val_len, ctx); +out: + ntfs_log_leave("\n"); + return ret; +} + +/** + * ntfs_attr_position - find given or next attribute type in an ntfs inode + * @type: attribute type to start lookup + * @ctx: search context with mft record and attribute to search from + * + * Find an attribute type in an ntfs inode or the next attribute which is not + * the AT_END attribute. Please see more details at ntfs_attr_lookup. + * + * Return 0 if the search was successful and -1 if not, with errno set to the + * error code. + * + * The following error codes are defined: + * EINVAL Invalid arguments. + * EIO I/O error or corrupt data structures found. + * ENOMEM Not enough memory to allocate necessary buffers. + * ENOSPC No attribute was found after 'type', only AT_END. + */ +int ntfs_attr_position(const ATTR_TYPES type, ntfs_attr_search_ctx *ctx) +{ + if (ntfs_attr_lookup(type, NULL, 0, CASE_SENSITIVE, 0, NULL, 0, ctx)) { + if (errno != ENOENT) + return -1; + if (ctx->attr->type == AT_END) { + errno = ENOSPC; + return -1; + } + } + return 0; +} + +/** + * 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; + ntfs_log_perror("%s: type=%d", __FUNCTION__, type); + 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; + } + errno = ENOENT; + ntfs_log_perror("%s: type=%d", __FUNCTION__, type); + 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; + s64 min_size, max_size; + + if (size < 0) { + errno = EINVAL; + ntfs_log_perror("%s: size=%lld", __FUNCTION__, + (long long)size); + return -1; + } + + /* + * $ATTRIBUTE_LIST shouldn't be greater than 0x40000, otherwise + * Windows would crash. This is not listed in the AttrDef. + */ + if (type == AT_ATTRIBUTE_LIST && size > 0x40000) { + errno = ERANGE; + ntfs_log_perror("Too large attrlist (%lld)", (long long)size); + return -1; + } + + ad = ntfs_attr_find_in_attrdef(vol, type); + if (!ad) + return -1; + + min_size = sle64_to_cpu(ad->min_size); + max_size = sle64_to_cpu(ad->max_size); + + if ((min_size && (size < min_size)) || + ((max_size > 0) && (size > max_size))) { + errno = ERANGE; + ntfs_log_perror("Attr type %d size check failed (min,size,max=" + "%lld,%lld,%lld)", type, (long long)min_size, + (long long)size, (long long)max_size); + 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 to check + * @name: attribute name to check + * @name_len: attribute name length + * + * Check whether the attribute of @type and @name with name length @name_len on + * the ntfs volume @vol is allowed to be non-resident. This information is + * obtained from $AttrDef system file and is augmented by rules imposed by + * Microsoft (e.g. see http://support.microsoft.com/kb/974729/). + * + * 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). + */ +static int ntfs_attr_can_be_non_resident(const ntfs_volume *vol, const ATTR_TYPES type, + const ntfschar *name, int name_len) +{ + ATTR_DEF *ad; + BOOL allowed; + + /* + * Microsoft has decreed that $LOGGED_UTILITY_STREAM attributes with a + * name of $TXF_DATA must be resident despite the entry for + * $LOGGED_UTILITY_STREAM in $AttrDef allowing them to be non-resident. + * Failure to obey this on the root directory mft record of a volume + * causes Windows Vista and later to see the volume as a RAW volume and + * thus cannot mount it at all. + */ + if ((type == AT_LOGGED_UTILITY_STREAM) + && name + && ntfs_names_are_equal(TXF_DATA, 9, name, name_len, + CASE_SENSITIVE, vol->upcase, vol->upcase_len)) + allowed = FALSE; + else { + /* 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. */ + allowed = !(ad->flags & ATTR_DEF_RESIDENT); + } + if (!allowed) { + 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) { + errno = EINVAL; + ntfs_log_perror("%s: pos=%p m=%p", __FUNCTION__, pos, m); + 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) || + pos + size > (u8*)m + 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 data_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) data_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 = (name_len + ? cpu_to_le16(offsetof(ATTR_RECORD, resident_end)) + : const_cpu_to_le16(0)); + a->flags = data_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; + } + } + if (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY + ? type == AT_INDEX_ROOT && name == NTFS_INDEX_I30 + : type == AT_DATA && name == AT_UNNAMED) { + ni->data_size = size; + ni->allocated_size = (size + 7) & ~7; + set_nino_flag(ni,KnownSize); + } + 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, name, name_len)) { + if (errno == EPERM) + ntfs_log_perror("Attribute can't be non resident"); + else + ntfs_log_perror("ntfs_attr_can_be_non_resident failed"); + 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_perror("Attribute 0x%x already present", type); + goto put_err_out; + } + if (errno != ENOENT) { + ntfs_log_perror("ntfs_attr_find failed"); + 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_perror("Failed to make room for attribute"); + 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) + ? STANDARD_COMPRESSION_UNIT : 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_log_perror("Failed add attr entry to attrlist"); + ntfs_attr_record_resize(m, a, 0); + 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_perror("%s: attribute lookup failed", __FUNCTION__); + 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; + + 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"); + errno = 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 doesn'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; + BOOL can_be_non_resident = FALSE; + ntfs_inode *attr_ni; + ntfs_attr *na; + ATTR_FLAGS data_flags; + + if (!ni || size < 0 || type == AT_ATTRIBUTE_LIST) { + errno = EINVAL; + ntfs_log_perror("%s: ni=%p size=%lld", __FUNCTION__, ni, + (long long)size); + return -1; + } + + ntfs_log_trace("Entering for inode %lld, attr %x, size %lld.\n", + (long long)ni->mft_no, type, (long long)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 == ENOENT) + errno = EIO; + return -1; + } + + /* Sanity checks for always resident attributes. */ + if (ntfs_attr_can_be_non_resident(ni->vol, type, name, name_len)) { + if (errno != EPERM) { + err = errno; + ntfs_log_perror("ntfs_attr_can_be_non_resident failed"); + goto err_out; + } + /* @val is mandatory. */ + if (!val) { + errno = EINVAL; + ntfs_log_perror("val is mandatory for always resident " + "attributes"); + return -1; + } + if (size > ni->vol->mft_record_size) { + errno = ERANGE; + ntfs_log_perror("Attribute is too big"); + return -1; + } + } else + can_be_non_resident = TRUE; + + /* + * 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_perror("ntfs_attr_can_be_resident failed"); + 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_perror("Failed to attach all extents to inode"); + 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_perror("Failed to add attribute list"); + 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_perror("Failed to allocate extent record"); + goto err_out; + } + +add_attr_record: + if ((ni->flags & FILE_ATTR_COMPRESSED) + && (ni->vol->cluster_size <= MAX_COMPRESSION_CLUSTER_SIZE) + && ((type == AT_DATA) + || ((type == AT_INDEX_ROOT) && (name == NTFS_INDEX_I30)))) + data_flags = ATTR_IS_COMPRESSED; + else + data_flags = const_cpu_to_le16(0); + if (is_resident) { + /* Add resident attribute. */ + offset = ntfs_resident_attr_record_add(attr_ni, type, name, + name_len, val, size, data_flags); + if (offset < 0) { + if (errno == ENOSPC && can_be_non_resident) + goto add_non_resident; + err = errno; + ntfs_log_perror("Failed to add resident attribute"); + goto free_err_out; + } + return 0; + } + +add_non_resident: + /* Add non resident attribute. */ + offset = ntfs_non_resident_attr_record_add(attr_ni, type, name, + name_len, 0, 8, data_flags); + if (offset < 0) { + err = errno; + ntfs_log_perror("Failed to add non resident attribute"); + 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_perror("Failed to open just added attribute"); + 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_perror("Failed to initialize just added attribute"); + if (ntfs_attr_rm(na)) + ntfs_log_perror("Failed to remove just added attribute"); + ntfs_attr_close(na); + goto err_out; + } + ntfs_attr_close(na); + 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_perror("Failed to remove just added attribute #2"); +free_err_out: + /* Free MFT record, if it doesn't contain attributes. */ + if (le32_to_cpu(attr_ni->mrec->bytes_in_use) - + le16_to_cpu(attr_ni->mrec->attrs_offset) == 8) + if (ntfs_mft_record_free(attr_ni->vol, attr_ni)) + ntfs_log_perror("Failed to free MFT record"); +err_out: + errno = err; + return -1; +} + +/* + * Change an attribute flag + */ + +int ntfs_attr_set_flags(ntfs_inode *ni, ATTR_TYPES type, + ntfschar *name, u8 name_len, ATTR_FLAGS flags, ATTR_FLAGS mask) +{ + ntfs_attr_search_ctx *ctx; + int res; + + res = -1; + /* Search for designated attribute */ + ctx = ntfs_attr_get_search_ctx(ni, NULL); + if (ctx) { + if (!ntfs_attr_lookup(type, name, name_len, + CASE_SENSITIVE, 0, NULL, 0, ctx)) { + /* do the requested change (all small endian le16) */ + ctx->attr->flags = (ctx->attr->flags & ~mask) + | (flags & mask); + NInoSetDirty(ni); + res = 0; + } + ntfs_attr_put_search_ctx(ctx); + } + return (res); +} + + +/** + * 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); + } + ntfs_attr_put_search_ctx(ctx); + if (errno != ENOENT) { + ntfs_log_trace("Attribute lookup failed. Probably leaving inconstant " + "metadata.\n"); + ret = -1; + } + + 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; + } + + if (a->type == AT_INDEX_ROOT && new_size > attr_size && + new_muse + 120 > alloc_size && old_size + 120 <= alloc_size) { + errno = ENOSPC; + ntfs_log_trace("Too big INDEX_ROOT (%u > %u)\n", + new_muse, alloc_size); + return STATUS_RESIDENT_ATTRIBUTE_FILLED_MFT; + } + + /* 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) +{ + int ret; + + ntfs_log_trace("Entering for new size %u.\n", (unsigned)new_size); + + /* Resize the resident part of the attribute record. */ + if ((ret = ntfs_attr_record_resize(m, a, (le16_to_cpu(a->value_offset) + + new_size + 7) & ~7)) < 0) + return ret; + /* + * 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) + 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) { + errno = EINVAL; + ntfs_log_perror("%s: ctx=%p ctx->attr=%p extra=%d", __FUNCTION__, + ctx, ctx ? ctx->attr : NULL, extra); + return -1; + } + + ntfs_log_trace("Entering for attr 0x%x, inode %llu\n", + (unsigned) le32_to_cpu(ctx->attr->type), + (unsigned 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)) { + errno = EINVAL; + ntfs_log_perror("Inode %llu has no attrlist", + (unsigned long long)base_ni->mft_no); + return -1; + } + + if (ntfs_inode_attach_all_extents(ctx->ntfs_ino)) { + ntfs_log_perror("Couldn't attach extents, inode=%llu", + (unsigned long long)base_ni->mft_no); + 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_perror("Couldn't allocate MFT record"); + return -1; + } + if (ntfs_attr_record_move_to(ctx, ni)) { + ntfs_log_perror("Couldn't move attribute to MFT record"); + 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. + */ +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, na->name, na->name_len)) + 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) + 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. + */ + NAttrClearSparse(na); + NAttrClearEncrypted(na); + if ((a->flags & ATTR_COMPRESSION_MASK) == ATTR_IS_COMPRESSED) { + /* set compression writing parameters */ + na->compression_block_size + = 1 << (STANDARD_COMPRESSION_UNIT + vol->cluster_size_bits); + na->compression_block_clusters = 1 << STANDARD_COMPRESSION_UNIT; + } + + 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, INT_MAX); + 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. */ + if (na->ni->flags & FILE_ATTR_COMPRESSED) + name_ofs = (sizeof(ATTR_REC) + 7) & ~7; + else + 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); + + /* 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); + + /* + * Update the flags to match the in-memory ones. + * However cannot change the compression state if we had + * a fuse_file_info open with a mark for release. + * The decisions about compression can only be made when + * creating/recreating the stream, not when making non resident. + */ + a->flags &= ~(ATTR_IS_SPARSE | ATTR_IS_ENCRYPTED); + if ((a->flags & ATTR_COMPRESSION_MASK) == ATTR_IS_COMPRESSED) { + /* support only ATTR_IS_COMPRESSED compression mode */ + a->compression_unit = STANDARD_COMPRESSION_UNIT; + a->compressed_size = const_cpu_to_le64(0); + } else { + a->compression_unit = 0; + a->flags &= ~ATTR_COMPRESSION_MASK; + na->data_flags = a->flags; + } + + 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; +} + + +static int ntfs_resident_attr_resize(ntfs_attr *na, const s64 newsize); + +/** + * 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_i(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; + ntfs_log_perror("ntfs_attr_lookup failed"); + 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 == ENOENT) + err = EIO; + ntfs_log_perror("%s: bounds check failed", __FUNCTION__); + 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 (!(ret = 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 ((na->data_flags & ATTR_COMPRESSION_MASK) + || NAttrSparse(na)) + na->compressed_size = na->allocated_size; + if (na->ni->mrec->flags & MFT_RECORD_IS_DIRECTORY + ? na->type == AT_INDEX_ROOT && na->name == NTFS_INDEX_I30 + : na->type == AT_DATA && na->name == AT_UNNAMED) { + na->ni->data_size = na->data_size; + na->ni->allocated_size = na->allocated_size; + set_nino_flag(na->ni,KnownSize); + if (na->type == AT_DATA) + NInoFileNameSetDirty(na->ni); + } + goto resize_done; + } + /* Prefer AT_INDEX_ALLOCATION instead of AT_ATTRIBUTE_LIST */ + if (ret == STATUS_RESIDENT_ATTRIBUTE_FILLED_MFT) { + err = errno; + goto put_err_out; + } + } + /* 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_perror("Failed to make attribute non-resident"); + 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_perror("Couldn't open attribute"); + 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_perror("%s: Attribute lookup failed 1", __FUNCTION__); + goto put_err_out; + } + + /* + * The standard information and attribute list attributes can't be + * moved out from the base MFT record, so try to move out others. + */ + if (na->type==AT_STANDARD_INFORMATION || 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_perror("Could not free space in MFT record"); + 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_perror("%s: Attribute lookup failed 2", __FUNCTION__); + 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_perror("Couldn't allocate new MFT record"); + goto put_err_out; + } + /* Move attribute to it. */ + if (ntfs_attr_record_move_to(ctx, ni)) { + err = errno; + ntfs_log_perror("Couldn't move attribute to new MFT record"); + 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); + ntfs_attr_put_search_ctx(ctx); + return 0; +put_err_out: + ntfs_attr_put_search_ctx(ctx); + errno = err; + return ret; +} + +static int ntfs_resident_attr_resize(ntfs_attr *na, const s64 newsize) +{ + int ret; + + ntfs_log_enter("Entering\n"); + ret = ntfs_resident_attr_resize_i(na, newsize); + ntfs_log_leave("\n"); + 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"); + errno = 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 (na->data_flags & ATTR_IS_ENCRYPTED) { + ntfs_log_trace("Making encrypted streams 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); + /* + * If a data stream was wiped out, adjust the compression mode + * to current state of compression flag + */ + if (!na->data_size + && (na->type == AT_DATA) + && (na->ni->vol->cluster_size <= MAX_COMPRESSION_CLUSTER_SIZE) + && (na->ni->flags & FILE_ATTR_COMPRESSED)) { + a->flags |= ATTR_IS_COMPRESSED; + na->data_flags = a->flags; + } + /* + * 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); + 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; +} + +/* + * If we are in the first extent, then set/clean sparse bit, + * update allocated and compressed size. + */ +static int ntfs_attr_update_meta(ATTR_RECORD *a, ntfs_attr *na, MFT_RECORD *m, + ntfs_attr_search_ctx *ctx) +{ + int sparse, ret = 0; + + ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x\n", + (unsigned long long)na->ni->mft_no, na->type); + + if (a->lowest_vcn) + goto out; + + a->allocated_size = cpu_to_sle64(na->allocated_size); + + /* Update sparse bit. */ + sparse = ntfs_rl_sparse(na->rl); + if (sparse == -1) { + errno = EIO; + goto error; + } + + /* Attribute become sparse. */ + if (sparse && !(a->flags & (ATTR_IS_SPARSE | ATTR_IS_COMPRESSED))) { + /* + * Move attribute to another mft record, if attribute is too + * 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)) + goto leave; + goto retry; + } + if (ntfs_attr_record_move_away(ctx, 8)) { + ntfs_log_perror("Failed to move attribute"); + goto error; + } + ntfs_attr_put_search_ctx(ctx); + goto retry; + } + if (!(le32_to_cpu(a->length) - le16_to_cpu( + a->mapping_pairs_offset))) { + errno = EIO; + ntfs_log_perror("Mapping pairs space is 0"); + goto error; + } + + NAttrSetSparse(na); + a->flags |= ATTR_IS_SPARSE; + a->compression_unit = STANDARD_COMPRESSION_UNIT; /* 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)); + + if (le16_to_cpu(a->name_offset) >= 8) + 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 || (na->data_flags & ATTR_COMPRESSION_MASK)) { + s64 new_compr_size; + + new_compr_size = ntfs_rl_get_compressed_size(na->ni->vol, na->rl); + if (new_compr_size == -1) + goto error; + + 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); + } +out: + return ret; +leave: ret = -1; goto out; /* return -1 */ +retry: ret = -2; goto out; +error: ret = -3; goto out; +} + +#define NTFS_VCN_DELETE_MARK -2 +/** + * ntfs_attr_update_mapping_pairs_i - see ntfs_attr_update_mapping_pairs + */ +static int ntfs_attr_update_mapping_pairs_i(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; + const runlist_element *stop_rl; + int err, mp_size, cur_max_mp_size, exp_max_mp_size, ret = -1; + BOOL finished_build; + +retry: + if (!na || !na->rl || from_vcn) { + errno = EINVAL; + ntfs_log_perror("%s: na=%p", __FUNCTION__, na); + return -1; + } + + ntfs_log_trace("Entering for inode %llu, attr 0x%x\n", + (unsigned long long)na->ni->mft_no, na->type); + + if (!NAttrNonResident(na)) { + errno = EINVAL; + ntfs_log_perror("%s: resident attribute", __FUNCTION__); + return -1; + } + + 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) + return -1; + + /* Fill attribute records with new mapping pairs. */ + stop_vcn = 0; + stop_rl = na->rl; + 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) { + errno = EIO; + ntfs_log_perror("Bad runlist"); + 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 " + "%lld.\n", (unsigned)le32_to_cpu(a->type), + (long long)ctx->ntfs_ino->mft_no); + a->highest_vcn = cpu_to_sle64(NTFS_VCN_DELETE_MARK); + ntfs_inode_mark_dirty(ctx->ntfs_ino); + continue; + } + + switch (ntfs_attr_update_meta(a, na, m, ctx)) { + case -1: return -1; + case -2: goto retry; + case -3: 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; + /* Get the size for the rest of mapping pairs array. */ + mp_size = ntfs_get_size_for_mapping_pairs(na->ni->vol, stop_rl, + stop_vcn, exp_max_mp_size); + if (mp_size <= 0) { + ntfs_log_perror("%s: get MP size failed", __FUNCTION__); + goto put_err_out; + } + /* 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)) { + ntfs_log_perror("Attribute list is too " + "big. Defragment the " + "volume\n"); + 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_perror("Can not add attrlist"); + 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)) { + errno = EIO; + ntfs_log_perror("Failed to resize attribute"); + 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_rl)) + finished_build = TRUE; + if (stop_rl) + stop_vcn = stop_rl->vcn; + else + stop_vcn = 0; + if (!finished_build && errno != ENOSPC) { + ntfs_log_perror("Failed to build mapping pairs"); + goto put_err_out; + } + a->highest_vcn = cpu_to_sle64(stop_vcn - 1); + } + /* Check whether error occurred. */ + if (errno != ENOENT) { + ntfs_log_perror("%s: Attribute lookup failed", __FUNCTION__); + 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)) { + ntfs_log_perror("Could not remove unused attr"); + goto put_err_out; + } + ntfs_attr_reinit_search_ctx(ctx); + } + if (errno != ENOENT) { + ntfs_log_perror("%s: Attr lookup failed", __FUNCTION__); + 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, INT_MAX); + if (mp_size <= 0) { + ntfs_log_perror("%s: get mp size failed", __FUNCTION__); + goto put_err_out; + } + /* Allocate new mft record. */ + ni = ntfs_mft_record_alloc(na->ni->vol, base_ni); + if (!ni) { + ntfs_log_perror("Could not allocate new MFT record"); + 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) + + (((na->data_flags & ATTR_COMPRESSION_MASK) + || 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, + na->data_flags); + if (err == -1) { + err = errno; + ntfs_log_perror("Could not add attribute extent"); + if (ntfs_mft_record_free(na->ni->vol, ni)) + ntfs_log_perror("Could not free MFT record"); + errno = err; + 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_rl); + if (stop_rl) + stop_vcn = stop_rl->vcn; + else + stop_vcn = 0; + if (err < 0 && errno != ENOSPC) { + err = errno; + ntfs_log_perror("Failed to build MP"); + if (ntfs_mft_record_free(na->ni->vol, ni)) + ntfs_log_perror("Couldn't free MFT record"); + errno = err; + 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); + goto out; +} +#undef NTFS_VCN_DELETE_MARK + +/** + * 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) +{ + int ret; + + ntfs_log_enter("Entering\n"); + ret = ntfs_attr_update_mapping_pairs_i(na, from_vcn); + ntfs_log_leave("\n"); + return ret; +} + +/** + * 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) + 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->ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) { + if (na->type == AT_INDEX_ROOT && na->name == NTFS_INDEX_I30) { + na->ni->data_size = na->data_size; + na->ni->allocated_size = na->allocated_size; + set_nino_flag(na->ni,KnownSize); + } + } else { + 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_i(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 %lld, 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 == ENOENT) + errno = EIO; + ntfs_log_perror("%s: bounds check failed", __FUNCTION__); + 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_perror("ntfs_attr_map_whole_runlist failed"); + 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_perror("Cluster allocation failed " + "(%lld)", + (long long)first_free_vcn - + ((long long)na->allocated_size >> + vol->cluster_size_bits)); + 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_perror("Run list merge failed"); + 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_perror("Mapping pairs update failed"); + goto rollback; + } + } + + ctx = ntfs_attr_get_search_ctx(na->ni, NULL); + if (!ctx) { + err = errno; + 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_perror("Lookup of first attribute extent failed"); + 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->ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) { + if (na->type == AT_INDEX_ROOT && na->name == NTFS_INDEX_I30) { + na->ni->data_size = na->data_size; + na->ni->allocated_size = na->allocated_size; + set_nino_flag(na->ni,KnownSize); + } + } else { + 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) { + err = EIO; + ntfs_log_perror("Leaking clusters"); + } + /* 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_perror("Couldn't truncate runlist. Rollback failed"); + } else { + /* Prepare to mapping pairs update. */ + na->allocated_size = org_alloc_size; + /* Restore mapping pairs. */ + if (ntfs_attr_update_mapping_pairs(na, 0 /*na->allocated_size >> + vol->cluster_size_bits*/)) { + ntfs_log_perror("Failed to restore old mapping pairs"); + } + } + errno = err; + return -1; +put_err_out: + ntfs_attr_put_search_ctx(ctx); + errno = err; + return -1; +} + + +static int ntfs_non_resident_attr_expand(ntfs_attr *na, const s64 newsize) +{ + int ret; + + ntfs_log_enter("Entering\n"); + ret = ntfs_non_resident_attr_expand_i(na, newsize); + ntfs_log_leave("\n"); + return ret; +} + +/** + * 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 = STATUS_ERROR; + s64 fullsize; + BOOL compressed; + + 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_enter("Entering for inode %lld, attr 0x%x, size %lld\n", + (unsigned long long)na->ni->mft_no, na->type, + (long long)newsize); + + if (na->data_size == newsize) { + ntfs_log_trace("Size is already ok\n"); + ret = STATUS_OK; + goto out; + } + /* + * Encrypted attributes are not supported. We return access denied, + * which is what Windows NT4 does, too. + */ + if (na->data_flags & ATTR_IS_ENCRYPTED) { + errno = EACCES; + ntfs_log_trace("Cannot truncate encrypted attribute\n"); + goto out; + } + /* + * TODO: Implement making handling of compressed attributes. + * Currently we can only expand the attribute or delete it, + * and only for ATTR_IS_COMPRESSED. This is however possible + * for resident attributes when there is no open fuse context + * (important case : $INDEX_ROOT:$I30) + */ + compressed = (na->data_flags & ATTR_COMPRESSION_MASK) + != const_cpu_to_le16(0); + if (compressed + && NAttrNonResident(na) + && (((na->data_flags & ATTR_COMPRESSION_MASK) != ATTR_IS_COMPRESSED) + || (newsize && (newsize < na->data_size)))) { + errno = EOPNOTSUPP; + ntfs_log_perror("Failed to truncate compressed attribute"); + goto out; + } + if (NAttrNonResident(na)) { + /* + * For compressed data, the last block must be fully + * allocated, and we do not known the size of compression + * block until the attribute has been made non-resident. + * Moreover we can only process a single compression + * block at a time (from where we are about to write), + * so we silently do not allocate more. + * + * Note : do not request truncate on compressed files + * unless being able to face the consequences ! + */ + if (compressed && newsize) + fullsize = (na->initialized_size + | (na->compression_block_size - 1)) + 1; + else + fullsize = newsize; + if (fullsize > na->data_size) + ret = ntfs_non_resident_attr_expand(na, fullsize); + else + ret = ntfs_non_resident_attr_shrink(na, fullsize); + } else + ret = ntfs_resident_attr_resize(na, newsize); +out: + ntfs_log_leave("Return status %d\n", ret); + return ret; +} + +/* + * Stuff a hole in a compressed file + * + * An unallocated hole must be aligned on compression block size. + * If needed current block and target block are stuffed with zeroes. + * + * Returns 0 if succeeded, + * -1 if it failed (as explained in errno) + */ + +static int stuff_hole(ntfs_attr *na, const s64 pos) +{ + s64 size; + s64 begin_size; + s64 end_size; + char *buf; + int ret; + + ret = 0; + /* + * If the attribute is resident, the compression block size + * is not defined yet and we can make no decision. + * So we first try resizing to the target and if the + * attribute is still resident, we're done + */ + if (!NAttrNonResident(na)) { + ret = ntfs_resident_attr_resize(na, pos); + if (!ret && !NAttrNonResident(na)) + na->initialized_size = na->data_size = pos; + } + if (!ret && NAttrNonResident(na)) { + /* does the hole span over several compression block ? */ + if ((pos ^ na->initialized_size) + & ~(na->compression_block_size - 1)) { + begin_size = ((na->initialized_size - 1) + | (na->compression_block_size - 1)) + + 1 - na->initialized_size; + end_size = pos & (na->compression_block_size - 1); + size = (begin_size > end_size ? begin_size : end_size); + } else { + /* short stuffing in a single compression block */ + begin_size = size = pos - na->initialized_size; + end_size = 0; + } + if (size) + buf = (char*)ntfs_malloc(size); + else + buf = (char*)NULL; + if (buf || !size) { + memset(buf,0,size); + /* stuff into current block */ + if (begin_size + && (ntfs_attr_pwrite(na, + na->initialized_size, begin_size, buf) + != begin_size)) + ret = -1; + /* create an unstuffed hole */ + if (!ret + && ((na->initialized_size + end_size) < pos) + && ntfs_non_resident_attr_expand(na, + pos - end_size)) + ret = -1; + else + na->initialized_size + = na->data_size = pos - end_size; + /* stuff into the target block */ + if (!ret && end_size + && (ntfs_attr_pwrite(na, + na->initialized_size, end_size, buf) + != end_size)) + ret = -1; + if (buf) + free(buf); + } else + ret = -1; + } + /* make absolutely sure we have reached the target */ + if (!ret && (na->initialized_size != pos)) { + ntfs_log_error("Failed to stuff a compressed file" + "target %lld reached %lld\n", + (long long)pos, (long long)na->initialized_size); + errno = EIO; + ret = -1; + } + 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; + + ntfs_log_enter("Entering\n"); + + na = ntfs_attr_open(ni, type, name, name_len); + if (!na) { + ntfs_log_perror("ntfs_attr_open failed"); + goto err_exit; + } + 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); +err_exit: + ntfs_log_leave("\n"); + 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; +} + +int ntfs_attr_remove(ntfs_inode *ni, const ATTR_TYPES type, ntfschar *name, + u32 name_len) +{ + ntfs_attr *na; + int ret; + + ntfs_log_trace("Entering\n"); + + if (!ni) { + ntfs_log_error("%s: NULL inode pointer", __FUNCTION__); + errno = EINVAL; + return -1; + } + + na = ntfs_attr_open(ni, type, name, name_len); + if (!na) { + /* do not log removal of non-existent stream */ + if (type != AT_DATA) { + ntfs_log_perror("Failed to open attribute 0x%02x of inode " + "0x%llx", type, (unsigned long long)ni->mft_no); + } + return -1; + } + + ret = ntfs_attr_rm(na); + if (ret) + ntfs_log_perror("Failed to remove attribute 0x%02x of inode " + "0x%llx", type, (unsigned long long)ni->mft_no); + ntfs_attr_close(na); + + return ret; +} + +/* Below macros are 32-bit ready. */ +#define BCX(x) ((x) - (((x) >> 1) & 0x77777777) - \ + (((x) >> 2) & 0x33333333) - \ + (((x) >> 3) & 0x11111111)) +#define BITCOUNT(x) (((BCX(x) + (BCX(x) >> 4)) & 0x0F0F0F0F) % 255) + +static u8 *ntfs_init_lut256(void) +{ + int i; + u8 *lut; + + lut = ntfs_malloc(256); + if (lut) + for(i = 0; i < 256; i++) + *(lut + i) = 8 - BITCOUNT(i); + return lut; +} + +s64 ntfs_attr_get_free_bits(ntfs_attr *na) +{ + u8 *buf, *lut; + s64 br = 0; + s64 total = 0; + s64 nr_free = 0; + + lut = ntfs_init_lut256(); + if (!lut) + return -1; + + buf = ntfs_malloc(65536); + if (!buf) + goto out; + + while (1) { + u32 *p; + br = ntfs_attr_pread(na, total, 65536, buf); + if (br <= 0) + break; + total += br; + p = (u32 *)buf + br / 4 - 1; + for (; (u8 *)p >= buf; p--) { + nr_free += lut[ *p & 255] + + lut[(*p >> 8) & 255] + + lut[(*p >> 16) & 255] + + lut[(*p >> 24) ]; + } + switch (br % 4) { + case 3: nr_free += lut[*(buf + br - 3)]; + case 2: nr_free += lut[*(buf + br - 2)]; + case 1: nr_free += lut[*(buf + br - 1)]; + } + } + free(buf); +out: + free(lut); + if (!total || br < 0) + return -1; + return nr_free; +} |