mirror of
https://git.code.sf.net/p/ntfs-3g/ntfs-3g.git
synced 2024-11-27 12:03:42 +08:00
f0c5c2a54f
Updating an attribute may imply decompressing runlists which are not contiguous, leaving an unmapped region between them. When checking whether the attribute has been made sparse, such unmapped regions should be ignored This mostly happens after updating an index. (fix by Forrest Liu)
6819 lines
199 KiB
C
6819 lines
199 KiB
C
/**
|
|
* 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-2013 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"
|
|
|
|
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;
|
|
le16 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->non_resident ? !a->initialized_size : !a->value_length)) {
|
|
/*
|
|
* 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.
|
|
*
|
|
* Also prevent compression on NTFS version < 3.0
|
|
* or cluster size > 4K or compression is disabled
|
|
*/
|
|
a->flags &= ~ATTR_COMPRESSION_MASK;
|
|
if ((ni->flags & FILE_ATTR_COMPRESSED)
|
|
&& (ni->vol->major_ver >= 3)
|
|
&& NVolCompression(ni->vol)
|
|
&& (ni->vol->cluster_size <= MAX_COMPRESSION_CLUSTER_SIZE))
|
|
a->flags |= ATTR_IS_COMPRESSED;
|
|
}
|
|
|
|
cs = a->flags & (ATTR_IS_COMPRESSED | ATTR_IS_SPARSE);
|
|
|
|
/* a file may be sparse though its unnamed data is not (cf $UsnJrnl) */
|
|
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;
|
|
}
|
|
|
|
#if PARTIAL_RUNLIST_UPDATING
|
|
|
|
/*
|
|
* Map the runlist of an attribute from some point to the end
|
|
*
|
|
* Returns 0 if success,
|
|
* -1 if it failed (errno telling why)
|
|
*/
|
|
|
|
static int ntfs_attr_map_partial_runlist(ntfs_attr *na, VCN vcn)
|
|
{
|
|
LCN lcn;
|
|
VCN last_vcn;
|
|
VCN highest_vcn;
|
|
VCN needed;
|
|
VCN existing_vcn;
|
|
runlist_element *rl;
|
|
ATTR_RECORD *a;
|
|
BOOL startseen;
|
|
ntfs_attr_search_ctx *ctx;
|
|
|
|
lcn = ntfs_rl_vcn_to_lcn(na->rl, vcn);
|
|
if (lcn >= 0 || lcn == LCN_HOLE || lcn == LCN_ENOENT)
|
|
return 0;
|
|
|
|
existing_vcn = (na->rl ? na->rl->vcn : -1);
|
|
|
|
ctx = ntfs_attr_get_search_ctx(na->ni, NULL);
|
|
if (!ctx)
|
|
return -1;
|
|
|
|
/* Get the last vcn in the attribute. */
|
|
last_vcn = na->allocated_size >> na->ni->vol->cluster_size_bits;
|
|
|
|
needed = vcn;
|
|
highest_vcn = 0;
|
|
startseen = FALSE;
|
|
do {
|
|
/* Find the attribute in the mft record. */
|
|
if (!ntfs_attr_lookup(na->type, na->name, na->name_len, CASE_SENSITIVE,
|
|
needed, NULL, 0, ctx)) {
|
|
|
|
a = ctx->attr;
|
|
/* Decode and merge the runlist. */
|
|
rl = ntfs_mapping_pairs_decompress(na->ni->vol, a,
|
|
na->rl);
|
|
if (rl) {
|
|
na->rl = rl;
|
|
highest_vcn = le64_to_cpu(a->highest_vcn);
|
|
/* corruption detection */
|
|
if (((highest_vcn + 1) < last_vcn)
|
|
&& ((highest_vcn + 1) <= needed)) {
|
|
ntfs_log_error("Corrupt attribute list\n");
|
|
rl = (runlist_element*)NULL;
|
|
}
|
|
needed = highest_vcn + 1;
|
|
if (!a->lowest_vcn)
|
|
startseen = TRUE;
|
|
/* reaching a previously allocated part ? */
|
|
if ((existing_vcn >= 0)
|
|
&& (needed >= existing_vcn)) {
|
|
needed = last_vcn;
|
|
}
|
|
}
|
|
} else
|
|
rl = (runlist_element*)NULL;
|
|
} while (rl && (needed < last_vcn));
|
|
ntfs_attr_put_search_ctx(ctx);
|
|
/* mark fully mapped if we did so */
|
|
if (rl && startseen)
|
|
NAttrSetFullyMapped(na);
|
|
return (rl ? 0 : -1);
|
|
}
|
|
|
|
#endif
|
|
|
|
/**
|
|
* 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);
|
|
|
|
/* avoid multiple full runlist mappings */
|
|
if (NAttrFullyMapped(na)) {
|
|
ret = 0;
|
|
goto out;
|
|
}
|
|
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) {
|
|
NAttrSetFullyMapped(na);
|
|
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);
|
|
/*
|
|
* If the zeroed block is fully within a hole,
|
|
* we need not write anything, so advance as far
|
|
* as possible within the hole.
|
|
*/
|
|
if ((rli->lcn == (LCN)LCN_HOLE)
|
|
&& (ofsi <= pos)
|
|
&& (ofsi + (rli->length << vol->cluster_size_bits)
|
|
>= (pos + size))) {
|
|
size = min(end - pos, ofsi - pos
|
|
+ (rli->length << vol->cluster_size_bits));
|
|
pos += size;
|
|
} else {
|
|
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 the runlist to be able to update mapping pairs later. */
|
|
#if PARTIAL_RUNLIST_UPDATING
|
|
if ((!na->rl
|
|
|| !NAttrDataAppending(na))) {
|
|
if (ntfs_attr_map_whole_runlist(na))
|
|
goto err_out;
|
|
} else {
|
|
/* make sure the previous non-hole is mapped */
|
|
rlc = *rl;
|
|
rlc--;
|
|
if (((*rl)->lcn == LCN_HOLE)
|
|
&& cur_vcn
|
|
&& (rlc->vcn < 0)) {
|
|
if (ntfs_attr_map_partial_runlist(na, cur_vcn - 1))
|
|
goto err_out;
|
|
}
|
|
}
|
|
#else
|
|
if (ntfs_attr_map_whole_runlist(na))
|
|
goto err_out;
|
|
#endif
|
|
|
|
/* 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
|
|
* compression block, as we may need space to decompress
|
|
* existing compressed data.
|
|
* So allocate the space common to compression block
|
|
* and existing hole.
|
|
*/
|
|
VCN alloc_vcn;
|
|
|
|
if ((from_vcn & -na->compression_block_clusters) <= (*rl)->vcn)
|
|
alloc_vcn = (*rl)->vcn;
|
|
else
|
|
alloc_vcn = from_vcn & -na->compression_block_clusters;
|
|
need = (alloc_vcn | (na->compression_block_clusters - 1))
|
|
+ 1 - alloc_vcn;
|
|
if (need > (*rl)->length) {
|
|
ntfs_log_error("Cannot allocate %lld clusters"
|
|
" within a hole of %lld\n",
|
|
(long long)need,
|
|
(long long)(*rl)->length);
|
|
errno = EIO;
|
|
goto err_out;
|
|
}
|
|
rlc = ntfs_cluster_alloc(vol, alloc_vcn, need,
|
|
lcn_seek_from, DATA_ZONE);
|
|
} else
|
|
rlc = ntfs_cluster_alloc(vol, from_vcn, need,
|
|
lcn_seek_from, DATA_ZONE);
|
|
if (!rlc)
|
|
goto err_out;
|
|
if (na->data_flags & (ATTR_COMPRESSION_MASK | ATTR_IS_SPARSE))
|
|
na->compressed_size += need << vol->cluster_size_bits;
|
|
|
|
*rl = ntfs_runlists_merge(na->rl, rlc);
|
|
/*
|
|
* For a compressed attribute, we must be sure there are two
|
|
* available entries, so reserve them before it gets too late.
|
|
*/
|
|
if (*rl && (na->data_flags & ATTR_COMPRESSION_MASK)) {
|
|
runlist_element *oldrl = na->rl;
|
|
na->rl = *rl;
|
|
*rl = ntfs_rl_extend(na,*rl,2);
|
|
if (!*rl) na->rl = oldrl; /* restore to original if failed */
|
|
}
|
|
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->unused_runs = 2;
|
|
na->rl = *rl;
|
|
if ((*update_from == -1) || (from_vcn < *update_from))
|
|
*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);
|
|
|
|
/*
|
|
* Split an existing hole for overwriting with data
|
|
* The hole may have to be split into two or three parts, so
|
|
* that the overwritten part fits within a single compression block
|
|
*
|
|
* No cluster allocation is needed, this will be done later in
|
|
* standard hole filling, hence no need to reserve runs for
|
|
* future needs.
|
|
*
|
|
* Returns the number of clusters with existing compressed data
|
|
* in the compression block to be written to
|
|
* (or the full block, if it was a full hole)
|
|
* -1 if there were an error
|
|
*/
|
|
|
|
static int split_compressed_hole(ntfs_attr *na, runlist_element **prl,
|
|
s64 pos, s64 count, VCN *update_from)
|
|
{
|
|
int compressed_part;
|
|
int cluster_size_bits = na->ni->vol->cluster_size_bits;
|
|
runlist_element *rl = *prl;
|
|
|
|
compressed_part
|
|
= na->compression_block_clusters;
|
|
/* reserve entries in runlist if we have to split */
|
|
if (rl->length > na->compression_block_clusters) {
|
|
*prl = ntfs_rl_extend(na,*prl,2);
|
|
if (!*prl) {
|
|
compressed_part = -1;
|
|
} else {
|
|
rl = *prl;
|
|
na->unused_runs = 2;
|
|
}
|
|
}
|
|
if (*prl && (rl->length > na->compression_block_clusters)) {
|
|
/*
|
|
* Locate the update part relative to beginning of
|
|
* current run
|
|
*/
|
|
int beginwrite = (pos >> cluster_size_bits) - rl->vcn;
|
|
s32 endblock = (((pos + count - 1) >> cluster_size_bits)
|
|
| (na->compression_block_clusters - 1)) + 1 - rl->vcn;
|
|
|
|
compressed_part = na->compression_block_clusters
|
|
- (rl->length & (na->compression_block_clusters - 1));
|
|
if ((beginwrite + compressed_part) >= na->compression_block_clusters)
|
|
compressed_part = na->compression_block_clusters;
|
|
/*
|
|
* if the run ends beyond end of needed block
|
|
* we have to split the run
|
|
*/
|
|
if (endblock < rl[0].length) {
|
|
runlist_element *xrl;
|
|
int n;
|
|
|
|
/*
|
|
* we have to split into three parts if the run
|
|
* does not end within the first compression block.
|
|
* This means the hole begins before the
|
|
* compression block.
|
|
*/
|
|
if (endblock > na->compression_block_clusters) {
|
|
if (na->unused_runs < 2) {
|
|
ntfs_log_error("No free run, case 1\n");
|
|
}
|
|
na->unused_runs -= 2;
|
|
xrl = rl;
|
|
n = 0;
|
|
while (xrl->length) {
|
|
xrl++;
|
|
n++;
|
|
}
|
|
do {
|
|
xrl[2] = *xrl;
|
|
xrl--;
|
|
} while (xrl != rl);
|
|
rl[1].length = na->compression_block_clusters;
|
|
rl[2].length = rl[0].length - endblock;
|
|
rl[0].length = endblock
|
|
- na->compression_block_clusters;
|
|
rl[1].lcn = LCN_HOLE;
|
|
rl[2].lcn = LCN_HOLE;
|
|
rl[1].vcn = rl[0].vcn + rl[0].length;
|
|
rl[2].vcn = rl[1].vcn
|
|
+ na->compression_block_clusters;
|
|
rl = ++(*prl);
|
|
} else {
|
|
/*
|
|
* split into two parts and use the
|
|
* first one
|
|
*/
|
|
if (!na->unused_runs) {
|
|
ntfs_log_error("No free run, case 2\n");
|
|
}
|
|
na->unused_runs--;
|
|
xrl = rl;
|
|
n = 0;
|
|
while (xrl->length) {
|
|
xrl++;
|
|
n++;
|
|
}
|
|
do {
|
|
xrl[1] = *xrl;
|
|
xrl--;
|
|
} while (xrl != rl);
|
|
if (beginwrite < endblock) {
|
|
/* we will write into the first part of hole */
|
|
rl[1].length = rl[0].length - endblock;
|
|
rl[0].length = endblock;
|
|
rl[1].vcn = rl[0].vcn + rl[0].length;
|
|
rl[1].lcn = LCN_HOLE;
|
|
} else {
|
|
/* we will write into the second part of hole */
|
|
// impossible ?
|
|
rl[1].length = rl[0].length - endblock;
|
|
rl[0].length = endblock;
|
|
rl[1].vcn = rl[0].vcn + rl[0].length;
|
|
rl[1].lcn = LCN_HOLE;
|
|
rl = ++(*prl);
|
|
}
|
|
}
|
|
} else {
|
|
if (rl[1].length) {
|
|
runlist_element *xrl;
|
|
int n;
|
|
|
|
/*
|
|
* split into two parts and use the
|
|
* last one
|
|
*/
|
|
if (!na->unused_runs) {
|
|
ntfs_log_error("No free run, case 4\n");
|
|
}
|
|
na->unused_runs--;
|
|
xrl = rl;
|
|
n = 0;
|
|
while (xrl->length) {
|
|
xrl++;
|
|
n++;
|
|
}
|
|
do {
|
|
xrl[1] = *xrl;
|
|
xrl--;
|
|
} while (xrl != rl);
|
|
} else {
|
|
rl[2].lcn = rl[1].lcn;
|
|
rl[2].vcn = rl[1].vcn;
|
|
rl[2].length = rl[1].length;
|
|
}
|
|
rl[1].vcn -= na->compression_block_clusters;
|
|
rl[1].lcn = LCN_HOLE;
|
|
rl[1].length = na->compression_block_clusters;
|
|
rl[0].length -= na->compression_block_clusters;
|
|
if (pos >= (rl[1].vcn << cluster_size_bits)) {
|
|
rl = ++(*prl);
|
|
}
|
|
}
|
|
if ((*update_from == -1) || ((*prl)->vcn < *update_from))
|
|
*update_from = (*prl)->vcn;
|
|
}
|
|
return (compressed_part);
|
|
}
|
|
|
|
/*
|
|
* Borrow space from adjacent hole for appending data
|
|
* The hole may have to be split so that the end of hole is not
|
|
* affected by cluster allocation and overwriting
|
|
* Cluster allocation is needed for the overwritten compression block
|
|
*
|
|
* Must always leave two unused entries in the runlist
|
|
*
|
|
* Returns the number of clusters with existing compressed data
|
|
* in the compression block to be written to
|
|
* -1 if there were an error
|
|
*/
|
|
|
|
static int borrow_from_hole(ntfs_attr *na, runlist_element **prl,
|
|
s64 pos, s64 count, VCN *update_from, BOOL wasnonresident)
|
|
{
|
|
int compressed_part = 0;
|
|
int cluster_size_bits = na->ni->vol->cluster_size_bits;
|
|
runlist_element *rl = *prl;
|
|
s32 endblock;
|
|
long long allocated;
|
|
runlist_element *zrl;
|
|
int irl;
|
|
BOOL undecided;
|
|
BOOL nothole;
|
|
|
|
/* check whether the compression block is fully allocated */
|
|
endblock = (((pos + count - 1) >> cluster_size_bits) | (na->compression_block_clusters - 1)) + 1 - rl->vcn;
|
|
allocated = 0;
|
|
zrl = rl;
|
|
irl = 0;
|
|
while (zrl->length && (zrl->lcn >= 0) && (allocated < endblock)) {
|
|
allocated += zrl->length;
|
|
zrl++;
|
|
irl++;
|
|
}
|
|
|
|
undecided = (allocated < endblock) && (zrl->lcn == LCN_RL_NOT_MAPPED);
|
|
nothole = (allocated >= endblock) || (zrl->lcn != LCN_HOLE);
|
|
|
|
if (undecided || nothole) {
|
|
runlist_element *orl = na->rl;
|
|
s64 olcn = (*prl)->lcn;
|
|
#if PARTIAL_RUNLIST_UPDATING
|
|
VCN prevblock;
|
|
#endif
|
|
/*
|
|
* Map the runlist, unless it has not been created.
|
|
* If appending data, a partial mapping from the
|
|
* end of previous block will do.
|
|
*/
|
|
irl = *prl - na->rl;
|
|
#if PARTIAL_RUNLIST_UPDATING
|
|
prevblock = pos >> cluster_size_bits;
|
|
if (prevblock)
|
|
prevblock--;
|
|
if (!NAttrBeingNonResident(na)
|
|
&& (NAttrDataAppending(na)
|
|
? ntfs_attr_map_partial_runlist(na,prevblock)
|
|
: ntfs_attr_map_whole_runlist(na))) {
|
|
#else
|
|
if (!NAttrBeingNonResident(na)
|
|
&& ntfs_attr_map_whole_runlist(na)) {
|
|
#endif
|
|
rl = (runlist_element*)NULL;
|
|
} else {
|
|
/*
|
|
* Mapping the runlist may cause its relocation,
|
|
* and relocation may be at the same place with
|
|
* relocated contents.
|
|
* Have to find the current run again when this
|
|
* happens.
|
|
*/
|
|
if ((na->rl != orl) || ((*prl)->lcn != olcn)) {
|
|
zrl = &na->rl[irl];
|
|
while (zrl->length && (zrl->lcn != olcn))
|
|
zrl++;
|
|
*prl = zrl;
|
|
}
|
|
if (!(*prl)->length) {
|
|
ntfs_log_error("Mapped run not found,"
|
|
" inode %lld lcn 0x%llx\n",
|
|
(long long)na->ni->mft_no,
|
|
(long long)olcn);
|
|
rl = (runlist_element*)NULL;
|
|
} else {
|
|
rl = ntfs_rl_extend(na,*prl,2);
|
|
na->unused_runs = 2;
|
|
}
|
|
}
|
|
*prl = rl;
|
|
if (rl && undecided) {
|
|
allocated = 0;
|
|
zrl = rl;
|
|
irl = 0;
|
|
while (zrl->length && (zrl->lcn >= 0)
|
|
&& (allocated < endblock)) {
|
|
allocated += zrl->length;
|
|
zrl++;
|
|
irl++;
|
|
}
|
|
}
|
|
}
|
|
/*
|
|
* compression block not fully allocated and followed
|
|
* by a hole : we must allocate in the hole.
|
|
*/
|
|
if (rl && (allocated < endblock) && (zrl->lcn == LCN_HOLE)) {
|
|
s64 xofs;
|
|
|
|
/*
|
|
* split the hole if not fully needed
|
|
*/
|
|
if ((allocated + zrl->length) > endblock) {
|
|
runlist_element *xrl;
|
|
|
|
*prl = ntfs_rl_extend(na,*prl,1);
|
|
if (*prl) {
|
|
/* beware : rl was reallocated */
|
|
rl = *prl;
|
|
zrl = &rl[irl];
|
|
na->unused_runs = 0;
|
|
xrl = zrl;
|
|
while (xrl->length) xrl++;
|
|
do {
|
|
xrl[1] = *xrl;
|
|
} while (xrl-- != zrl);
|
|
zrl->length = endblock - allocated;
|
|
zrl[1].length -= zrl->length;
|
|
zrl[1].vcn = zrl->vcn + zrl->length;
|
|
}
|
|
}
|
|
if (*prl) {
|
|
if (wasnonresident)
|
|
compressed_part = na->compression_block_clusters
|
|
- zrl->length;
|
|
xofs = 0;
|
|
if (ntfs_attr_fill_hole(na,
|
|
zrl->length << cluster_size_bits,
|
|
&xofs, &zrl, update_from))
|
|
compressed_part = -1;
|
|
else {
|
|
/* go back to initial cluster, now reallocated */
|
|
while (zrl->vcn > (pos >> cluster_size_bits))
|
|
zrl--;
|
|
*prl = zrl;
|
|
}
|
|
}
|
|
}
|
|
if (!*prl) {
|
|
ntfs_log_error("No elements to borrow from a hole\n");
|
|
compressed_part = -1;
|
|
} else
|
|
if ((*update_from == -1) || ((*prl)->vcn < *update_from))
|
|
*update_from = (*prl)->vcn;
|
|
return (compressed_part);
|
|
}
|
|
|
|
static int ntfs_attr_truncate_i(ntfs_attr *na, const s64 newsize,
|
|
hole_type holes);
|
|
|
|
/**
|
|
* 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 wasnonresident = FALSE;
|
|
BOOL compressed;
|
|
BOOL updatemap;
|
|
|
|
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);
|
|
na->unused_runs = 0; /* prepare overflow checks */
|
|
/*
|
|
* 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;
|
|
/*
|
|
* Compression is restricted to data streams and
|
|
* only ATTR_IS_COMPRESSED compression mode is supported.
|
|
*/
|
|
if (compressed
|
|
&& ((na->type != AT_DATA)
|
|
|| ((na->data_flags & ATTR_COMPRESSION_MASK)
|
|
!= ATTR_IS_COMPRESSED))) {
|
|
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;
|
|
/* identify whether this is appending to a non resident data attribute */
|
|
if ((na->type == AT_DATA) && (pos >= old_data_size)
|
|
&& NAttrNonResident(na))
|
|
NAttrSetDataAppending(na);
|
|
if (pos + count > na->data_size) {
|
|
#if PARTIAL_RUNLIST_UPDATING
|
|
/*
|
|
* When appending data, the attribute is first extended
|
|
* before being filled with data. This may cause the
|
|
* attribute to be made temporarily sparse, which
|
|
* implies reformating the inode and reorganizing the
|
|
* full runlist. To avoid unnecessary reorganization,
|
|
* we avoid sparse testing until the data is filled in.
|
|
*/
|
|
if (ntfs_attr_truncate_i(na, pos + count,
|
|
(NAttrDataAppending(na) ?
|
|
HOLES_DELAY : HOLES_OK))) {
|
|
ntfs_log_perror("Failed to enlarge attribute");
|
|
goto errno_set;
|
|
}
|
|
/*
|
|
* If we avoided updating the runlist, we must be sure
|
|
* to cancel the enlargement and put back the runlist to
|
|
* a clean state if we get into some error.
|
|
*/
|
|
if (NAttrDataAppending(na))
|
|
need_to.undo_data_size = 1;
|
|
#else
|
|
if (ntfs_attr_truncate_i(na, pos + count, HOLES_OK)) {
|
|
ntfs_log_perror("Failed to enlarge attribute");
|
|
goto errno_set;
|
|
}
|
|
#endif
|
|
/* 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 = (pos | (na->compression_block_size - 1)) + 1 - 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 PARTIAL_RUNLIST_UPDATING
|
|
/*
|
|
* When appending, we only need to map the end of the runlist,
|
|
* starting at the last previously allocated run, so that
|
|
* we are able a new one to it.
|
|
* However, for compressed file, we need the full compression
|
|
* block, which may be split in several extents.
|
|
*/
|
|
if (NAttrDataAppending(na)) {
|
|
VCN block_begin = pos >> vol->cluster_size_bits;
|
|
|
|
if (compressed)
|
|
block_begin &= -na->compression_block_clusters;
|
|
if (block_begin)
|
|
block_begin--;
|
|
if (ntfs_attr_map_partial_runlist(na, block_begin))
|
|
goto err_out;
|
|
if ((update_from == -1) || (block_begin < update_from))
|
|
update_from = block_begin;
|
|
} else
|
|
#endif
|
|
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,na->rl,2);
|
|
if (!na->rl)
|
|
goto err_out;
|
|
na->unused_runs = 2;
|
|
}
|
|
/* 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) {
|
|
na->data_size = pos + count;
|
|
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;
|
|
if ((compressed || NAttrSparse(na))
|
|
&& NAttrNonResident(na))
|
|
na->ni->allocated_size = na->compressed_size;
|
|
else
|
|
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;
|
|
}
|
|
/*
|
|
* 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)
|
|
/*
|
|
* the needed block is in a hole smaller
|
|
* than the compression block : we can use
|
|
* it fully
|
|
*/
|
|
compressed_part
|
|
= na->compression_block_clusters
|
|
- rl->length;
|
|
else {
|
|
/*
|
|
* the needed block is in a hole bigger
|
|
* than the compression block : we must
|
|
* split the hole and use it partially
|
|
*/
|
|
compressed_part = split_compressed_hole(na,
|
|
&rl, pos, count, &update_from);
|
|
}
|
|
} else {
|
|
if (rl->lcn >= 0) {
|
|
/*
|
|
* the needed block contains data, make
|
|
* sure the full compression block is
|
|
* allocated. Borrow from hole if needed
|
|
*/
|
|
compressed_part = borrow_from_hole(na,
|
|
&rl, pos, count, &update_from,
|
|
wasnonresident);
|
|
}
|
|
}
|
|
|
|
if (compressed_part < 0)
|
|
goto err_out;
|
|
|
|
/* just making non-resident, so not yet compressed */
|
|
if (NAttrBeingNonResident(na)
|
|
&& (compressed_part < na->compression_block_clusters))
|
|
compressed_part = 0;
|
|
}
|
|
ofs = pos - (rl->vcn << vol->cluster_size_bits);
|
|
/*
|
|
* 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) {
|
|
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, cb, compressed_part,
|
|
&update_from);
|
|
} 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,
|
|
&update_from);
|
|
} 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.
|
|
* For a compressed file, we try to make a partial update
|
|
* of the mapping list. This makes a difference only if
|
|
* inode extents were needed.
|
|
*/
|
|
#if PARTIAL_RUNLIST_UPDATING
|
|
updatemap = NAttrFullyMapped(na) || NAttrDataAppending(na);
|
|
#else
|
|
updatemap = (compressed
|
|
? NAttrFullyMapped(na) != 0 : update_from != -1);
|
|
#endif
|
|
if (updatemap) {
|
|
if (ntfs_attr_update_mapping_pairs(na,
|
|
(update_from < 0 ? 0 : update_from))) {
|
|
/*
|
|
* FIXME: trying to recover by goto rl_err_out;
|
|
* could cause driver hang by infinite looping.
|
|
*/
|
|
total = -1;
|
|
goto out;
|
|
}
|
|
if (!wasnonresident)
|
|
NAttrClearBeingNonResident(na);
|
|
NAttrClearDataAppending(na);
|
|
}
|
|
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. */
|
|
updatemap = (compressed
|
|
? NAttrFullyMapped(na) != 0 : update_from != -1);
|
|
if (updatemap)
|
|
ntfs_attr_update_mapping_pairs(na, 0);
|
|
/* Restore original data_size if needed. */
|
|
if (need_to.undo_data_size
|
|
&& ntfs_attr_truncate_i(na, old_data_size, HOLES_OK))
|
|
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;
|
|
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;
|
|
na->unused_runs = 0;
|
|
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;
|
|
|
|
/* safety check : no recursion on close */
|
|
if (NAttrComprClosing(na)) {
|
|
errno = EIO;
|
|
ntfs_log_error("Bad ntfs_attr_pclose"
|
|
" recursion on inode %lld\n",
|
|
(long long)na->ni->mft_no);
|
|
goto out;
|
|
}
|
|
NAttrSetComprClosing(na);
|
|
/*
|
|
* For a compressed attribute, we must be sure there are two
|
|
* available entries, so reserve them before it gets too late.
|
|
*/
|
|
if (ntfs_attr_map_whole_runlist(na))
|
|
goto err_out;
|
|
na->rl = ntfs_rl_extend(na,na->rl,2);
|
|
if (!na->rl)
|
|
goto err_out;
|
|
na->unused_runs = 2;
|
|
/* 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) {
|
|
runlist_element *xrl;
|
|
|
|
xrl = rl;
|
|
do {
|
|
xrl++;
|
|
} while (xrl->lcn >= 0);
|
|
compressed_part = (-xrl->length)
|
|
& (na->compression_block_clusters - 1);
|
|
} 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) {
|
|
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 (update_from < 0) update_from = 0;
|
|
if (!NVolReadOnly(vol)) {
|
|
failed = ntfs_compressed_close(na, rl, ofs, &update_from);
|
|
#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->compressed_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 (NAttrFullyMapped(na))
|
|
if (ntfs_attr_update_mapping_pairs(na, update_from)) {
|
|
/*
|
|
* FIXME: trying to recover by goto rl_err_out;
|
|
* could cause driver hang by infinite looping.
|
|
*/
|
|
ok = FALSE;
|
|
goto out;
|
|
}
|
|
out:
|
|
NAttrClearComprClosing(na);
|
|
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. */
|
|
if (NAttrFullyMapped(na))
|
|
ntfs_attr_update_mapping_pairs(na, 0);
|
|
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;
|
|
BOOL warn;
|
|
|
|
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;
|
|
/* log errors unless silenced */
|
|
warn = !na->ni || !na->ni->vol || !NVolNoFixupWarn(na->ni->vol);
|
|
for (end = (u8*)dst + br * bk_size; (u8*)dst < end; dst = (u8*)dst +
|
|
bk_size)
|
|
ntfs_mst_post_read_fixup_warn((NTFS_RECORD*)dst, bk_size, warn);
|
|
/* 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);
|
|
|
|
/* The $AttrDef generated by Windows specifies 2 as min_size for the
|
|
* volume name attribute, but in reality Windows sets it to 0 when
|
|
* clearing the volume name. If we want to be able to clear the volume
|
|
* name we must also accept 0 as min_size, despite the $AttrDef
|
|
* definition. */
|
|
if(type == AT_VOLUME_NAME)
|
|
min_size = 0;
|
|
|
|
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,
|
|
const ntfschar *name, u8 name_len, const 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,
|
|
const 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, const 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->major_ver >= 3)
|
|
&& NVolCompression(ni->vol)
|
|
&& (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_i(na, size, HOLES_OK) ||
|
|
(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, const 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) {
|
|
if ((a->flags & ATTR_COMPRESSION_MASK)
|
|
== ATTR_IS_COMPRESSED) {
|
|
/* must allocate full compression blocks */
|
|
new_allocated_size = ((new_allocated_size - 1)
|
|
| ((1L << (STANDARD_COMPRESSION_UNIT
|
|
+ vol->cluster_size_bits)) - 1)) + 1;
|
|
}
|
|
/* 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);
|
|
NAttrSetBeingNonResident(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);
|
|
NAttrClearFullyMapped(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.
|
|
* Can also be used to force an attribute non-resident. In this case, the
|
|
* size cannot be changed.
|
|
*
|
|
* 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,
|
|
hole_type holes)
|
|
{
|
|
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) && (holes != HOLES_NONRES)) {
|
|
/* 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;
|
|
if (((na->data_flags & ATTR_COMPRESSION_MASK)
|
|
|| NAttrSparse(na))
|
|
&& NAttrNonResident(na))
|
|
na->ni->allocated_size
|
|
= na->compressed_size;
|
|
else
|
|
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);
|
|
/*
|
|
* do not truncate when forcing non-resident, this
|
|
* could cause the attribute to be made resident again,
|
|
* so size changes are not allowed.
|
|
*/
|
|
if (holes == HOLES_NONRES) {
|
|
ret = 0;
|
|
if (newsize != na->data_size) {
|
|
ntfs_log_error("Cannot change size when"
|
|
" forcing non-resident\n");
|
|
errno = EIO;
|
|
ret = STATUS_ERROR;
|
|
}
|
|
return (ret);
|
|
}
|
|
/* Resize non-resident attribute */
|
|
return ntfs_attr_truncate_i(na, newsize, holes);
|
|
} 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;
|
|
}
|
|
if ((tna->type == AT_DATA) && !tna->name_len) {
|
|
/*
|
|
* If we had to make the unnamed data attribute
|
|
* non-resident, propagate its new allocated size
|
|
* to all name attributes and directory indexes
|
|
*/
|
|
tna->ni->allocated_size = tna->allocated_size;
|
|
NInoFileNameSetDirty(tna->ni);
|
|
}
|
|
if (((tna->data_flags & ATTR_COMPRESSION_MASK)
|
|
== ATTR_IS_COMPRESSED)
|
|
&& ntfs_attr_pclose(tna)) {
|
|
err = errno;
|
|
ntfs_attr_close(tna);
|
|
goto put_err_out;
|
|
}
|
|
ntfs_inode_mark_dirty(tna->ni);
|
|
ntfs_attr_close(tna);
|
|
ntfs_attr_put_search_ctx(ctx);
|
|
return ntfs_resident_attr_resize_i(na, newsize, holes);
|
|
}
|
|
/* 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_i(na, newsize, holes);
|
|
}
|
|
|
|
/*
|
|
* 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_i(na, newsize, holes);
|
|
}
|
|
/* 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_i(na, newsize, holes);
|
|
|
|
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, HOLES_OK);
|
|
ntfs_log_leave("\n");
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Force an attribute to be made non-resident without
|
|
* changing its size.
|
|
*
|
|
* This is particularly needed when the attribute has no data,
|
|
* as the non-resident variant requires more space in the MFT
|
|
* record, and may imply expelling some other attribute.
|
|
*
|
|
* As a consequence the existing ntfs_attr_search_ctx's have to
|
|
* be closed or reinitialized.
|
|
*
|
|
* returns 0 if successful,
|
|
* < 0 if failed, with errno telling why
|
|
*/
|
|
|
|
int ntfs_attr_force_non_resident(ntfs_attr *na)
|
|
{
|
|
int res;
|
|
|
|
res = ntfs_resident_attr_resize_i(na, na->data_size, HOLES_NONRES);
|
|
if (!res && !NAttrNonResident(na)) {
|
|
res = -1;
|
|
errno = EIO;
|
|
ntfs_log_error("Failed to force non-resident\n");
|
|
}
|
|
return (res);
|
|
}
|
|
|
|
/**
|
|
* 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->major_ver >= 3)
|
|
&& NVolCompression(na->ni->vol)
|
|
&& (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) {
|
|
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);
|
|
NAttrClearFullyMapped(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,
|
|
hole_type holes, 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, unless this is an intermediate state */
|
|
if (holes == HOLES_DELAY)
|
|
sparse = (a->flags & ATTR_IS_SPARSE) != const_cpu_to_le16(0);
|
|
else {
|
|
sparse = ntfs_rl_sparse(na->rl);
|
|
if (sparse == -1) {
|
|
errno = EIO;
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
/* Check whether attribute becomes sparse, unless check is delayed. */
|
|
if ((holes != HOLES_DELAY)
|
|
&& 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;
|
|
na->data_flags = a->flags;
|
|
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;
|
|
na->data_flags = a->flags;
|
|
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 (NAttrFullyMapped(na)
|
|
&& (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->data_flags & ATTR_COMPRESSION_MASK))
|
|
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,
|
|
hole_type holes)
|
|
{
|
|
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;
|
|
BOOL first_updated = FALSE;
|
|
|
|
retry:
|
|
if (!na || !na->rl) {
|
|
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 PARTIAL_RUNLIST_UPDATING
|
|
/*
|
|
* For a file just been made sparse, we will have
|
|
* to reformat the first extent, so be sure the
|
|
* runlist is fully mapped and fully processed.
|
|
* Same if the file was sparse and is not any more.
|
|
* Note : not needed if the full runlist is to be processed
|
|
*/
|
|
if ((holes != HOLES_DELAY)
|
|
&& (!NAttrFullyMapped(na) || from_vcn)
|
|
&& !(na->data_flags & ATTR_IS_COMPRESSED)) {
|
|
BOOL changed;
|
|
|
|
if (!(na->data_flags & ATTR_IS_SPARSE)) {
|
|
int sparse = 0;
|
|
runlist_element *xrl;
|
|
|
|
/*
|
|
* If attribute was not sparse, we only
|
|
* have to check whether there is a hole
|
|
* in the updated region.
|
|
*/
|
|
for (xrl = na->rl; xrl->length; xrl++) {
|
|
if (xrl->lcn < 0) {
|
|
if (xrl->lcn == LCN_HOLE) {
|
|
sparse = 1;
|
|
break;
|
|
}
|
|
if (xrl->lcn != LCN_RL_NOT_MAPPED) {
|
|
sparse = -1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (sparse < 0) {
|
|
ntfs_log_error("Could not check whether sparse\n");
|
|
errno = EIO;
|
|
return (-1);
|
|
}
|
|
changed = sparse > 0;
|
|
} else {
|
|
/*
|
|
* If attribute was sparse, the compressed
|
|
* size has been maintained, and it gives
|
|
* and easy way to check whether the
|
|
* attribute is still sparse.
|
|
*/
|
|
changed = (((na->data_size - 1)
|
|
| (na->ni->vol->cluster_size - 1)) + 1)
|
|
== na->compressed_size;
|
|
}
|
|
if (changed) {
|
|
if (ntfs_attr_map_whole_runlist(na)) {
|
|
ntfs_log_error("Could not map whole for sparse change\n");
|
|
errno = EIO;
|
|
return (-1);
|
|
}
|
|
from_vcn = 0;
|
|
}
|
|
}
|
|
#endif
|
|
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 (!a->lowest_vcn)
|
|
first_updated = TRUE;
|
|
/*
|
|
* 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, holes, 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;
|
|
}
|
|
/*
|
|
* If the base extent was skipped in the above process,
|
|
* we still may have to update the sizes.
|
|
*/
|
|
if (!first_updated) {
|
|
le16 spcomp;
|
|
|
|
ntfs_attr_reinit_search_ctx(ctx);
|
|
if (!ntfs_attr_lookup(na->type, na->name, na->name_len,
|
|
CASE_SENSITIVE, 0, NULL, 0, ctx)) {
|
|
a = ctx->attr;
|
|
a->allocated_size = cpu_to_sle64(na->allocated_size);
|
|
spcomp = na->data_flags
|
|
& (ATTR_IS_COMPRESSED | ATTR_IS_SPARSE);
|
|
if (spcomp)
|
|
a->compressed_size = cpu_to_sle64(na->compressed_size);
|
|
if ((na->type == AT_DATA) && (na->name == AT_UNNAMED)) {
|
|
na->ni->allocated_size
|
|
= (spcomp
|
|
? na->compressed_size
|
|
: na->allocated_size);
|
|
NInoFileNameSetDirty(na->ni);
|
|
}
|
|
} else {
|
|
ntfs_log_error("Failed to update sizes in base extent\n");
|
|
goto put_err_out;
|
|
}
|
|
}
|
|
|
|
/* Deallocate not used attribute extents and return with success. */
|
|
if (finished_build) {
|
|
ntfs_attr_reinit_search_ctx(ctx);
|
|
ntfs_log_trace("Deallocate marked extents.\n");
|
|
while (!ntfs_attr_lookup(na->type, na->name, na->name_len,
|
|
CASE_SENSITIVE, 0, NULL, 0, ctx)) {
|
|
if (sle64_to_cpu(ctx->attr->highest_vcn) !=
|
|
NTFS_VCN_DELETE_MARK)
|
|
continue;
|
|
/* Remove unused attribute record. */
|
|
if (ntfs_attr_record_rm(ctx)) {
|
|
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, HOLES_OK);
|
|
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. */
|
|
if (na->data_flags & ATTR_COMPRESSION_MASK)
|
|
/*
|
|
* For compressed files we must keep full compressions blocks,
|
|
* but currently we do not decompress/recompress the last
|
|
* block to truncate the data, so we may leave more allocated
|
|
* clusters than really needed.
|
|
*/
|
|
first_free_vcn = (((newsize - 1)
|
|
| (na->compression_block_size - 1)) + 1)
|
|
>> vol->cluster_size_bits;
|
|
else
|
|
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,
|
|
hole_type holes)
|
|
{
|
|
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;
|
|
}
|
|
|
|
if (na->type == AT_DATA)
|
|
NAttrSetDataAppending(na);
|
|
/* 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 PARTIAL_RUNLIST_UPDATING
|
|
s64 start_update;
|
|
|
|
/*
|
|
* Update from the last previously allocated run,
|
|
* as we may have to expand an existing hole.
|
|
*/
|
|
start_update = na->allocated_size >> vol->cluster_size_bits;
|
|
if (start_update)
|
|
start_update--;
|
|
if (ntfs_attr_map_partial_runlist(na, start_update)) {
|
|
ntfs_log_perror("failed to map partial runlist");
|
|
return -1;
|
|
}
|
|
#else
|
|
if (ntfs_attr_map_whole_runlist(na)) {
|
|
ntfs_log_perror("ntfs_attr_map_whole_runlist failed");
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* 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)
|
|
&& (holes != HOLES_NO)) {
|
|
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;
|
|
#if PARTIAL_RUNLIST_UPDATING
|
|
/*
|
|
* Write mapping pairs for new runlist, unless this is
|
|
* a temporary state before appending data.
|
|
* If the update is not done, we must be sure to do
|
|
* it later, and to get to a clean state even on errors.
|
|
*/
|
|
if ((holes != HOLES_DELAY)
|
|
&& ntfs_attr_update_mapping_pairs_i(na, start_update,
|
|
holes)) {
|
|
#else
|
|
/* Write mapping pairs for new runlist. */
|
|
if (ntfs_attr_update_mapping_pairs(na, 0)) {
|
|
#endif
|
|
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,
|
|
hole_type holes)
|
|
{
|
|
int ret;
|
|
|
|
ntfs_log_enter("Entering\n");
|
|
ret = ntfs_non_resident_attr_expand_i(na, newsize, holes);
|
|
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
|
|
* @holes: how to create a hole if expanding
|
|
*
|
|
* 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.
|
|
*/
|
|
static int ntfs_attr_truncate_i(ntfs_attr *na, const s64 newsize,
|
|
hole_type holes)
|
|
{
|
|
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)) {
|
|
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 know 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 upsizing of compressed files
|
|
* unless being able to face the consequences !
|
|
*/
|
|
if (compressed && newsize && (newsize > na->data_size))
|
|
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,
|
|
holes);
|
|
else
|
|
ret = ntfs_non_resident_attr_shrink(na, fullsize);
|
|
} else
|
|
ret = ntfs_resident_attr_resize_i(na, newsize, holes);
|
|
out:
|
|
ntfs_log_leave("Return status %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Resize an attribute, creating a hole if relevant
|
|
*/
|
|
|
|
int ntfs_attr_truncate(ntfs_attr *na, const s64 newsize)
|
|
{
|
|
int r;
|
|
|
|
r = ntfs_attr_truncate_i(na, newsize, HOLES_OK);
|
|
NAttrClearDataAppending(na);
|
|
NAttrClearBeingNonResident(na);
|
|
return (r);
|
|
}
|
|
|
|
/*
|
|
* Resize an attribute, avoiding hole creation
|
|
*/
|
|
|
|
int ntfs_attr_truncate_solid(ntfs_attr *na, const s64 newsize)
|
|
{
|
|
return (ntfs_attr_truncate_i(na, newsize, HOLES_NO));
|
|
}
|
|
|
|
/*
|
|
* 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, HOLES_OK))
|
|
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, inode %lld attr 0x%lx",
|
|
(long long)ni->mft_no,(long)le32_to_cpu(type));
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* Read some data from a data attribute
|
|
*
|
|
* Returns the amount of data read, negative if there was an error
|
|
*/
|
|
|
|
int ntfs_attr_data_read(ntfs_inode *ni,
|
|
ntfschar *stream_name, int stream_name_len,
|
|
char *buf, size_t size, off_t offset)
|
|
{
|
|
ntfs_attr *na = NULL;
|
|
int res, total = 0;
|
|
|
|
na = ntfs_attr_open(ni, AT_DATA, stream_name, stream_name_len);
|
|
if (!na) {
|
|
res = -errno;
|
|
goto exit;
|
|
}
|
|
if ((size_t)offset < (size_t)na->data_size) {
|
|
if (offset + size > (size_t)na->data_size)
|
|
size = na->data_size - offset;
|
|
while (size) {
|
|
res = ntfs_attr_pread(na, offset, size, buf + total);
|
|
if ((off_t)res < (off_t)size)
|
|
ntfs_log_perror("ntfs_attr_pread partial read "
|
|
"(%lld : %lld <> %d)",
|
|
(long long)offset,
|
|
(long long)size, res);
|
|
if (res <= 0) {
|
|
res = -errno;
|
|
goto exit;
|
|
}
|
|
size -= res;
|
|
offset += res;
|
|
total += res;
|
|
}
|
|
}
|
|
res = total;
|
|
exit:
|
|
if (na)
|
|
ntfs_attr_close(na);
|
|
return res;
|
|
}
|
|
|
|
|
|
/*
|
|
* Write some data into a data attribute
|
|
*
|
|
* Returns the amount of data written, negative if there was an error
|
|
*/
|
|
|
|
int ntfs_attr_data_write(ntfs_inode *ni, ntfschar *stream_name,
|
|
int stream_name_len, const char *buf, size_t size, off_t offset)
|
|
{
|
|
ntfs_attr *na = NULL;
|
|
int res, total = 0;
|
|
|
|
na = ntfs_attr_open(ni, AT_DATA, stream_name, stream_name_len);
|
|
if (!na) {
|
|
res = -errno;
|
|
goto exit;
|
|
}
|
|
while (size) {
|
|
res = ntfs_attr_pwrite(na, offset, size, buf + total);
|
|
if (res < (s64)size)
|
|
ntfs_log_perror("ntfs_attr_pwrite partial write (%lld: "
|
|
"%lld <> %d)", (long long)offset,
|
|
(long long)size, res);
|
|
if (res <= 0) {
|
|
res = -errno;
|
|
goto exit;
|
|
}
|
|
size -= res;
|
|
offset += res;
|
|
total += res;
|
|
}
|
|
res = total;
|
|
exit:
|
|
if (na)
|
|
ntfs_attr_close(na);
|
|
return res;
|
|
}
|
|
|
|
|
|
int ntfs_attr_exist(ntfs_inode *ni, const ATTR_TYPES type, const 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;
|
|
}
|