usbloadergx/source/libntfs/attrib.c
giantpune 9e79c9d99b * remove little unused code
* code cleanup
2010-09-18 23:16:05 +00:00

6922 lines
232 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-2010 Jean-Pierre Andre
* Copyright (c) 2010 Erik Larsson
*
* This program/include file is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as published
* by the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program/include file is distributed in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program (in the main directory of the NTFS-3G
* distribution in the file COPYING); if not, write to the Free Software
* Foundation,Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef HAVE_STDIO_H
#include <stdio.h>
#endif
#ifdef HAVE_STRING_H
#include <string.h>
#endif
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#ifdef HAVE_ERRNO_H
#include <errno.h>
#endif
#ifdef HAVE_LIMITS_H
#include <limits.h>
#endif
#include "param.h"
#include "compat.h"
#include "attrib.h"
#include "attrlist.h"
#include "device.h"
#include "mft.h"
#include "debug.h"
#include "mst.h"
#include "volume.h"
#include "types.h"
#include "layout.h"
#include "inode.h"
#include "runlist.h"
#include "lcnalloc.h"
#include "dir.h"
#include "compress.h"
#include "bitmap.h"
#include "logging.h"
#include "misc.h"
#include "efs.h"
ntfschar AT_UNNAMED[] = { const_cpu_to_le16( '\0' ) };
ntfschar STREAM_SDS[] = { const_cpu_to_le16( '$' ),
const_cpu_to_le16( 'S' ),
const_cpu_to_le16( 'D' ),
const_cpu_to_le16( 'S' ),
const_cpu_to_le16( '\0' )
};
ntfschar TXF_DATA[] = { const_cpu_to_le16( '$' ),
const_cpu_to_le16( 'T' ),
const_cpu_to_le16( 'X' ),
const_cpu_to_le16( 'F' ),
const_cpu_to_le16( '_' ),
const_cpu_to_le16( 'D' ),
const_cpu_to_le16( 'A' ),
const_cpu_to_le16( 'T' ),
const_cpu_to_le16( 'A' ),
const_cpu_to_le16( '\0' )
};
static int NAttrFlag( ntfs_attr *na, FILE_ATTR_FLAGS flag )
{
if ( na->type == AT_DATA && na->name == AT_UNNAMED )
return ( na->ni->flags & flag );
return 0;
}
static void NAttrSetFlag( ntfs_attr *na, FILE_ATTR_FLAGS flag )
{
if ( na->type == AT_DATA && na->name == AT_UNNAMED )
na->ni->flags |= flag;
else
ntfs_log_trace( "Denied setting flag %d for not unnamed data "
"attribute\n", flag );
}
static void NAttrClearFlag( ntfs_attr *na, FILE_ATTR_FLAGS flag )
{
if ( na->type == AT_DATA && na->name == AT_UNNAMED )
na->ni->flags &= ~flag;
}
#define GenNAttrIno(func_name, flag) \
int NAttr##func_name(ntfs_attr *na) { return NAttrFlag (na, flag); } \
void NAttrSet##func_name(ntfs_attr *na) { NAttrSetFlag (na, flag); } \
void NAttrClear##func_name(ntfs_attr *na){ NAttrClearFlag(na, flag); }
GenNAttrIno( Compressed, FILE_ATTR_COMPRESSED )
GenNAttrIno( Encrypted, FILE_ATTR_ENCRYPTED )
GenNAttrIno( Sparse, FILE_ATTR_SPARSE_FILE )
/**
* ntfs_get_attribute_value_length - Find the length of an attribute
* @a:
*
* Description...
*
* Returns:
*/
s64 ntfs_get_attribute_value_length( const ATTR_RECORD *a )
{
if ( !a )
{
errno = EINVAL;
return 0;
}
errno = 0;
if ( a->non_resident )
return sle64_to_cpu( a->data_size );
return ( s64 )le32_to_cpu( a->value_length );
}
/**
* ntfs_get_attribute_value - Get a copy of an attribute
* @vol:
* @a:
* @b:
*
* Description...
*
* Returns:
*/
s64 ntfs_get_attribute_value( const ntfs_volume *vol,
const ATTR_RECORD *a, u8 *b )
{
runlist *rl;
s64 total, r;
int i;
/* Sanity checks. */
if ( !vol || !a || !b )
{
errno = EINVAL;
return 0;
}
/* Complex attribute? */
/*
* Ignore the flags in case they are not zero for an attribute list
* attribute. Windows does not complain about invalid flags and chkdsk
* does not detect or fix them so we need to cope with it, too.
*/
if ( a->type != AT_ATTRIBUTE_LIST && a->flags )
{
ntfs_log_error( "Non-zero (%04x) attribute flags. Cannot handle "
"this yet.\n", le16_to_cpu( a->flags ) );
errno = EOPNOTSUPP;
return 0;
}
if ( !a->non_resident )
{
/* Attribute is resident. */
/* Sanity check. */
if ( le32_to_cpu( a->value_length ) + le16_to_cpu( a->value_offset )
> le32_to_cpu( a->length ) )
{
return 0;
}
memcpy( b, ( const char* )a + le16_to_cpu( a->value_offset ),
le32_to_cpu( a->value_length ) );
errno = 0;
return ( s64 )le32_to_cpu( a->value_length );
}
/* Attribute is not resident. */
/* If no data, return 0. */
if ( !( a->data_size ) )
{
errno = 0;
return 0;
}
/*
* FIXME: What about attribute lists?!? (AIA)
*/
/* Decompress the mapping pairs array into a runlist. */
rl = ntfs_mapping_pairs_decompress( vol, a, NULL );
if ( !rl )
{
errno = EINVAL;
return 0;
}
/*
* FIXED: We were overflowing here in a nasty fashion when we
* reach the last cluster in the runlist as the buffer will
* only be big enough to hold data_size bytes while we are
* reading in allocated_size bytes which is usually larger
* than data_size, since the actual data is unlikely to have a
* size equal to a multiple of the cluster size!
* FIXED2: We were also overflowing here in the same fashion
* when the data_size was more than one run smaller than the
* allocated size which happens with Windows XP sometimes.
*/
/* Now load all clusters in the runlist into b. */
for ( i = 0, total = 0; rl[i].length; i++ )
{
if ( total + ( rl[i].length << vol->cluster_size_bits ) >=
sle64_to_cpu( a->data_size ) )
{
unsigned char *intbuf = NULL;
/*
* We have reached the last run so we were going to
* overflow when executing the ntfs_pread() which is
* BAAAAAAAD!
* Temporary fix:
* Allocate a new buffer with size:
* rl[i].length << vol->cluster_size_bits, do the
* read into our buffer, then memcpy the correct
* amount of data into the caller supplied buffer,
* free our buffer, and continue.
* We have reached the end of data size so we were
* going to overflow in the same fashion.
* Temporary fix: same as above.
*/
intbuf = ntfs_malloc( rl[i].length << vol->cluster_size_bits );
if ( !intbuf )
{
free( rl );
return 0;
}
/*
* FIXME: If compressed file: Only read if lcn != -1.
* Otherwise, we are dealing with a sparse run and we
* just memset the user buffer to 0 for the length of
* the run, which should be 16 (= compression unit
* size).
* FIXME: Really only when file is compressed, or can
* we have sparse runs in uncompressed files as well?
* - Yes we can, in sparse files! But not necessarily
* size of 16, just run length.
*/
r = ntfs_pread( vol->dev, rl[i].lcn <<
vol->cluster_size_bits, rl[i].length <<
vol->cluster_size_bits, intbuf );
if ( r != rl[i].length << vol->cluster_size_bits )
{
#define ESTR "Error reading attribute value"
if ( r == -1 )
ntfs_log_perror( ESTR );
else if ( r < rl[i].length <<
vol->cluster_size_bits )
{
ntfs_log_debug( ESTR ": Ran out of input data.\n" );
errno = EIO;
}
else
{
ntfs_log_debug( ESTR ": unknown error\n" );
errno = EIO;
}
#undef ESTR
free( rl );
free( intbuf );
return 0;
}
memcpy( b + total, intbuf, sle64_to_cpu( a->data_size ) -
total );
free( intbuf );
total = sle64_to_cpu( a->data_size );
break;
}
/*
* FIXME: If compressed file: Only read if lcn != -1.
* Otherwise, we are dealing with a sparse run and we just
* memset the user buffer to 0 for the length of the run, which
* should be 16 (= compression unit size).
* FIXME: Really only when file is compressed, or can
* we have sparse runs in uncompressed files as well?
* - Yes we can, in sparse files! But not necessarily size of
* 16, just run length.
*/
r = ntfs_pread( vol->dev, rl[i].lcn << vol->cluster_size_bits,
rl[i].length << vol->cluster_size_bits,
b + total );
if ( r != rl[i].length << vol->cluster_size_bits )
{
#define ESTR "Error reading attribute value"
if ( r == -1 )
ntfs_log_perror( ESTR );
else if ( r < rl[i].length << vol->cluster_size_bits )
{
ntfs_log_debug( ESTR ": Ran out of input data.\n" );
errno = EIO;
}
else
{
ntfs_log_debug( ESTR ": unknown error\n" );
errno = EIO;
}
#undef ESTR
free( rl );
return 0;
}
total += r;
}
free( rl );
return total;
}
/* Already cleaned up code below, but still look for FIXME:... */
/**
* __ntfs_attr_init - primary initialization of an ntfs attribute structure
* @na: ntfs attribute to initialize
* @ni: ntfs inode with which to initialize the ntfs attribute
* @type: attribute type
* @name: attribute name in little endian Unicode or NULL
* @name_len: length of attribute @name in Unicode characters (if @name given)
*
* Initialize the ntfs attribute @na with @ni, @type, @name, and @name_len.
*/
static void __ntfs_attr_init( ntfs_attr *na, ntfs_inode *ni,
const ATTR_TYPES type, ntfschar *name, const u32 name_len )
{
na->rl = NULL;
na->ni = ni;
na->type = type;
na->name = name;
if ( name )
na->name_len = name_len;
else
na->name_len = 0;
}
/**
* ntfs_attr_init - initialize an ntfs_attr with data sizes and status
* @na:
* @non_resident:
* @compressed:
* @encrypted:
* @sparse:
* @allocated_size:
* @data_size:
* @initialized_size:
* @compressed_size:
* @compression_unit:
*
* Final initialization for an ntfs attribute.
*/
void ntfs_attr_init( ntfs_attr *na, const BOOL non_resident,
const ATTR_FLAGS data_flags,
const BOOL encrypted, const BOOL sparse,
const s64 allocated_size, const s64 data_size,
const s64 initialized_size, const s64 compressed_size,
const u8 compression_unit )
{
if ( !NAttrInitialized( na ) )
{
na->data_flags = data_flags;
if ( non_resident )
NAttrSetNonResident( na );
if ( data_flags & ATTR_COMPRESSION_MASK )
NAttrSetCompressed( na );
if ( encrypted )
NAttrSetEncrypted( na );
if ( sparse )
NAttrSetSparse( na );
na->allocated_size = allocated_size;
na->data_size = data_size;
na->initialized_size = initialized_size;
if ( ( data_flags & ATTR_COMPRESSION_MASK ) || sparse )
{
ntfs_volume *vol = na->ni->vol;
na->compressed_size = compressed_size;
na->compression_block_clusters = 1 << compression_unit;
na->compression_block_size = 1 << ( compression_unit +
vol->cluster_size_bits );
na->compression_block_size_bits = ffs(
na->compression_block_size ) - 1;
}
NAttrSetInitialized( na );
}
}
/**
* ntfs_attr_open - open an ntfs attribute for access
* @ni: open ntfs inode in which the ntfs attribute resides
* @type: attribute type
* @name: attribute name in little endian Unicode or AT_UNNAMED or NULL
* @name_len: length of attribute @name in Unicode characters (if @name given)
*
* Allocate a new ntfs attribute structure, initialize it with @ni, @type,
* @name, and @name_len, then return it. Return NULL on error with
* errno set to the error code.
*
* If @name is AT_UNNAMED look specifically for an unnamed attribute. If you
* do not care whether the attribute is named or not set @name to NULL. In
* both those cases @name_len is not used at all.
*/
ntfs_attr *ntfs_attr_open( ntfs_inode *ni, const ATTR_TYPES type,
ntfschar *name, u32 name_len )
{
ntfs_attr_search_ctx *ctx;
ntfs_attr *na = NULL;
ntfschar *newname = NULL;
ATTR_RECORD *a;
BOOL cs;
ntfs_log_enter( "Entering for inode %lld, attr 0x%x.\n",
( unsigned long long )ni->mft_no, type );
if ( !ni || !ni->vol || !ni->mrec )
{
errno = EINVAL;
goto out;
}
na = ntfs_calloc( sizeof( ntfs_attr ) );
if ( !na )
goto out;
if ( name && name != AT_UNNAMED && name != NTFS_INDEX_I30 )
{
name = ntfs_ucsndup( name, name_len );
if ( !name )
goto err_out;
newname = name;
}
ctx = ntfs_attr_get_search_ctx( ni, NULL );
if ( !ctx )
goto err_out;
if ( ntfs_attr_lookup( type, name, name_len, 0, 0, NULL, 0, ctx ) )
goto put_err_out;
a = ctx->attr;
if ( !name )
{
if ( a->name_length )
{
name = ntfs_ucsndup( ( ntfschar* )( ( u8* )a + le16_to_cpu(
a->name_offset ) ), a->name_length );
if ( !name )
goto put_err_out;
newname = name;
name_len = a->name_length;
}
else
{
name = AT_UNNAMED;
name_len = 0;
}
}
__ntfs_attr_init( na, ni, type, name, name_len );
/*
* Wipe the flags in case they are not zero for an attribute list
* attribute. Windows does not complain about invalid flags and chkdsk
* does not detect or fix them so we need to cope with it, too.
*/
if ( type == AT_ATTRIBUTE_LIST )
a->flags = 0;
if ( ( type == AT_DATA ) && !a->initialized_size )
{
/*
* Define/redefine the compression state if stream is
* empty, based on the compression mark on parent
* directory (for unnamed data streams) or on current
* inode (for named data streams). The compression mark
* may change any time, the compression state can only
* change when stream is wiped out.
*
* 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 );
if ( na->type == AT_DATA && na->name == AT_UNNAMED &&
( ( !( a->flags & ATTR_IS_SPARSE ) != !NAttrSparse( na ) ) ||
( !( a->flags & ATTR_IS_ENCRYPTED ) != !NAttrEncrypted( na ) ) ) )
{
errno = EIO;
ntfs_log_perror( "Inode %lld has corrupt attribute flags "
"(0x%x <> 0x%x)", ( unsigned long long )ni->mft_no,
a->flags, na->ni->flags );
goto put_err_out;
}
if ( a->non_resident )
{
if ( ( a->flags & ATTR_COMPRESSION_MASK )
&& !a->compression_unit )
{
errno = EIO;
ntfs_log_perror( "Compressed inode %lld attr 0x%x has "
"no compression unit",
( unsigned long long )ni->mft_no, type );
goto put_err_out;
}
ntfs_attr_init( na, TRUE, a->flags,
a->flags & ATTR_IS_ENCRYPTED,
a->flags & ATTR_IS_SPARSE,
sle64_to_cpu( a->allocated_size ),
sle64_to_cpu( a->data_size ),
sle64_to_cpu( a->initialized_size ),
cs ? sle64_to_cpu( a->compressed_size ) : 0,
cs ? a->compression_unit : 0 );
}
else
{
s64 l = le32_to_cpu( a->value_length );
ntfs_attr_init( na, FALSE, a->flags,
a->flags & ATTR_IS_ENCRYPTED,
a->flags & ATTR_IS_SPARSE, ( l + 7 ) & ~7, l, l,
cs ? ( l + 7 ) & ~7 : 0, 0 );
}
ntfs_attr_put_search_ctx( ctx );
out:
ntfs_log_leave( "\n" );
return na;
put_err_out:
ntfs_attr_put_search_ctx( ctx );
err_out:
free( newname );
free( na );
na = NULL;
goto out;
}
/**
* ntfs_attr_close - free an ntfs attribute structure
* @na: ntfs attribute structure to free
*
* Release all memory associated with the ntfs attribute @na and then release
* @na itself.
*/
void ntfs_attr_close( ntfs_attr *na )
{
if ( !na )
return;
if ( NAttrNonResident( na ) && na->rl )
free( na->rl );
/* Don't release if using an internal constant. */
if ( na->name != AT_UNNAMED && na->name != NTFS_INDEX_I30
&& na->name != STREAM_SDS )
free( na->name );
free( na );
}
/**
* ntfs_attr_map_runlist - map (a part of) a runlist of an ntfs attribute
* @na: ntfs attribute for which to map (part of) a runlist
* @vcn: map runlist part containing this vcn
*
* Map the part of a runlist containing the @vcn of the ntfs attribute @na.
*
* Return 0 on success and -1 on error with errno set to the error code.
*/
int ntfs_attr_map_runlist( ntfs_attr *na, VCN vcn )
{
LCN lcn;
ntfs_attr_search_ctx *ctx;
ntfs_log_trace( "Entering for inode 0x%llx, attr 0x%x, vcn 0x%llx.\n",
( unsigned long long )na->ni->mft_no, na->type, ( long long )vcn );
lcn = ntfs_rl_vcn_to_lcn( na->rl, vcn );
if ( lcn >= 0 || lcn == LCN_HOLE || lcn == LCN_ENOENT )
return 0;
ctx = ntfs_attr_get_search_ctx( na->ni, NULL );
if ( !ctx )
return -1;
/* Find the attribute in the mft record. */
if ( !ntfs_attr_lookup( na->type, na->name, na->name_len, CASE_SENSITIVE,
vcn, NULL, 0, ctx ) )
{
runlist_element *rl;
/* Decode the runlist. */
rl = ntfs_mapping_pairs_decompress( na->ni->vol, ctx->attr,
na->rl );
if ( rl )
{
na->rl = rl;
ntfs_attr_put_search_ctx( ctx );
return 0;
}
}
ntfs_attr_put_search_ctx( ctx );
return -1;
}
/**
* ntfs_attr_map_whole_runlist - map the whole runlist of an ntfs attribute
* @na: ntfs attribute for which to map the runlist
*
* Map the whole runlist of the ntfs attribute @na. For an attribute made up
* of only one attribute extent this is the same as calling
* ntfs_attr_map_runlist(na, 0) but for an attribute with multiple extents this
* will map the runlist fragments from each of the extents thus giving access
* to the entirety of the disk allocation of an attribute.
*
* Return 0 on success and -1 on error with errno set to the error code.
*/
int ntfs_attr_map_whole_runlist( ntfs_attr *na )
{
VCN next_vcn, last_vcn, highest_vcn;
ntfs_attr_search_ctx *ctx;
ntfs_volume *vol = na->ni->vol;
ATTR_RECORD *a;
int ret = -1;
ntfs_log_enter( "Entering for inode %llu, attr 0x%x.\n",
( unsigned long long )na->ni->mft_no, na->type );
/* 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 );
written = ntfs_rl_pwrite( vol, rli, ofsi, pos, size, buf );
if ( written <= 0 )
{
ntfs_log_perror( "Failed to zero space" );
goto err_free;
}
pos += written;
}
ret = 0;
err_free:
free( buf );
err_out:
return ret;
}
static int ntfs_attr_fill_hole( ntfs_attr *na, s64 count, s64 *ofs,
runlist_element **rl, VCN *update_from )
{
s64 to_write;
s64 need;
ntfs_volume *vol = na->ni->vol;
int eo, ret = -1;
runlist *rlc;
LCN lcn_seek_from = -1;
VCN cur_vcn, from_vcn;
to_write = min( count, ( ( *rl )->length << vol->cluster_size_bits ) - *ofs );
cur_vcn = ( *rl )->vcn;
from_vcn = ( *rl )->vcn + ( *ofs >> vol->cluster_size_bits );
ntfs_log_trace( "count: %lld, cur_vcn: %lld, from: %lld, to: %lld, ofs: "
"%lld\n", ( long long )count, ( long long )cur_vcn,
( long long )from_vcn, ( long long )to_write, ( long long )*ofs );
/* Map whole runlist to be able update mapping pairs later. */
if ( ntfs_attr_map_whole_runlist( na ) )
goto err_out;
/* Restore @*rl, it probably get lost during runlist mapping. */
*rl = ntfs_attr_find_vcn( na, cur_vcn );
if ( !*rl )
{
ntfs_log_error( "Failed to find run after mapping runlist. "
"Please report to %s.\n", NTFS_DEV_LIST );
errno = EIO;
goto err_out;
}
/* Search backwards to find the best lcn to start seek from. */
rlc = *rl;
while ( rlc->vcn )
{
rlc--;
if ( rlc->lcn >= 0 )
{
/*
* avoid fragmenting a compressed file
* Windows does not do that, and that may
* not be desirable for files which can
* be updated
*/
if ( na->data_flags & ATTR_COMPRESSION_MASK )
lcn_seek_from = rlc->lcn + rlc->length;
else
lcn_seek_from = rlc->lcn + ( from_vcn - rlc->vcn );
break;
}
}
if ( lcn_seek_from == -1 )
{
/* Backwards search failed, search forwards. */
rlc = *rl;
while ( rlc->length )
{
rlc++;
if ( rlc->lcn >= 0 )
{
lcn_seek_from = rlc->lcn - ( rlc->vcn - from_vcn );
if ( lcn_seek_from < -1 )
lcn_seek_from = -1;
break;
}
}
}
need = ( ( *ofs + to_write - 1 ) >> vol->cluster_size_bits )
+ 1 + ( *rl )->vcn - from_vcn;
if ( ( na->data_flags & ATTR_COMPRESSION_MASK )
&& ( need < na->compression_block_clusters ) )
{
/*
* for a compressed file, be sure to allocate the full
* 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;
/*
* Map the full runlist (needed to compute the
* compressed size), unless the runlist has not
* yet been created (data just made non-resident)
*/
irl = *prl - na->rl;
if ( !NAttrBeingNonResident( na )
&& ntfs_attr_map_whole_runlist( na ) )
{
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 );
}
/**
* 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;
if ( pos + count > na->data_size )
{
if ( ntfs_attr_truncate( na, pos + count ) )
{
ntfs_log_perror( "Failed to enlarge attribute" );
goto errno_set;
}
/* resizing may change the compression mode */
compressed = ( na->data_flags & ATTR_COMPRESSION_MASK )
!= const_cpu_to_le16( 0 );
need_to.undo_data_size = 1;
}
/*
* For compressed data, a single full block was allocated
* to deal with compression, possibly in a previous call.
* We are not able to process several blocks because
* some clusters are freed after compression and
* new allocations have to be done before proceeding,
* so truncate the requested count if needed (big buffers).
*/
if ( compressed )
{
fullcount = ( 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 ( 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.
*/
updatemap = ( compressed
? NAttrFullyMapped( na ) != 0 : update_from != -1 );
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;
}
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( na, old_data_size ) )
ntfs_log_perror( "Failed to restore data_size" );
errno = eo;
errno_set:
total = -1;
goto out;
}
int ntfs_attr_pclose( ntfs_attr *na )
{
s64 ofs;
int failed;
BOOL ok = TRUE;
VCN update_from = -1;
ntfs_volume *vol;
ntfs_attr_search_ctx *ctx = NULL;
runlist_element *rl;
int eo;
s64 hole;
int compressed_part;
BOOL compressed;
ntfs_log_enter( "Entering for inode 0x%llx, attr 0x%x.\n",
na->ni->mft_no, na->type );
if ( !na || !na->ni || !na->ni->vol )
{
errno = EINVAL;
ntfs_log_perror( "%s", __FUNCTION__ );
goto errno_set;
}
vol = na->ni->vol;
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 )
{
hole = rl->vcn + rl->length;
if ( rl->lcn != ( LCN )LCN_HOLE )
{
errno = EIO;
ntfs_log_perror( "%s: Unexpected LCN (%lld)",
__FUNCTION__,
( long long )rl->lcn );
goto rl_err_out;
}
if ( ntfs_attr_fill_hole( na, ( s64 )0, &ofs, &rl, &update_from ) )
goto err_out;
}
while ( rl->length
&& ( ofs >= ( rl->length << vol->cluster_size_bits ) ) )
{
ofs -= rl->length << vol->cluster_size_bits;
rl++;
}
retry:
failed = 0;
if ( 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:
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;
ntfs_log_trace( "Entering for inode 0x%llx, attr type 0x%x, pos 0x%llx.\n",
( unsigned long long )na->ni->mft_no, na->type,
( long long )pos );
if ( bk_cnt < 0 || bk_size % NTFS_BLOCK_SIZE )
{
errno = EINVAL;
ntfs_log_perror( "%s", __FUNCTION__ );
return -1;
}
br = ntfs_attr_pread( na, pos, bk_cnt * bk_size, dst );
if ( br <= 0 )
return br;
br /= bk_size;
for ( end = ( u8* )dst + br * bk_size; ( u8* )dst < end; dst = ( u8* )dst +
bk_size )
ntfs_mst_post_read_fixup( ( NTFS_RECORD* )dst, bk_size );
/* Finally, return the number of blocks read. */
return br;
}
/**
* ntfs_attr_mst_pwrite - multi sector transfer protected ntfs attribute write
* @na: multi sector transfer protected ntfs attribute to write to
* @pos: position in the attribute to write to
* @bk_cnt: number of mst protected blocks to write
* @bk_size: size of each mst protected block in bytes
* @src: data buffer to write to disk
*
* This function will write @bk_cnt blocks of size @bk_size bytes each from
* data buffer @b to multi sector transfer (mst) protected ntfs attribute @na
* at position @pos.
*
* On success, return the number of successfully written blocks. If this number
* is lower than @bk_cnt this means that an error was encountered during the
* write so that the write is partial. 0 means nothing was written (also
* return 0 when @bk_cnt or @bk_size are 0).
*
* On error and nothing has been written, return -1 with errno set
* appropriately to the return code of ntfs_attr_pwrite(), or to EINVAL in case
* of invalid arguments.
*
* NOTE: We mst protect the data, write it, then mst deprotect it using a quick
* deprotect algorithm (no checking). This saves us from making a copy before
* the write and at the same time causes the usn to be incremented in the
* buffer. This conceptually fits in better with the idea that cached data is
* always deprotected and protection is performed when the data is actually
* going to hit the disk and the cache is immediately deprotected again
* simulating an mst read on the written data. This way cache coherency is
* achieved.
*/
s64 ntfs_attr_mst_pwrite( ntfs_attr *na, const s64 pos, s64 bk_cnt,
const u32 bk_size, void *src )
{
s64 written, i;
ntfs_log_trace( "Entering for inode 0x%llx, attr type 0x%x, pos 0x%llx.\n",
( unsigned long long )na->ni->mft_no, na->type,
( long long )pos );
if ( bk_cnt < 0 || bk_size % NTFS_BLOCK_SIZE )
{
errno = EINVAL;
return -1;
}
if ( !bk_cnt )
return 0;
/* Prepare data for writing. */
for ( i = 0; i < bk_cnt; ++i )
{
int err;
err = ntfs_mst_pre_write_fixup( ( NTFS_RECORD* )
( ( u8* )src + i * bk_size ), bk_size );
if ( err < 0 )
{
/* Abort write at this position. */
ntfs_log_perror( "%s #1", __FUNCTION__ );
if ( !i )
return err;
bk_cnt = i;
break;
}
}
/* Write the prepared data. */
written = ntfs_attr_pwrite( na, pos, bk_cnt * bk_size, src );
if ( written <= 0 )
{
ntfs_log_perror( "%s: written=%lld", __FUNCTION__,
( long long )written );
}
/* Quickly deprotect the data again. */
for ( i = 0; i < bk_cnt; ++i )
ntfs_mst_post_write_fixup( ( NTFS_RECORD* )( ( u8* )src + i *
bk_size ) );
if ( written <= 0 )
return written;
/* Finally, return the number of complete blocks written. */
return written / bk_size;
}
/**
* ntfs_attr_find - find (next) attribute in mft record
* @type: attribute type to find
* @name: attribute name to find (optional, i.e. NULL means don't care)
* @name_len: attribute name length (only needed if @name present)
* @ic: IGNORE_CASE or CASE_SENSITIVE (ignored if @name not present)
* @val: attribute value to find (optional, resident attributes only)
* @val_len: attribute value length
* @ctx: search context with mft record and attribute to search from
*
* You shouldn't need to call this function directly. Use lookup_attr() instead.
*
* ntfs_attr_find() takes a search context @ctx as parameter and searches the
* mft record specified by @ctx->mrec, beginning at @ctx->attr, for an
* attribute of @type, optionally @name and @val. If found, ntfs_attr_find()
* returns 0 and @ctx->attr will point to the found attribute.
*
* If not found, ntfs_attr_find() returns -1, with errno set to ENOENT and
* @ctx->attr will point to the attribute before which the attribute being
* searched for would need to be inserted if such an action were to be desired.
*
* On actual error, ntfs_attr_find() returns -1 with errno set to the error
* code but not to ENOENT. In this case @ctx->attr is undefined and in
* particular do not rely on it not changing.
*
* If @ctx->is_first is TRUE, the search begins with @ctx->attr itself. If it
* is FALSE, the search begins after @ctx->attr.
*
* If @type is AT_UNUSED, return the first found attribute, i.e. one can
* enumerate all attributes by setting @type to AT_UNUSED and then calling
* ntfs_attr_find() repeatedly until it returns -1 with errno set to ENOENT to
* indicate that there are no more entries. During the enumeration, each
* successful call of ntfs_attr_find() will return the next attribute in the
* mft record @ctx->mrec.
*
* If @type is AT_END, seek to the end and return -1 with errno set to ENOENT.
* AT_END is not a valid attribute, its length is zero for example, thus it is
* safer to return error instead of success in this case. This also allows us
* to interoperate cleanly with ntfs_external_attr_find().
*
* If @name is AT_UNNAMED search for an unnamed attribute. If @name is present
* but not AT_UNNAMED search for a named attribute matching @name. Otherwise,
* match both named and unnamed attributes.
*
* If @ic is IGNORE_CASE, the @name comparison is not case sensitive and
* @ctx->ntfs_ino must be set to the ntfs inode to which the mft record
* @ctx->mrec belongs. This is so we can get at the ntfs volume and hence at
* the upcase table. If @ic is CASE_SENSITIVE, the comparison is case
* sensitive. When @name is present, @name_len is the @name length in Unicode
* characters.
*
* If @name is not present (NULL), we assume that the unnamed attribute is
* being searched for.
*
* Finally, the resident attribute value @val is looked for, if present.
* If @val is not present (NULL), @val_len is ignored.
*
* ntfs_attr_find() only searches the specified mft record and it ignores the
* presence of an attribute list attribute (unless it is the one being searched
* for, obviously). If you need to take attribute lists into consideration, use
* ntfs_attr_lookup() instead (see below). This also means that you cannot use
* ntfs_attr_find() to search for extent records of non-resident attributes, as
* extents with lowest_vcn != 0 are usually described by the attribute list
* attribute only. - Note that it is possible that the first extent is only in
* the attribute list while the last extent is in the base mft record, so don't
* rely on being able to find the first extent in the base mft record.
*
* Warning: Never use @val when looking for attribute types which can be
* non-resident as this most likely will result in a crash!
*/
static int ntfs_attr_find( const ATTR_TYPES type, const ntfschar *name,
const u32 name_len, const IGNORE_CASE_BOOL ic,
const u8 *val, const u32 val_len, ntfs_attr_search_ctx *ctx )
{
ATTR_RECORD *a;
ntfs_volume *vol;
ntfschar *upcase;
u32 upcase_len;
ntfs_log_trace( "attribute type 0x%x.\n", type );
if ( ctx->ntfs_ino )
{
vol = ctx->ntfs_ino->vol;
upcase = vol->upcase;
upcase_len = vol->upcase_len;
}
else
{
if ( name && name != AT_UNNAMED )
{
errno = EINVAL;
ntfs_log_perror( "%s", __FUNCTION__ );
return -1;
}
vol = NULL;
upcase = NULL;
upcase_len = 0;
}
/*
* Iterate over attributes in mft record starting at @ctx->attr, or the
* attribute following that, if @ctx->is_first is TRUE.
*/
if ( ctx->is_first )
{
a = ctx->attr;
ctx->is_first = FALSE;
}
else
a = ( ATTR_RECORD* )( ( char* )ctx->attr +
le32_to_cpu( ctx->attr->length ) );
for ( ;; a = ( ATTR_RECORD* )( ( char* )a + le32_to_cpu( a->length ) ) )
{
if ( p2n( a ) < p2n( ctx->mrec ) || ( char* )a > ( char* )ctx->mrec +
le32_to_cpu( ctx->mrec->bytes_allocated ) )
break;
ctx->attr = a;
if ( ( ( type != AT_UNUSED ) && ( le32_to_cpu( a->type ) >
le32_to_cpu( type ) ) ) ||
( a->type == AT_END ) )
{
errno = ENOENT;
return -1;
}
if ( !a->length )
break;
/* If this is an enumeration return this attribute. */
if ( type == AT_UNUSED )
return 0;
if ( a->type != type )
continue;
/*
* If @name is AT_UNNAMED we want an unnamed attribute.
* If @name is present, compare the two names.
* Otherwise, match any attribute.
*/
if ( name == AT_UNNAMED )
{
/* The search failed if the found attribute is named. */
if ( a->name_length )
{
errno = ENOENT;
return -1;
}
}
else
{
register int rc;
if ( name && ( ( rc = ntfs_names_full_collate( name,
name_len, ( ntfschar* )( ( char* )a +
le16_to_cpu( a->name_offset ) ),
a->name_length, ic,
upcase, upcase_len ) ) ) )
{
/*
* If @name collates before a->name,
* there is no matching attribute.
*/
if ( rc < 0 )
{
errno = ENOENT;
return -1;
}
/* If the strings are not equal, continue search. */
continue;
}
}
/*
* The names match or @name not present and attribute is
* unnamed. If no @val specified, we have found the attribute
* and are done.
*/
if ( !val )
return 0;
/* @val is present; compare values. */
else
{
register int rc;
rc = memcmp( val, ( char* )a + le16_to_cpu( a->value_offset ),
min( val_len,
le32_to_cpu( a->value_length ) ) );
/*
* If @val collates before the current attribute's
* value, there is no matching attribute.
*/
if ( !rc )
{
register u32 avl;
avl = le32_to_cpu( a->value_length );
if ( val_len == avl )
return 0;
if ( val_len < avl )
{
errno = ENOENT;
return -1;
}
}
else if ( rc < 0 )
{
errno = ENOENT;
return -1;
}
}
}
errno = EIO;
ntfs_log_perror( "%s: Corrupt inode (%lld)", __FUNCTION__,
ctx->ntfs_ino ? ( long long )ctx->ntfs_ino->mft_no : -1 );
return -1;
}
void ntfs_attr_name_free( char **name )
{
if ( *name )
{
free( *name );
*name = NULL;
}
}
char *ntfs_attr_name_get( const ntfschar *uname, const int uname_len )
{
char *name = NULL;
int name_len;
name_len = ntfs_ucstombs( uname, uname_len, &name, 0 );
if ( name_len < 0 )
{
ntfs_log_perror( "ntfs_ucstombs" );
return NULL;
}
else if ( name_len > 0 )
return name;
ntfs_attr_name_free( &name );
return NULL;
}
/**
* ntfs_external_attr_find - find an attribute in the attribute list of an inode
* @type: attribute type to find
* @name: attribute name to find (optional, i.e. NULL means don't care)
* @name_len: attribute name length (only needed if @name present)
* @ic: IGNORE_CASE or CASE_SENSITIVE (ignored if @name not present)
* @lowest_vcn: lowest vcn to find (optional, non-resident attributes only)
* @val: attribute value to find (optional, resident attributes only)
* @val_len: attribute value length
* @ctx: search context with mft record and attribute to search from
*
* You shouldn't need to call this function directly. Use ntfs_attr_lookup()
* instead.
*
* Find an attribute by searching the attribute list for the corresponding
* attribute list entry. Having found the entry, map the mft record for read
* if the attribute is in a different mft record/inode, find the attribute in
* there and return it.
*
* If @type is AT_UNUSED, return the first found attribute, i.e. one can
* enumerate all attributes by setting @type to AT_UNUSED and then calling
* ntfs_external_attr_find() repeatedly until it returns -1 with errno set to
* ENOENT to indicate that there are no more entries. During the enumeration,
* each successful call of ntfs_external_attr_find() will return the next
* attribute described by the attribute list of the base mft record described
* by the search context @ctx.
*
* If @type is AT_END, seek to the end of the base mft record ignoring the
* attribute list completely and return -1 with errno set to ENOENT. AT_END is
* not a valid attribute, its length is zero for example, thus it is safer to
* return error instead of success in this case.
*
* If @name is AT_UNNAMED search for an unnamed attribute. If @name is present
* but not AT_UNNAMED search for a named attribute matching @name. Otherwise,
* match both named and unnamed attributes.
*
* On first search @ctx->ntfs_ino must be the inode of the base mft record and
* @ctx must have been obtained from a call to ntfs_attr_get_search_ctx().
* On subsequent calls, @ctx->ntfs_ino can be any extent inode, too
* (@ctx->base_ntfs_ino is then the base inode).
*
* After finishing with the attribute/mft record you need to call
* ntfs_attr_put_search_ctx() to cleanup the search context (unmapping any
* mapped extent inodes, etc).
*
* Return 0 if the search was successful and -1 if not, with errno set to the
* error code.
*
* On success, @ctx->attr is the found attribute, it is in mft record
* @ctx->mrec, and @ctx->al_entry is the attribute list entry for this
* attribute with @ctx->base_* being the base mft record to which @ctx->attr
* belongs.
*
* On error ENOENT, i.e. attribute not found, @ctx->attr is set to the
* attribute which collates just after the attribute being searched for in the
* base ntfs inode, i.e. if one wants to add the attribute to the mft record
* this is the correct place to insert it into, and if there is not enough
* space, the attribute should be placed in an extent mft record.
* @ctx->al_entry points to the position within @ctx->base_ntfs_ino->attr_list
* at which the new attribute's attribute list entry should be inserted. The
* other @ctx fields, base_ntfs_ino, base_mrec, and base_attr are set to NULL.
* The only exception to this is when @type is AT_END, in which case
* @ctx->al_entry is set to NULL also (see above).
*
* The following error codes are defined:
* ENOENT Attribute not found, not an error as such.
* EINVAL Invalid arguments.
* EIO I/O error or corrupt data structures found.
* ENOMEM Not enough memory to allocate necessary buffers.
*/
static int ntfs_external_attr_find( ATTR_TYPES type, const ntfschar *name,
const u32 name_len, const IGNORE_CASE_BOOL ic,
const VCN lowest_vcn, const u8 *val, const u32 val_len,
ntfs_attr_search_ctx *ctx )
{
ntfs_inode *base_ni, *ni;
ntfs_volume *vol;
ATTR_LIST_ENTRY *al_entry, *next_al_entry;
u8 *al_start, *al_end;
ATTR_RECORD *a;
ntfschar *al_name;
u32 al_name_len;
BOOL is_first_search = FALSE;
ni = ctx->ntfs_ino;
base_ni = ctx->base_ntfs_ino;
ntfs_log_trace( "Entering for inode %lld, attribute type 0x%x.\n",
( unsigned long long )ni->mft_no, type );
if ( !base_ni )
{
/* First call happens with the base mft record. */
base_ni = ctx->base_ntfs_ino = ctx->ntfs_ino;
ctx->base_mrec = ctx->mrec;
}
if ( ni == base_ni )
ctx->base_attr = ctx->attr;
if ( type == AT_END )
goto not_found;
vol = base_ni->vol;
al_start = base_ni->attr_list;
al_end = al_start + base_ni->attr_list_size;
if ( !ctx->al_entry )
{
ctx->al_entry = ( ATTR_LIST_ENTRY* )al_start;
is_first_search = TRUE;
}
/*
* Iterate over entries in attribute list starting at @ctx->al_entry,
* or the entry following that, if @ctx->is_first is TRUE.
*/
if ( ctx->is_first )
{
al_entry = ctx->al_entry;
ctx->is_first = FALSE;
/*
* If an enumeration and the first attribute is higher than
* the attribute list itself, need to return the attribute list
* attribute.
*/
if ( ( type == AT_UNUSED ) && is_first_search &&
le32_to_cpu( al_entry->type ) >
le32_to_cpu( AT_ATTRIBUTE_LIST ) )
goto find_attr_list_attr;
}
else
{
al_entry = ( ATTR_LIST_ENTRY* )( ( char* )ctx->al_entry +
le16_to_cpu( ctx->al_entry->length ) );
/*
* If this is an enumeration and the attribute list attribute
* is the next one in the enumeration sequence, just return the
* attribute list attribute from the base mft record as it is
* not listed in the attribute list itself.
*/
if ( ( type == AT_UNUSED ) && le32_to_cpu( ctx->al_entry->type ) <
le32_to_cpu( AT_ATTRIBUTE_LIST ) &&
le32_to_cpu( al_entry->type ) >
le32_to_cpu( AT_ATTRIBUTE_LIST ) )
{
int rc;
find_attr_list_attr:
/* Check for bogus calls. */
if ( name || name_len || val || val_len || lowest_vcn )
{
errno = EINVAL;
ntfs_log_perror( "%s", __FUNCTION__ );
return -1;
}
/* We want the base record. */
ctx->ntfs_ino = base_ni;
ctx->mrec = ctx->base_mrec;
ctx->is_first = TRUE;
/* Sanity checks are performed elsewhere. */
ctx->attr = ( ATTR_RECORD* )( ( u8* )ctx->mrec +
le16_to_cpu( ctx->mrec->attrs_offset ) );
/* Find the attribute list attribute. */
rc = ntfs_attr_find( AT_ATTRIBUTE_LIST, NULL, 0,
IGNORE_CASE, NULL, 0, ctx );
/*
* Setup the search context so the correct
* attribute is returned next time round.
*/
ctx->al_entry = al_entry;
ctx->is_first = TRUE;
/* Got it. Done. */
if ( !rc )
return 0;
/* Error! If other than not found return it. */
if ( errno != ENOENT )
return rc;
/* Not found?!? Absurd! */
errno = EIO;
ntfs_log_error( "Attribute list wasn't found" );
return -1;
}
}
for ( ;; al_entry = next_al_entry )
{
/* Out of bounds check. */
if ( ( u8* )al_entry < base_ni->attr_list ||
( u8* )al_entry > al_end )
break; /* Inode is corrupt. */
ctx->al_entry = al_entry;
/* Catch the end of the attribute list. */
if ( ( u8* )al_entry == al_end )
goto not_found;
if ( !al_entry->length )
break;
if ( ( u8* )al_entry + 6 > al_end || ( u8* )al_entry +
le16_to_cpu( al_entry->length ) > al_end )
break;
next_al_entry = ( ATTR_LIST_ENTRY* )( ( u8* )al_entry +
le16_to_cpu( al_entry->length ) );
if ( type != AT_UNUSED )
{
if ( le32_to_cpu( al_entry->type ) > le32_to_cpu( type ) )
goto not_found;
if ( type != al_entry->type )
continue;
}
al_name_len = al_entry->name_length;
al_name = ( ntfschar* )( ( u8* )al_entry + al_entry->name_offset );
/*
* If !@type we want the attribute represented by this
* attribute list entry.
*/
if ( type == AT_UNUSED )
goto is_enumeration;
/*
* If @name is AT_UNNAMED we want an unnamed attribute.
* If @name is present, compare the two names.
* Otherwise, match any attribute.
*/
if ( name == AT_UNNAMED )
{
if ( al_name_len )
goto not_found;
}
else
{
int rc;
if ( name && ( ( rc = ntfs_names_full_collate( name,
name_len, al_name, al_name_len, ic,
vol->upcase, vol->upcase_len ) ) ) )
{
/*
* If @name collates before al_name,
* there is no matching attribute.
*/
if ( rc < 0 )
goto not_found;
/* If the strings are not equal, continue search. */
continue;
}
}
/*
* The names match or @name not present and attribute is
* unnamed. Now check @lowest_vcn. Continue search if the
* next attribute list entry still fits @lowest_vcn. Otherwise
* we have reached the right one or the search has failed.
*/
if ( lowest_vcn && ( u8* )next_al_entry >= al_start &&
( u8* )next_al_entry + 6 < al_end &&
( u8* )next_al_entry + le16_to_cpu(
next_al_entry->length ) <= al_end &&
sle64_to_cpu( next_al_entry->lowest_vcn ) <=
lowest_vcn &&
next_al_entry->type == al_entry->type &&
next_al_entry->name_length == al_name_len &&
ntfs_names_are_equal( ( ntfschar* )( ( char* )
next_al_entry +
next_al_entry->name_offset ),
next_al_entry->name_length,
al_name, al_name_len, CASE_SENSITIVE,
vol->upcase, vol->upcase_len ) )
continue;
is_enumeration:
if ( MREF_LE( al_entry->mft_reference ) == ni->mft_no )
{
if ( MSEQNO_LE( al_entry->mft_reference ) !=
le16_to_cpu(
ni->mrec->sequence_number ) )
{
ntfs_log_error( "Found stale mft reference in "
"attribute list!\n" );
break;
}
}
else /* Mft references do not match. */
{
/* Do we want the base record back? */
if ( MREF_LE( al_entry->mft_reference ) ==
base_ni->mft_no )
{
ni = ctx->ntfs_ino = base_ni;
ctx->mrec = ctx->base_mrec;
}
else
{
/* We want an extent record. */
ni = ntfs_extent_inode_open( base_ni,
al_entry->mft_reference );
if ( !ni )
break;
ctx->ntfs_ino = ni;
ctx->mrec = ni->mrec;
}
}
a = ctx->attr = ( ATTR_RECORD* )( ( char* )ctx->mrec +
le16_to_cpu( ctx->mrec->attrs_offset ) );
/*
* ctx->ntfs_ino, ctx->mrec, and ctx->attr now point to the
* mft record containing the attribute represented by the
* current al_entry.
*
* We could call into ntfs_attr_find() to find the right
* attribute in this mft record but this would be less
* efficient and not quite accurate as ntfs_attr_find() ignores
* the attribute instance numbers for example which become
* important when one plays with attribute lists. Also, because
* a proper match has been found in the attribute list entry
* above, the comparison can now be optimized. So it is worth
* re-implementing a simplified ntfs_attr_find() here.
*
* Use a manual loop so we can still use break and continue
* with the same meanings as above.
*/
do_next_attr_loop:
if ( ( char* )a < ( char* )ctx->mrec || ( char* )a > ( char* )ctx->mrec +
le32_to_cpu( ctx->mrec->bytes_allocated ) )
break;
if ( a->type == AT_END )
continue;
if ( !a->length )
break;
if ( al_entry->instance != a->instance )
goto do_next_attr;
/*
* If the type and/or the name are/is mismatched between the
* attribute list entry and the attribute record, there is
* corruption so we break and return error EIO.
*/
if ( al_entry->type != a->type )
break;
if ( !ntfs_names_are_equal( ( ntfschar* )( ( char* )a +
le16_to_cpu( a->name_offset ) ),
a->name_length, al_name,
al_name_len, CASE_SENSITIVE,
vol->upcase, vol->upcase_len ) )
break;
ctx->attr = a;
/*
* If no @val specified or @val specified and it matches, we
* have found it! Also, if !@type, it is an enumeration, so we
* want the current attribute.
*/
if ( ( type == AT_UNUSED ) || !val || ( !a->non_resident &&
le32_to_cpu( a->value_length ) == val_len &&
!memcmp( ( char* )a + le16_to_cpu( a->value_offset ),
val, val_len ) ) )
{
return 0;
}
do_next_attr:
/* Proceed to the next attribute in the current mft record. */
a = ( ATTR_RECORD* )( ( char* )a + le32_to_cpu( a->length ) );
goto do_next_attr_loop;
}
if ( ni != base_ni )
{
ctx->ntfs_ino = base_ni;
ctx->mrec = ctx->base_mrec;
ctx->attr = ctx->base_attr;
}
errno = EIO;
ntfs_log_perror( "Inode is corrupt (%lld)", ( long long )base_ni->mft_no );
return -1;
not_found:
/*
* If we were looking for AT_END or we were enumerating and reached the
* end, we reset the search context @ctx and use ntfs_attr_find() to
* seek to the end of the base mft record.
*/
if ( type == AT_UNUSED || type == AT_END )
{
ntfs_attr_reinit_search_ctx( ctx );
return ntfs_attr_find( AT_END, name, name_len, ic, val, val_len,
ctx );
}
/*
* The attribute wasn't found. Before we return, we want to ensure
* @ctx->mrec and @ctx->attr indicate the position at which the
* attribute should be inserted in the base mft record. Since we also
* want to preserve @ctx->al_entry we cannot reinitialize the search
* context using ntfs_attr_reinit_search_ctx() as this would set
* @ctx->al_entry to NULL. Thus we do the necessary bits manually (see
* ntfs_attr_init_search_ctx() below). Note, we _only_ preserve
* @ctx->al_entry as the remaining fields (base_*) are identical to
* their non base_ counterparts and we cannot set @ctx->base_attr
* correctly yet as we do not know what @ctx->attr will be set to by
* the call to ntfs_attr_find() below.
*/
ctx->mrec = ctx->base_mrec;
ctx->attr = ( ATTR_RECORD* )( ( u8* )ctx->mrec +
le16_to_cpu( ctx->mrec->attrs_offset ) );
ctx->is_first = TRUE;
ctx->ntfs_ino = ctx->base_ntfs_ino;
ctx->base_ntfs_ino = NULL;
ctx->base_mrec = NULL;
ctx->base_attr = NULL;
/*
* In case there are multiple matches in the base mft record, need to
* keep enumerating until we get an attribute not found response (or
* another error), otherwise we would keep returning the same attribute
* over and over again and all programs using us for enumeration would
* lock up in a tight loop.
*/
{
int ret;
do
{
ret = ntfs_attr_find( type, name, name_len, ic, val,
val_len, ctx );
}
while ( !ret );
return ret;
}
}
/**
* ntfs_attr_lookup - find an attribute in an ntfs inode
* @type: attribute type to find
* @name: attribute name to find (optional, i.e. NULL means don't care)
* @name_len: attribute name length (only needed if @name present)
* @ic: IGNORE_CASE or CASE_SENSITIVE (ignored if @name not present)
* @lowest_vcn: lowest vcn to find (optional, non-resident attributes only)
* @val: attribute value to find (optional, resident attributes only)
* @val_len: attribute value length
* @ctx: search context with mft record and attribute to search from
*
* Find an attribute in an ntfs inode. On first search @ctx->ntfs_ino must
* be the base mft record and @ctx must have been obtained from a call to
* ntfs_attr_get_search_ctx().
*
* This function transparently handles attribute lists and @ctx is used to
* continue searches where they were left off at.
*
* If @type is AT_UNUSED, return the first found attribute, i.e. one can
* enumerate all attributes by setting @type to AT_UNUSED and then calling
* ntfs_attr_lookup() repeatedly until it returns -1 with errno set to ENOENT
* to indicate that there are no more entries. During the enumeration, each
* successful call of ntfs_attr_lookup() will return the next attribute, with
* the current attribute being described by the search context @ctx.
*
* If @type is AT_END, seek to the end of the base mft record ignoring the
* attribute list completely and return -1 with errno set to ENOENT. AT_END is
* not a valid attribute, its length is zero for example, thus it is safer to
* return error instead of success in this case. It should never be needed to
* do this, but we implement the functionality because it allows for simpler
* code inside ntfs_external_attr_find().
*
* If @name is AT_UNNAMED search for an unnamed attribute. If @name is present
* but not AT_UNNAMED search for a named attribute matching @name. Otherwise,
* match both named and unnamed attributes.
*
* After finishing with the attribute/mft record you need to call
* ntfs_attr_put_search_ctx() to cleanup the search context (unmapping any
* mapped extent inodes, etc).
*
* Return 0 if the search was successful and -1 if not, with errno set to the
* error code.
*
* On success, @ctx->attr is the found attribute, it is in mft record
* @ctx->mrec, and @ctx->al_entry is the attribute list entry for this
* attribute with @ctx->base_* being the base mft record to which @ctx->attr
* belongs. If no attribute list attribute is present @ctx->al_entry and
* @ctx->base_* are NULL.
*
* On error ENOENT, i.e. attribute not found, @ctx->attr is set to the
* attribute which collates just after the attribute being searched for in the
* base ntfs inode, i.e. if one wants to add the attribute to the mft record
* this is the correct place to insert it into, and if there is not enough
* space, the attribute should be placed in an extent mft record.
* @ctx->al_entry points to the position within @ctx->base_ntfs_ino->attr_list
* at which the new attribute's attribute list entry should be inserted. The
* other @ctx fields, base_ntfs_ino, base_mrec, and base_attr are set to NULL.
* The only exception to this is when @type is AT_END, in which case
* @ctx->al_entry is set to NULL also (see above).
*
*
* The following error codes are defined:
* ENOENT Attribute not found, not an error as such.
* EINVAL Invalid arguments.
* EIO I/O error or corrupt data structures found.
* ENOMEM Not enough memory to allocate necessary buffers.
*/
int ntfs_attr_lookup( const ATTR_TYPES type, const ntfschar *name,
const u32 name_len, const IGNORE_CASE_BOOL ic,
const VCN lowest_vcn, const u8 *val, const u32 val_len,
ntfs_attr_search_ctx *ctx )
{
ntfs_volume *vol;
ntfs_inode *base_ni;
int ret = -1;
ntfs_log_enter( "Entering for attribute type 0x%x\n", type );
if ( !ctx || !ctx->mrec || !ctx->attr || ( name && name != AT_UNNAMED &&
( !ctx->ntfs_ino || !( vol = ctx->ntfs_ino->vol ) ||
!vol->upcase || !vol->upcase_len ) ) )
{
errno = EINVAL;
ntfs_log_perror( "%s", __FUNCTION__ );
goto out;
}
if ( ctx->base_ntfs_ino )
base_ni = ctx->base_ntfs_ino;
else
base_ni = ctx->ntfs_ino;
if ( !base_ni || !NInoAttrList( base_ni ) || type == AT_ATTRIBUTE_LIST )
ret = ntfs_attr_find( type, name, name_len, ic, val, val_len, ctx );
else
ret = ntfs_external_attr_find( type, name, name_len, ic,
lowest_vcn, val, val_len, ctx );
out:
ntfs_log_leave( "\n" );
return ret;
}
/**
* ntfs_attr_position - find given or next attribute type in an ntfs inode
* @type: attribute type to start lookup
* @ctx: search context with mft record and attribute to search from
*
* Find an attribute type in an ntfs inode or the next attribute which is not
* the AT_END attribute. Please see more details at ntfs_attr_lookup.
*
* Return 0 if the search was successful and -1 if not, with errno set to the
* error code.
*
* The following error codes are defined:
* EINVAL Invalid arguments.
* EIO I/O error or corrupt data structures found.
* ENOMEM Not enough memory to allocate necessary buffers.
* ENOSPC No attribute was found after 'type', only AT_END.
*/
int ntfs_attr_position( const ATTR_TYPES type, ntfs_attr_search_ctx *ctx )
{
if ( ntfs_attr_lookup( type, NULL, 0, CASE_SENSITIVE, 0, NULL, 0, ctx ) )
{
if ( errno != ENOENT )
return -1;
if ( ctx->attr->type == AT_END )
{
errno = ENOSPC;
return -1;
}
}
return 0;
}
/**
* ntfs_attr_init_search_ctx - initialize an attribute search context
* @ctx: attribute search context to initialize
* @ni: ntfs inode with which to initialize the search context
* @mrec: mft record with which to initialize the search context
*
* Initialize the attribute search context @ctx with @ni and @mrec.
*/
static void ntfs_attr_init_search_ctx( ntfs_attr_search_ctx *ctx,
ntfs_inode *ni, MFT_RECORD *mrec )
{
if ( !mrec )
mrec = ni->mrec;
ctx->mrec = mrec;
/* Sanity checks are performed elsewhere. */
ctx->attr = ( ATTR_RECORD* )( ( u8* )mrec + le16_to_cpu( mrec->attrs_offset ) );
ctx->is_first = TRUE;
ctx->ntfs_ino = ni;
ctx->al_entry = NULL;
ctx->base_ntfs_ino = NULL;
ctx->base_mrec = NULL;
ctx->base_attr = NULL;
}
/**
* ntfs_attr_reinit_search_ctx - reinitialize an attribute search context
* @ctx: attribute search context to reinitialize
*
* Reinitialize the attribute search context @ctx.
*
* This is used when a search for a new attribute is being started to reset
* the search context to the beginning.
*/
void ntfs_attr_reinit_search_ctx( ntfs_attr_search_ctx *ctx )
{
if ( !ctx->base_ntfs_ino )
{
/* No attribute list. */
ctx->is_first = TRUE;
/* Sanity checks are performed elsewhere. */
ctx->attr = ( ATTR_RECORD* )( ( u8* )ctx->mrec +
le16_to_cpu( ctx->mrec->attrs_offset ) );
/*
* This needs resetting due to ntfs_external_attr_find() which
* can leave it set despite having zeroed ctx->base_ntfs_ino.
*/
ctx->al_entry = NULL;
return;
} /* Attribute list. */
ntfs_attr_init_search_ctx( ctx, ctx->base_ntfs_ino, ctx->base_mrec );
return;
}
/**
* ntfs_attr_get_search_ctx - allocate/initialize a new attribute search context
* @ni: ntfs inode with which to initialize the search context
* @mrec: mft record with which to initialize the search context
*
* Allocate a new attribute search context, initialize it with @ni and @mrec,
* and return it. Return NULL on error with errno set.
*
* @mrec can be NULL, in which case the mft record is taken from @ni.
*
* Note: For low level utilities which know what they are doing we allow @ni to
* be NULL and @mrec to be set. Do NOT do this unless you understand the
* implications!!! For example it is no longer safe to call ntfs_attr_lookup().
*/
ntfs_attr_search_ctx *ntfs_attr_get_search_ctx( ntfs_inode *ni, MFT_RECORD *mrec )
{
ntfs_attr_search_ctx *ctx;
if ( !ni && !mrec )
{
errno = EINVAL;
ntfs_log_perror( "NULL arguments" );
return NULL;
}
ctx = ntfs_malloc( sizeof( ntfs_attr_search_ctx ) );
if ( ctx )
ntfs_attr_init_search_ctx( ctx, ni, mrec );
return ctx;
}
/**
* ntfs_attr_put_search_ctx - release an attribute search context
* @ctx: attribute search context to free
*
* Release the attribute search context @ctx.
*/
void ntfs_attr_put_search_ctx( ntfs_attr_search_ctx *ctx )
{
// NOTE: save errno if it could change and function stays void!
free( ctx );
}
/**
* ntfs_attr_find_in_attrdef - find an attribute in the $AttrDef system file
* @vol: ntfs volume to which the attribute belongs
* @type: attribute type which to find
*
* Search for the attribute definition record corresponding to the attribute
* @type in the $AttrDef system file.
*
* Return the attribute type definition record if found and NULL if not found
* or an error occurred. On error the error code is stored in errno. The
* following error codes are defined:
* ENOENT - The attribute @type is not specified in $AttrDef.
* EINVAL - Invalid parameters (e.g. @vol is not valid).
*/
ATTR_DEF *ntfs_attr_find_in_attrdef( const ntfs_volume *vol,
const ATTR_TYPES type )
{
ATTR_DEF *ad;
if ( !vol || !vol->attrdef || !type )
{
errno = EINVAL;
ntfs_log_perror( "%s: type=%d", __FUNCTION__, type );
return NULL;
}
for ( ad = vol->attrdef; ( u8* )ad - ( u8* )vol->attrdef <
vol->attrdef_len && ad->type; ++ad )
{
/* We haven't found it yet, carry on searching. */
if ( le32_to_cpu( ad->type ) < le32_to_cpu( type ) )
continue;
/* We found the attribute; return it. */
if ( ad->type == type )
return ad;
/* We have gone too far already. No point in continuing. */
break;
}
errno = ENOENT;
ntfs_log_perror( "%s: type=%d", __FUNCTION__, type );
return NULL;
}
/**
* ntfs_attr_size_bounds_check - check a size of an attribute type for validity
* @vol: ntfs volume to which the attribute belongs
* @type: attribute type which to check
* @size: size which to check
*
* Check whether the @size in bytes is valid for an attribute of @type on the
* ntfs volume @vol. This information is obtained from $AttrDef system file.
*
* Return 0 if valid and -1 if not valid or an error occurred. On error the
* error code is stored in errno. The following error codes are defined:
* ERANGE - @size is not valid for the attribute @type.
* ENOENT - The attribute @type is not specified in $AttrDef.
* EINVAL - Invalid parameters (e.g. @size is < 0 or @vol is not valid).
*/
int ntfs_attr_size_bounds_check( const ntfs_volume *vol, const ATTR_TYPES type,
const s64 size )
{
ATTR_DEF *ad;
s64 min_size, max_size;
if ( size < 0 )
{
errno = EINVAL;
ntfs_log_perror( "%s: size=%lld", __FUNCTION__,
( long long )size );
return -1;
}
/*
* $ATTRIBUTE_LIST shouldn't be greater than 0x40000, otherwise
* Windows would crash. This is not listed in the AttrDef.
*/
if ( type == AT_ATTRIBUTE_LIST && size > 0x40000 )
{
errno = ERANGE;
ntfs_log_perror( "Too large attrlist (%lld)", ( long long )size );
return -1;
}
ad = ntfs_attr_find_in_attrdef( vol, type );
if ( !ad )
return -1;
min_size = sle64_to_cpu( ad->min_size );
max_size = sle64_to_cpu( ad->max_size );
if ( ( min_size && ( size < min_size ) ) ||
( ( max_size > 0 ) && ( size > max_size ) ) )
{
errno = ERANGE;
ntfs_log_perror( "Attr type %d size check failed (min,size,max="
"%lld,%lld,%lld)", type, ( long long )min_size,
( long long )size, ( long long )max_size );
return -1;
}
return 0;
}
/**
* ntfs_attr_can_be_non_resident - check if an attribute can be non-resident
* @vol: ntfs volume to which the attribute belongs
* @type: attribute type to check
* @name: attribute name to check
* @name_len: attribute name length
*
* Check whether the attribute of @type and @name with name length @name_len on
* the ntfs volume @vol is allowed to be non-resident. This information is
* obtained from $AttrDef system file and is augmented by rules imposed by
* Microsoft (e.g. see http://support.microsoft.com/kb/974729/).
*
* Return 0 if the attribute is allowed to be non-resident and -1 if not or an
* error occurred. On error the error code is stored in errno. The following
* error codes are defined:
* EPERM - The attribute is not allowed to be non-resident.
* ENOENT - The attribute @type is not specified in $AttrDef.
* EINVAL - Invalid parameters (e.g. @vol is not valid).
*/
static int ntfs_attr_can_be_non_resident( const ntfs_volume *vol, const ATTR_TYPES type,
const ntfschar *name, int name_len )
{
ATTR_DEF *ad;
BOOL allowed;
/*
* Microsoft has decreed that $LOGGED_UTILITY_STREAM attributes with a
* name of $TXF_DATA must be resident despite the entry for
* $LOGGED_UTILITY_STREAM in $AttrDef allowing them to be non-resident.
* Failure to obey this on the root directory mft record of a volume
* causes Windows Vista and later to see the volume as a RAW volume and
* thus cannot mount it at all.
*/
if ( ( type == AT_LOGGED_UTILITY_STREAM )
&& name
&& ntfs_names_are_equal( TXF_DATA, 9, name, name_len,
CASE_SENSITIVE, vol->upcase, vol->upcase_len ) )
allowed = FALSE;
else
{
/* Find the attribute definition record in $AttrDef. */
ad = ntfs_attr_find_in_attrdef( vol, type );
if ( !ad )
return -1;
/* Check the flags and return the result. */
allowed = !( ad->flags & ATTR_DEF_RESIDENT );
}
if ( !allowed )
{
errno = EPERM;
ntfs_log_trace( "Attribute can't be non-resident\n" );
return -1;
}
return 0;
}
/**
* ntfs_attr_can_be_resident - check if an attribute can be resident
* @vol: ntfs volume to which the attribute belongs
* @type: attribute type which to check
*
* Check whether the attribute of @type on the ntfs volume @vol is allowed to
* be resident. This information is derived from our ntfs knowledge and may
* not be completely accurate, especially when user defined attributes are
* present. Basically we allow everything to be resident except for index
* allocation and extended attribute attributes.
*
* Return 0 if the attribute is allowed to be resident and -1 if not or an
* error occurred. On error the error code is stored in errno. The following
* error codes are defined:
* EPERM - The attribute is not allowed to be resident.
* EINVAL - Invalid parameters (e.g. @vol is not valid).
*
* Warning: In the system file $MFT the attribute $Bitmap must be non-resident
* otherwise windows will not boot (blue screen of death)! We cannot
* check for this here as we don't know which inode's $Bitmap is being
* asked about so the caller needs to special case this.
*/
int ntfs_attr_can_be_resident( const ntfs_volume *vol, const ATTR_TYPES type )
{
if ( !vol || !vol->attrdef || !type )
{
errno = EINVAL;
return -1;
}
if ( type != AT_INDEX_ALLOCATION )
return 0;
ntfs_log_trace( "Attribute can't be resident\n" );
errno = EPERM;
return -1;
}
/**
* ntfs_make_room_for_attr - make room for an attribute inside an mft record
* @m: mft record
* @pos: position at which to make space
* @size: byte size to make available at this position
*
* @pos points to the attribute in front of which we want to make space.
*
* Return 0 on success or -1 on error. On error the error code is stored in
* errno. Possible error codes are:
* ENOSPC - There is not enough space available to complete operation. The
* caller has to make space before calling this.
* EINVAL - Input parameters were faulty.
*/
int ntfs_make_room_for_attr( MFT_RECORD *m, u8 *pos, u32 size )
{
u32 biu;
ntfs_log_trace( "Entering for pos 0x%d, size %u.\n",
( int )( pos - ( u8* )m ), ( unsigned ) size );
/* Make size 8-byte alignment. */
size = ( size + 7 ) & ~7;
/* Rigorous consistency checks. */
if ( !m || !pos || pos < ( u8* )m )
{
errno = EINVAL;
ntfs_log_perror( "%s: pos=%p m=%p", __FUNCTION__, pos, m );
return -1;
}
/* The -8 is for the attribute terminator. */
if ( pos - ( u8* )m > ( int )le32_to_cpu( m->bytes_in_use ) - 8 )
{
errno = EINVAL;
return -1;
}
/* Nothing to do. */
if ( !size )
return 0;
biu = le32_to_cpu( m->bytes_in_use );
/* Do we have enough space? */
if ( biu + size > le32_to_cpu( m->bytes_allocated ) ||
pos + size > ( u8* )m + le32_to_cpu( m->bytes_allocated ) )
{
errno = ENOSPC;
ntfs_log_trace( "No enough space in the MFT record\n" );
return -1;
}
/* Move everything after pos to pos + size. */
memmove( pos + size, pos, biu - ( pos - ( u8* )m ) );
/* Update mft record. */
m->bytes_in_use = cpu_to_le32( biu + size );
return 0;
}
/**
* ntfs_resident_attr_record_add - add resident attribute to inode
* @ni: opened ntfs inode to which MFT record add attribute
* @type: type of the new attribute
* @name: name of the new attribute
* @name_len: name length of the new attribute
* @val: value of the new attribute
* @size: size of new attribute (length of @val, if @val != NULL)
* @flags: flags of the new attribute
*
* Return offset to attribute from the beginning of the mft record on success
* and -1 on error. On error the error code is stored in errno.
* Possible error codes are:
* EINVAL - Invalid arguments passed to function.
* EEXIST - Attribute of such type and with same name already exists.
* EIO - I/O error occurred or damaged filesystem.
*/
int ntfs_resident_attr_record_add( ntfs_inode *ni, ATTR_TYPES type,
ntfschar *name, u8 name_len, u8 *val, u32 size,
ATTR_FLAGS data_flags )
{
ntfs_attr_search_ctx *ctx;
u32 length;
ATTR_RECORD *a;
MFT_RECORD *m;
int err, offset;
ntfs_inode *base_ni;
ntfs_log_trace( "Entering for inode 0x%llx, attr 0x%x, flags 0x%x.\n",
( long long ) ni->mft_no, ( unsigned ) type, ( unsigned ) data_flags );
if ( !ni || ( !name && name_len ) )
{
errno = EINVAL;
return -1;
}
if ( ntfs_attr_can_be_resident( ni->vol, type ) )
{
if ( errno == EPERM )
ntfs_log_trace( "Attribute can't be resident.\n" );
else
ntfs_log_trace( "ntfs_attr_can_be_resident failed.\n" );
return -1;
}
/* Locate place where record should be. */
ctx = ntfs_attr_get_search_ctx( ni, NULL );
if ( !ctx )
return -1;
/*
* Use ntfs_attr_find instead of ntfs_attr_lookup to find place for
* attribute in @ni->mrec, not any extent inode in case if @ni is base
* file record.
*/
if ( !ntfs_attr_find( type, name, name_len, CASE_SENSITIVE, val, size,
ctx ) )
{
err = EEXIST;
ntfs_log_trace( "Attribute already present.\n" );
goto put_err_out;
}
if ( errno != ENOENT )
{
err = EIO;
goto put_err_out;
}
a = ctx->attr;
m = ctx->mrec;
/* Make room for attribute. */
length = offsetof( ATTR_RECORD, resident_end ) +
( ( name_len * sizeof( ntfschar ) + 7 ) & ~7 ) +
( ( size + 7 ) & ~7 );
if ( ntfs_make_room_for_attr( ctx->mrec, ( u8* ) ctx->attr, length ) )
{
err = errno;
ntfs_log_trace( "Failed to make room for attribute.\n" );
goto put_err_out;
}
/* Setup record fields. */
offset = ( ( u8* )a - ( u8* )m );
a->type = type;
a->length = cpu_to_le32( length );
a->non_resident = 0;
a->name_length = name_len;
a->name_offset = ( name_len
? cpu_to_le16( offsetof( ATTR_RECORD, resident_end ) )
: const_cpu_to_le16( 0 ) );
a->flags = data_flags;
a->instance = m->next_attr_instance;
a->value_length = cpu_to_le32( size );
a->value_offset = cpu_to_le16( length - ( ( size + 7 ) & ~7 ) );
if ( val )
memcpy( ( u8* )a + le16_to_cpu( a->value_offset ), val, size );
else
memset( ( u8* )a + le16_to_cpu( a->value_offset ), 0, size );
if ( type == AT_FILE_NAME )
a->resident_flags = RESIDENT_ATTR_IS_INDEXED;
else
a->resident_flags = 0;
if ( name_len )
memcpy( ( u8* )a + le16_to_cpu( a->name_offset ),
name, sizeof( ntfschar ) * name_len );
m->next_attr_instance =
cpu_to_le16( ( le16_to_cpu( m->next_attr_instance ) + 1 ) & 0xffff );
if ( ni->nr_extents == -1 )
base_ni = ni->base_ni;
else
base_ni = ni;
if ( type != AT_ATTRIBUTE_LIST && NInoAttrList( base_ni ) )
{
if ( ntfs_attrlist_entry_add( ni, a ) )
{
err = errno;
ntfs_attr_record_resize( m, a, 0 );
ntfs_log_trace( "Failed add attribute entry to "
"ATTRIBUTE_LIST.\n" );
goto put_err_out;
}
}
if ( ni->mrec->flags & MFT_RECORD_IS_DIRECTORY
? type == AT_INDEX_ROOT && name == NTFS_INDEX_I30
: type == AT_DATA && name == AT_UNNAMED )
{
ni->data_size = size;
ni->allocated_size = ( size + 7 ) & ~7;
set_nino_flag( ni, KnownSize );
}
ntfs_inode_mark_dirty( ni );
ntfs_attr_put_search_ctx( ctx );
return offset;
put_err_out:
ntfs_attr_put_search_ctx( ctx );
errno = err;
return -1;
}
/**
* ntfs_non_resident_attr_record_add - add extent of non-resident attribute
* @ni: opened ntfs inode to which MFT record add attribute
* @type: type of the new attribute extent
* @name: name of the new attribute extent
* @name_len: name length of the new attribute extent
* @lowest_vcn: lowest vcn of the new attribute extent
* @dataruns_size: dataruns size of the new attribute extent
* @flags: flags of the new attribute extent
*
* Return offset to attribute from the beginning of the mft record on success
* and -1 on error. On error the error code is stored in errno.
* Possible error codes are:
* EINVAL - Invalid arguments passed to function.
* EEXIST - Attribute of such type, with same lowest vcn and with same
* name already exists.
* EIO - I/O error occurred or damaged filesystem.
*/
int ntfs_non_resident_attr_record_add( ntfs_inode *ni, ATTR_TYPES type,
ntfschar *name, u8 name_len, VCN lowest_vcn, int dataruns_size,
ATTR_FLAGS flags )
{
ntfs_attr_search_ctx *ctx;
u32 length;
ATTR_RECORD *a;
MFT_RECORD *m;
ntfs_inode *base_ni;
int err, offset;
ntfs_log_trace( "Entering for inode 0x%llx, attr 0x%x, lowest_vcn %lld, "
"dataruns_size %d, flags 0x%x.\n",
( long long ) ni->mft_no, ( unsigned ) type,
( long long ) lowest_vcn, dataruns_size, ( unsigned ) flags );
if ( !ni || dataruns_size <= 0 || ( !name && name_len ) )
{
errno = EINVAL;
return -1;
}
if ( ntfs_attr_can_be_non_resident( ni->vol, type, name, name_len ) )
{
if ( errno == EPERM )
ntfs_log_perror( "Attribute can't be non resident" );
else
ntfs_log_perror( "ntfs_attr_can_be_non_resident failed" );
return -1;
}
/* Locate place where record should be. */
ctx = ntfs_attr_get_search_ctx( ni, NULL );
if ( !ctx )
return -1;
/*
* Use ntfs_attr_find instead of ntfs_attr_lookup to find place for
* attribute in @ni->mrec, not any extent inode in case if @ni is base
* file record.
*/
if ( !ntfs_attr_find( type, name, name_len, CASE_SENSITIVE, NULL, 0,
ctx ) )
{
err = EEXIST;
ntfs_log_perror( "Attribute 0x%x already present", type );
goto put_err_out;
}
if ( errno != ENOENT )
{
ntfs_log_perror( "ntfs_attr_find failed" );
err = EIO;
goto put_err_out;
}
a = ctx->attr;
m = ctx->mrec;
/* Make room for attribute. */
dataruns_size = ( dataruns_size + 7 ) & ~7;
length = offsetof( ATTR_RECORD, compressed_size ) + ( ( sizeof( ntfschar ) *
name_len + 7 ) & ~7 ) + dataruns_size +
( ( flags & ( ATTR_IS_COMPRESSED | ATTR_IS_SPARSE ) ) ?
sizeof( a->compressed_size ) : 0 );
if ( ntfs_make_room_for_attr( ctx->mrec, ( u8* ) ctx->attr, length ) )
{
err = errno;
ntfs_log_perror( "Failed to make room for attribute" );
goto put_err_out;
}
/* Setup record fields. */
a->type = type;
a->length = cpu_to_le32( length );
a->non_resident = 1;
a->name_length = name_len;
a->name_offset = cpu_to_le16( offsetof( ATTR_RECORD, compressed_size ) +
( ( flags & ( ATTR_IS_COMPRESSED | ATTR_IS_SPARSE ) ) ?
sizeof( a->compressed_size ) : 0 ) );
a->flags = flags;
a->instance = m->next_attr_instance;
a->lowest_vcn = cpu_to_sle64( lowest_vcn );
a->mapping_pairs_offset = cpu_to_le16( length - dataruns_size );
a->compression_unit = ( flags & ATTR_IS_COMPRESSED )
? STANDARD_COMPRESSION_UNIT : 0;
/* If @lowest_vcn == 0, than setup empty attribute. */
if ( !lowest_vcn )
{
a->highest_vcn = cpu_to_sle64( -1 );
a->allocated_size = 0;
a->data_size = 0;
a->initialized_size = 0;
/* Set empty mapping pairs. */
*( ( u8* )a + le16_to_cpu( a->mapping_pairs_offset ) ) = 0;
}
if ( name_len )
memcpy( ( u8* )a + le16_to_cpu( a->name_offset ),
name, sizeof( ntfschar ) * name_len );
m->next_attr_instance =
cpu_to_le16( ( le16_to_cpu( m->next_attr_instance ) + 1 ) & 0xffff );
if ( ni->nr_extents == -1 )
base_ni = ni->base_ni;
else
base_ni = ni;
if ( type != AT_ATTRIBUTE_LIST && NInoAttrList( base_ni ) )
{
if ( ntfs_attrlist_entry_add( ni, a ) )
{
err = errno;
ntfs_log_perror( "Failed add attr entry to attrlist" );
ntfs_attr_record_resize( m, a, 0 );
goto put_err_out;
}
}
ntfs_inode_mark_dirty( ni );
/*
* Locate offset from start of the MFT record where new attribute is
* placed. We need relookup it, because record maybe moved during
* update of attribute list.
*/
ntfs_attr_reinit_search_ctx( ctx );
if ( ntfs_attr_lookup( type, name, name_len, CASE_SENSITIVE,
lowest_vcn, NULL, 0, ctx ) )
{
ntfs_log_perror( "%s: attribute lookup failed", __FUNCTION__ );
ntfs_attr_put_search_ctx( ctx );
return -1;
}
offset = ( u8* )ctx->attr - ( u8* )ctx->mrec;
ntfs_attr_put_search_ctx( ctx );
return offset;
put_err_out:
ntfs_attr_put_search_ctx( ctx );
errno = err;
return -1;
}
/**
* ntfs_attr_record_rm - remove attribute extent
* @ctx: search context describing the attribute which should be removed
*
* If this function succeed, user should reinit search context if he/she wants
* use it anymore.
*
* Return 0 on success and -1 on error. On error the error code is stored in
* errno. Possible error codes are:
* EINVAL - Invalid arguments passed to function.
* EIO - I/O error occurred or damaged filesystem.
*/
int ntfs_attr_record_rm( ntfs_attr_search_ctx *ctx )
{
ntfs_inode *base_ni, *ni;
ATTR_TYPES type;
if ( !ctx || !ctx->ntfs_ino || !ctx->mrec || !ctx->attr )
{
errno = EINVAL;
return -1;
}
ntfs_log_trace( "Entering for inode 0x%llx, attr 0x%x.\n",
( long long ) ctx->ntfs_ino->mft_no,
( unsigned ) le32_to_cpu( ctx->attr->type ) );
type = ctx->attr->type;
ni = ctx->ntfs_ino;
if ( ctx->base_ntfs_ino )
base_ni = ctx->base_ntfs_ino;
else
base_ni = ctx->ntfs_ino;
/* Remove attribute itself. */
if ( ntfs_attr_record_resize( ctx->mrec, ctx->attr, 0 ) )
{
ntfs_log_trace( "Couldn't remove attribute record. Bug or damaged MFT "
"record.\n" );
if ( NInoAttrList( base_ni ) && type != AT_ATTRIBUTE_LIST )
if ( ntfs_attrlist_entry_add( ni, ctx->attr ) )
ntfs_log_trace( "Rollback failed. Leaving inconstant "
"metadata.\n" );
errno = EIO;
return -1;
}
ntfs_inode_mark_dirty( ni );
/*
* Remove record from $ATTRIBUTE_LIST if present and we don't want
* delete $ATTRIBUTE_LIST itself.
*/
if ( NInoAttrList( base_ni ) && type != AT_ATTRIBUTE_LIST )
{
if ( ntfs_attrlist_entry_rm( ctx ) )
{
ntfs_log_trace( "Couldn't delete record from "
"$ATTRIBUTE_LIST.\n" );
return -1;
}
}
/* Post $ATTRIBUTE_LIST delete setup. */
if ( type == AT_ATTRIBUTE_LIST )
{
if ( NInoAttrList( base_ni ) && base_ni->attr_list )
free( base_ni->attr_list );
base_ni->attr_list = NULL;
NInoClearAttrList( base_ni );
NInoAttrListClearDirty( base_ni );
}
/* Free MFT record, if it doesn't contain attributes. */
if ( le32_to_cpu( ctx->mrec->bytes_in_use ) -
le16_to_cpu( ctx->mrec->attrs_offset ) == 8 )
{
if ( ntfs_mft_record_free( ni->vol, ni ) )
{
// FIXME: We need rollback here.
ntfs_log_trace( "Couldn't free MFT record.\n" );
errno = EIO;
return -1;
}
/* Remove done if we freed base inode. */
if ( ni == base_ni )
return 0;
}
if ( type == AT_ATTRIBUTE_LIST || !NInoAttrList( base_ni ) )
return 0;
/* Remove attribute list if we don't need it any more. */
if ( !ntfs_attrlist_need( base_ni ) )
{
ntfs_attr_reinit_search_ctx( ctx );
if ( ntfs_attr_lookup( AT_ATTRIBUTE_LIST, NULL, 0, CASE_SENSITIVE,
0, NULL, 0, ctx ) )
{
/*
* FIXME: Should we succeed here? Definitely something
* goes wrong because NInoAttrList(base_ni) returned
* that we have got attribute list.
*/
ntfs_log_trace( "Couldn't find attribute list. Succeed "
"anyway.\n" );
return 0;
}
/* Deallocate clusters. */
if ( ctx->attr->non_resident )
{
runlist *al_rl;
al_rl = ntfs_mapping_pairs_decompress( base_ni->vol,
ctx->attr, NULL );
if ( !al_rl )
{
ntfs_log_trace( "Couldn't decompress attribute list "
"runlist. Succeed anyway.\n" );
return 0;
}
if ( ntfs_cluster_free_from_rl( base_ni->vol, al_rl ) )
{
ntfs_log_trace( "Leaking clusters! Run chkdsk. "
"Couldn't free clusters from "
"attribute list runlist.\n" );
}
free( al_rl );
}
/* Remove attribute record itself. */
if ( ntfs_attr_record_rm( ctx ) )
{
/*
* FIXME: Should we succeed here? BTW, chkdsk doesn't
* complain if it find MFT record with attribute list,
* but without extents.
*/
ntfs_log_trace( "Couldn't remove attribute list. Succeed "
"anyway.\n" );
return 0;
}
}
return 0;
}
/**
* ntfs_attr_add - add attribute to inode
* @ni: opened ntfs inode to which add attribute
* @type: type of the new attribute
* @name: name in unicode of the new attribute
* @name_len: name length in unicode characters of the new attribute
* @val: value of new attribute
* @size: size of the new attribute / length of @val (if specified)
*
* @val should always be specified for always resident attributes (eg. FILE_NAME
* attribute), for attributes that can become non-resident @val can be NULL
* (eg. DATA attribute). @size can be specified even if @val is NULL, in this
* case data size will be equal to @size and initialized size will be equal
* to 0.
*
* If inode haven't got enough space to add attribute, add attribute to one of
* it extents, if no extents present or no one of them have enough space, than
* allocate new extent and add attribute to it.
*
* If on one of this steps attribute list is needed but not present, than it is
* added transparently to caller. So, this function should not be called with
* @type == AT_ATTRIBUTE_LIST, if you really need to add attribute list call
* ntfs_inode_add_attrlist instead.
*
* On success return 0. On error return -1 with errno set to the error code.
*/
int ntfs_attr_add( ntfs_inode *ni, ATTR_TYPES type,
ntfschar *name, u8 name_len, u8 *val, s64 size )
{
u32 attr_rec_size;
int err, i, offset;
BOOL is_resident;
BOOL can_be_non_resident = FALSE;
ntfs_inode *attr_ni;
ntfs_attr *na;
ATTR_FLAGS data_flags;
if ( !ni || size < 0 || type == AT_ATTRIBUTE_LIST )
{
errno = EINVAL;
ntfs_log_perror( "%s: ni=%p size=%lld", __FUNCTION__, ni,
( long long )size );
return -1;
}
ntfs_log_trace( "Entering for inode %lld, attr %x, size %lld.\n",
( long long )ni->mft_no, type, ( long long )size );
if ( ni->nr_extents == -1 )
ni = ni->base_ni;
/* Check the attribute type and the size. */
if ( ntfs_attr_size_bounds_check( ni->vol, type, size ) )
{
if ( errno == ENOENT )
errno = EIO;
return -1;
}
/* Sanity checks for always resident attributes. */
if ( ntfs_attr_can_be_non_resident( ni->vol, type, name, name_len ) )
{
if ( errno != EPERM )
{
err = errno;
ntfs_log_perror( "ntfs_attr_can_be_non_resident failed" );
goto err_out;
}
/* @val is mandatory. */
if ( !val )
{
errno = EINVAL;
ntfs_log_perror( "val is mandatory for always resident "
"attributes" );
return -1;
}
if ( size > ni->vol->mft_record_size )
{
errno = ERANGE;
ntfs_log_perror( "Attribute is too big" );
return -1;
}
}
else
can_be_non_resident = TRUE;
/*
* Determine resident or not will be new attribute. We add 8 to size in
* non resident case for mapping pairs.
*/
if ( !ntfs_attr_can_be_resident( ni->vol, type ) )
{
is_resident = TRUE;
}
else
{
if ( errno != EPERM )
{
err = errno;
ntfs_log_perror( "ntfs_attr_can_be_resident failed" );
goto err_out;
}
is_resident = FALSE;
}
/* Calculate attribute record size. */
if ( is_resident )
attr_rec_size = offsetof( ATTR_RECORD, resident_end ) +
( ( name_len * sizeof( ntfschar ) + 7 ) & ~7 ) +
( ( size + 7 ) & ~7 );
else
attr_rec_size = offsetof( ATTR_RECORD, non_resident_end ) +
( ( name_len * sizeof( ntfschar ) + 7 ) & ~7 ) + 8;
/*
* If we have enough free space for the new attribute in the base MFT
* record, then add attribute to it.
*/
if ( le32_to_cpu( ni->mrec->bytes_allocated ) -
le32_to_cpu( ni->mrec->bytes_in_use ) >= attr_rec_size )
{
attr_ni = ni;
goto add_attr_record;
}
/* Try to add to extent inodes. */
if ( ntfs_inode_attach_all_extents( ni ) )
{
err = errno;
ntfs_log_perror( "Failed to attach all extents to inode" );
goto err_out;
}
for ( i = 0; i < ni->nr_extents; i++ )
{
attr_ni = ni->extent_nis[i];
if ( le32_to_cpu( attr_ni->mrec->bytes_allocated ) -
le32_to_cpu( attr_ni->mrec->bytes_in_use ) >=
attr_rec_size )
goto add_attr_record;
}
/* There is no extent that contain enough space for new attribute. */
if ( !NInoAttrList( ni ) )
{
/* Add attribute list not present, add it and retry. */
if ( ntfs_inode_add_attrlist( ni ) )
{
err = errno;
ntfs_log_perror( "Failed to add attribute list" );
goto err_out;
}
return ntfs_attr_add( ni, type, name, name_len, val, size );
}
/* Allocate new extent. */
attr_ni = ntfs_mft_record_alloc( ni->vol, ni );
if ( !attr_ni )
{
err = errno;
ntfs_log_perror( "Failed to allocate extent record" );
goto err_out;
}
add_attr_record:
if ( ( ni->flags & FILE_ATTR_COMPRESSED )
&& ( ni->vol->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( na, size ) ||
( val && ( ntfs_attr_pwrite( na, 0, size, val ) != size ) ) )
{
err = errno;
ntfs_log_perror( "Failed to initialize just added attribute" );
if ( ntfs_attr_rm( na ) )
ntfs_log_perror( "Failed to remove just added attribute" );
ntfs_attr_close( na );
goto err_out;
}
ntfs_attr_close( na );
return 0;
rm_attr_err_out:
/* Remove just added attribute. */
if ( ntfs_attr_record_resize( attr_ni->mrec,
( ATTR_RECORD* )( ( u8* )attr_ni->mrec + offset ), 0 ) )
ntfs_log_perror( "Failed to remove just added attribute #2" );
free_err_out:
/* Free MFT record, if it doesn't contain attributes. */
if ( le32_to_cpu( attr_ni->mrec->bytes_in_use ) -
le16_to_cpu( attr_ni->mrec->attrs_offset ) == 8 )
if ( ntfs_mft_record_free( attr_ni->vol, attr_ni ) )
ntfs_log_perror( "Failed to free MFT record" );
err_out:
errno = err;
return -1;
}
/*
* Change an attribute flag
*/
int ntfs_attr_set_flags( ntfs_inode *ni, ATTR_TYPES type,
ntfschar *name, u8 name_len, ATTR_FLAGS flags, ATTR_FLAGS mask )
{
ntfs_attr_search_ctx *ctx;
int res;
res = -1;
/* Search for designated attribute */
ctx = ntfs_attr_get_search_ctx( ni, NULL );
if ( ctx )
{
if ( !ntfs_attr_lookup( type, name, name_len,
CASE_SENSITIVE, 0, NULL, 0, ctx ) )
{
/* do the requested change (all small endian le16) */
ctx->attr->flags = ( ctx->attr->flags & ~mask )
| ( flags & mask );
NInoSetDirty( ni );
res = 0;
}
ntfs_attr_put_search_ctx( ctx );
}
return ( res );
}
/**
* ntfs_attr_rm - remove attribute from ntfs inode
* @na: opened ntfs attribute to delete
*
* Remove attribute and all it's extents from ntfs inode. If attribute was non
* resident also free all clusters allocated by attribute.
*
* Return 0 on success or -1 on error with errno set to the error code.
*/
int ntfs_attr_rm( ntfs_attr *na )
{
ntfs_attr_search_ctx *ctx;
int ret = 0;
if ( !na )
{
ntfs_log_trace( "Invalid arguments passed.\n" );
errno = EINVAL;
return -1;
}
ntfs_log_trace( "Entering for inode 0x%llx, attr 0x%x.\n",
( long long ) na->ni->mft_no, na->type );
/* Free cluster allocation. */
if ( NAttrNonResident( na ) )
{
if ( ntfs_attr_map_whole_runlist( na ) )
return -1;
if ( ntfs_cluster_free( na->ni->vol, na, 0, -1 ) < 0 )
{
ntfs_log_trace( "Failed to free cluster allocation. Leaving "
"inconstant metadata.\n" );
ret = -1;
}
}
/* Search for attribute extents and remove them all. */
ctx = ntfs_attr_get_search_ctx( na->ni, NULL );
if ( !ctx )
return -1;
while ( !ntfs_attr_lookup( na->type, na->name, na->name_len,
CASE_SENSITIVE, 0, NULL, 0, ctx ) )
{
if ( ntfs_attr_record_rm( ctx ) )
{
ntfs_log_trace( "Failed to remove attribute extent. Leaving "
"inconstant metadata.\n" );
ret = -1;
}
ntfs_attr_reinit_search_ctx( ctx );
}
ntfs_attr_put_search_ctx( ctx );
if ( errno != ENOENT )
{
ntfs_log_trace( "Attribute lookup failed. Probably leaving inconstant "
"metadata.\n" );
ret = -1;
}
return ret;
}
/**
* ntfs_attr_record_resize - resize an attribute record
* @m: mft record containing attribute record
* @a: attribute record to resize
* @new_size: new size in bytes to which to resize the attribute record @a
*
* Resize the attribute record @a, i.e. the resident part of the attribute, in
* the mft record @m to @new_size bytes.
*
* Return 0 on success and -1 on error with errno set to the error code.
* The following error codes are defined:
* ENOSPC - Not enough space in the mft record @m to perform the resize.
* Note that on error no modifications have been performed whatsoever.
*
* Warning: If you make a record smaller without having copied all the data you
* are interested in the data may be overwritten!
*/
int ntfs_attr_record_resize( MFT_RECORD *m, ATTR_RECORD *a, u32 new_size )
{
u32 old_size, alloc_size, attr_size;
old_size = le32_to_cpu( m->bytes_in_use );
alloc_size = le32_to_cpu( m->bytes_allocated );
attr_size = le32_to_cpu( a->length );
ntfs_log_trace( "Sizes: old=%u alloc=%u attr=%u new=%u\n",
( unsigned )old_size, ( unsigned )alloc_size,
( unsigned )attr_size, ( unsigned )new_size );
/* Align to 8 bytes, just in case the caller hasn't. */
new_size = ( new_size + 7 ) & ~7;
/* If the actual attribute length has changed, move things around. */
if ( new_size != attr_size )
{
u32 new_muse = old_size - attr_size + new_size;
/* Not enough space in this mft record. */
if ( new_muse > alloc_size )
{
errno = ENOSPC;
ntfs_log_trace( "Not enough space in the MFT record "
"(%u > %u)\n", new_muse, alloc_size );
return -1;
}
if ( a->type == AT_INDEX_ROOT && new_size > attr_size &&
new_muse + 120 > alloc_size && old_size + 120 <= alloc_size )
{
errno = ENOSPC;
ntfs_log_trace( "Too big INDEX_ROOT (%u > %u)\n",
new_muse, alloc_size );
return STATUS_RESIDENT_ATTRIBUTE_FILLED_MFT;
}
/* Move attributes following @a to their new location. */
memmove( ( u8 * )a + new_size, ( u8 * )a + attr_size,
old_size - ( ( u8 * )a - ( u8 * )m ) - attr_size );
/* Adjust @m to reflect the change in used space. */
m->bytes_in_use = cpu_to_le32( new_muse );
/* Adjust @a to reflect the new size. */
if ( new_size >= offsetof( ATTR_REC, length ) + sizeof( a->length ) )
a->length = cpu_to_le32( new_size );
}
return 0;
}
/**
* ntfs_resident_attr_value_resize - resize the value of a resident attribute
* @m: mft record containing attribute record
* @a: attribute record whose value to resize
* @new_size: new size in bytes to which to resize the attribute value of @a
*
* Resize the value of the attribute @a in the mft record @m to @new_size bytes.
* If the value is made bigger, the newly "allocated" space is cleared.
*
* Return 0 on success and -1 on error with errno set to the error code.
* The following error codes are defined:
* ENOSPC - Not enough space in the mft record @m to perform the resize.
* Note that on error no modifications have been performed whatsoever.
*/
int ntfs_resident_attr_value_resize( MFT_RECORD *m, ATTR_RECORD *a,
const u32 new_size )
{
int ret;
ntfs_log_trace( "Entering for new size %u.\n", ( unsigned )new_size );
/* Resize the resident part of the attribute record. */
if ( ( ret = ntfs_attr_record_resize( m, a, ( le16_to_cpu( a->value_offset ) +
new_size + 7 ) & ~7 ) ) < 0 )
return ret;
/*
* If we made the attribute value bigger, clear the area between the
* old size and @new_size.
*/
if ( new_size > le32_to_cpu( a->value_length ) )
memset( ( u8* )a + le16_to_cpu( a->value_offset ) +
le32_to_cpu( a->value_length ), 0, new_size -
le32_to_cpu( a->value_length ) );
/* Finally update the length of the attribute value. */
a->value_length = cpu_to_le32( new_size );
return 0;
}
/**
* ntfs_attr_record_move_to - move attribute record to target inode
* @ctx: attribute search context describing the attribute record
* @ni: opened ntfs inode to which move attribute record
*
* If this function succeed, user should reinit search context if he/she wants
* use it anymore.
*
* Return 0 on success and -1 on error with errno set to the error code.
*/
int ntfs_attr_record_move_to( ntfs_attr_search_ctx *ctx, ntfs_inode *ni )
{
ntfs_attr_search_ctx *nctx;
ATTR_RECORD *a;
int err;
if ( !ctx || !ctx->attr || !ctx->ntfs_ino || !ni )
{
ntfs_log_trace( "Invalid arguments passed.\n" );
errno = EINVAL;
return -1;
}
ntfs_log_trace( "Entering for ctx->attr->type 0x%x, ctx->ntfs_ino->mft_no "
"0x%llx, ni->mft_no 0x%llx.\n",
( unsigned ) le32_to_cpu( ctx->attr->type ),
( long long ) ctx->ntfs_ino->mft_no,
( long long ) ni->mft_no );
if ( ctx->ntfs_ino == ni )
return 0;
if ( !ctx->al_entry )
{
ntfs_log_trace( "Inode should contain attribute list to use this "
"function.\n" );
errno = EINVAL;
return -1;
}
/* Find place in MFT record where attribute will be moved. */
a = ctx->attr;
nctx = ntfs_attr_get_search_ctx( ni, NULL );
if ( !nctx )
return -1;
/*
* Use ntfs_attr_find instead of ntfs_attr_lookup to find place for
* attribute in @ni->mrec, not any extent inode in case if @ni is base
* file record.
*/
if ( !ntfs_attr_find( a->type, ( ntfschar* )( ( u8* )a + le16_to_cpu(
a->name_offset ) ), a->name_length, CASE_SENSITIVE, NULL,
0, nctx ) )
{
ntfs_log_trace( "Attribute of such type, with same name already "
"present in this MFT record.\n" );
err = EEXIST;
goto put_err_out;
}
if ( errno != ENOENT )
{
err = errno;
ntfs_log_debug( "Attribute lookup failed.\n" );
goto put_err_out;
}
/* Make space and move attribute. */
if ( ntfs_make_room_for_attr( ni->mrec, ( u8* ) nctx->attr,
le32_to_cpu( a->length ) ) )
{
err = errno;
ntfs_log_trace( "Couldn't make space for attribute.\n" );
goto put_err_out;
}
memcpy( nctx->attr, a, le32_to_cpu( a->length ) );
nctx->attr->instance = nctx->mrec->next_attr_instance;
nctx->mrec->next_attr_instance = cpu_to_le16(
( le16_to_cpu( nctx->mrec->next_attr_instance ) + 1 ) & 0xffff );
ntfs_attr_record_resize( ctx->mrec, a, 0 );
ntfs_inode_mark_dirty( ctx->ntfs_ino );
ntfs_inode_mark_dirty( ni );
/* Update attribute list. */
ctx->al_entry->mft_reference =
MK_LE_MREF( ni->mft_no, le16_to_cpu( ni->mrec->sequence_number ) );
ctx->al_entry->instance = nctx->attr->instance;
ntfs_attrlist_mark_dirty( ni );
ntfs_attr_put_search_ctx( nctx );
return 0;
put_err_out:
ntfs_attr_put_search_ctx( nctx );
errno = err;
return -1;
}
/**
* ntfs_attr_record_move_away - move away attribute record from it's mft record
* @ctx: attribute search context describing the attribute record
* @extra: minimum amount of free space in the new holder of record
*
* New attribute record holder must have free @extra bytes after moving
* attribute record to it.
*
* If this function succeed, user should reinit search context if he/she wants
* use it anymore.
*
* Return 0 on success and -1 on error with errno set to the error code.
*/
int ntfs_attr_record_move_away( ntfs_attr_search_ctx *ctx, int extra )
{
ntfs_inode *base_ni, *ni;
MFT_RECORD *m;
int i;
if ( !ctx || !ctx->attr || !ctx->ntfs_ino || extra < 0 )
{
errno = EINVAL;
ntfs_log_perror( "%s: ctx=%p ctx->attr=%p extra=%d", __FUNCTION__,
ctx, ctx ? ctx->attr : NULL, extra );
return -1;
}
ntfs_log_trace( "Entering for attr 0x%x, inode %llu\n",
( unsigned ) le32_to_cpu( ctx->attr->type ),
( unsigned long long )ctx->ntfs_ino->mft_no );
if ( ctx->ntfs_ino->nr_extents == -1 )
base_ni = ctx->base_ntfs_ino;
else
base_ni = ctx->ntfs_ino;
if ( !NInoAttrList( base_ni ) )
{
errno = EINVAL;
ntfs_log_perror( "Inode %llu has no attrlist",
( unsigned long long )base_ni->mft_no );
return -1;
}
if ( ntfs_inode_attach_all_extents( ctx->ntfs_ino ) )
{
ntfs_log_perror( "Couldn't attach extents, inode=%llu",
( unsigned long long )base_ni->mft_no );
return -1;
}
/* Walk through all extents and try to move attribute to them. */
for ( i = 0; i < base_ni->nr_extents; i++ )
{
ni = base_ni->extent_nis[i];
m = ni->mrec;
if ( ctx->ntfs_ino->mft_no == ni->mft_no )
continue;
if ( le32_to_cpu( m->bytes_allocated ) -
le32_to_cpu( m->bytes_in_use ) <
le32_to_cpu( ctx->attr->length ) + extra )
continue;
/*
* ntfs_attr_record_move_to can fail if extent with other lowest
* VCN already present in inode we trying move record to. So,
* do not return error.
*/
if ( !ntfs_attr_record_move_to( ctx, ni ) )
return 0;
}
/*
* Failed to move attribute to one of the current extents, so allocate
* new extent and move attribute to it.
*/
ni = ntfs_mft_record_alloc( base_ni->vol, base_ni );
if ( !ni )
{
ntfs_log_perror( "Couldn't allocate MFT record" );
return -1;
}
if ( ntfs_attr_record_move_to( ctx, ni ) )
{
ntfs_log_perror( "Couldn't move attribute to MFT record" );
return -1;
}
return 0;
}
/**
* ntfs_attr_make_non_resident - convert a resident to a non-resident attribute
* @na: open ntfs attribute to make non-resident
* @ctx: ntfs search context describing the attribute
*
* Convert a resident ntfs attribute to a non-resident one.
*
* Return 0 on success and -1 on error with errno set to the error code. The
* following error codes are defined:
* EPERM - The attribute is not allowed to be non-resident.
* TODO: others...
*
* NOTE to self: No changes in the attribute list are required to move from
* a resident to a non-resident attribute.
*
* Warning: We do not set the inode dirty and we do not write out anything!
* We expect the caller to do this as this is a fairly low level
* function and it is likely there will be further changes made.
*/
int ntfs_attr_make_non_resident( ntfs_attr *na,
ntfs_attr_search_ctx *ctx )
{
s64 new_allocated_size, bw;
ntfs_volume *vol = na->ni->vol;
ATTR_REC *a = ctx->attr;
runlist *rl;
int mp_size, mp_ofs, name_ofs, arec_size, err;
ntfs_log_trace( "Entering for inode 0x%llx, attr 0x%x.\n", ( unsigned long
long )na->ni->mft_no, na->type );
/* Some preliminary sanity checking. */
if ( NAttrNonResident( na ) )
{
ntfs_log_trace( "Eeek! Trying to make non-resident attribute "
"non-resident. Aborting...\n" );
errno = EINVAL;
return -1;
}
/* Check that the attribute is allowed to be non-resident. */
if ( ntfs_attr_can_be_non_resident( vol, na->type, na->name, na->name_len ) )
return -1;
new_allocated_size = ( le32_to_cpu( a->value_length ) + vol->cluster_size
- 1 ) & ~( vol->cluster_size - 1 );
if ( new_allocated_size > 0 )
{
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 );
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,
BOOL force_non_resident )
{
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 ) && !force_non_resident )
{
/* 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 ( force_non_resident )
{
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( na, newsize );
}
else if ( errno != ENOSPC && errno != EPERM )
{
err = errno;
ntfs_log_perror( "Failed to make attribute non-resident" );
goto put_err_out;
}
/* Try to make other attributes non-resident and retry each time. */
ntfs_attr_init_search_ctx( ctx, NULL, na->ni->mrec );
while ( !ntfs_attr_lookup( AT_UNUSED, NULL, 0, 0, 0, NULL, 0, ctx ) )
{
ntfs_attr *tna;
ATTR_RECORD *a;
a = ctx->attr;
if ( a->non_resident )
continue;
/*
* Check out whether convert is reasonable. Assume that mapping
* pairs will take 8 bytes.
*/
if ( le32_to_cpu( a->length ) <= offsetof( ATTR_RECORD,
compressed_size ) + ( ( a->name_length *
sizeof( ntfschar ) + 7 ) & ~7 ) + 8 )
continue;
tna = ntfs_attr_open( na->ni, a->type, ( ntfschar* )( ( u8* )a +
le16_to_cpu( a->name_offset ) ), a->name_length );
if ( !tna )
{
err = errno;
ntfs_log_perror( "Couldn't open attribute" );
goto put_err_out;
}
if ( ntfs_attr_make_non_resident( tna, ctx ) )
{
ntfs_attr_close( tna );
continue;
}
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, force_non_resident );
}
/* 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, force_non_resident );
}
/*
* 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, force_non_resident );
}
/* 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, force_non_resident );
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, FALSE );
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, TRUE );
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 )
{
err = errno;
ntfs_log_perror( "Eeek! Failed to release allocated clusters" );
ntfs_log_trace( "Ignoring error and leaving behind wasted "
"clusters.\n" );
}
/* Throw away the now unused runlist. */
free( na->rl );
na->rl = NULL;
/* Update in-memory struct ntfs_attr. */
NAttrClearNonResident( na );
NAttrClearSparse( na );
NAttrClearEncrypted( na );
na->initialized_size = na->data_size;
na->allocated_size = na->compressed_size = ( na->data_size + 7 ) & ~7;
na->compression_block_size = 0;
na->compression_block_size_bits = na->compression_block_clusters = 0;
return 0;
}
/*
* If we are in the first extent, then set/clean sparse bit,
* update allocated and compressed size.
*/
static int ntfs_attr_update_meta( ATTR_RECORD *a, ntfs_attr *na, MFT_RECORD *m,
ntfs_attr_search_ctx *ctx )
{
int sparse, ret = 0;
ntfs_log_trace( "Entering for inode 0x%llx, attr 0x%x\n",
( unsigned long long )na->ni->mft_no, na->type );
if ( a->lowest_vcn )
goto out;
a->allocated_size = cpu_to_sle64( na->allocated_size );
/* Update sparse bit. */
sparse = ntfs_rl_sparse( na->rl );
if ( sparse == -1 )
{
errno = EIO;
goto error;
}
/* Attribute become sparse. */
if ( sparse && !( a->flags & ( ATTR_IS_SPARSE | ATTR_IS_COMPRESSED ) ) )
{
/*
* Move attribute to another mft record, if attribute is too
* small to add compressed_size field to it and we have no
* free space in the current mft record.
*/
if ( ( le32_to_cpu( a->length ) -
le16_to_cpu( a->mapping_pairs_offset ) == 8 )
&& !( le32_to_cpu( m->bytes_allocated ) -
le32_to_cpu( m->bytes_in_use ) ) )
{
if ( !NInoAttrList( na->ni ) )
{
ntfs_attr_put_search_ctx( ctx );
if ( ntfs_inode_add_attrlist( na->ni ) )
goto leave;
goto retry;
}
if ( ntfs_attr_record_move_away( ctx, 8 ) )
{
ntfs_log_perror( "Failed to move attribute" );
goto error;
}
ntfs_attr_put_search_ctx( ctx );
goto retry;
}
if ( !( le32_to_cpu( a->length ) - le16_to_cpu(
a->mapping_pairs_offset ) ) )
{
errno = EIO;
ntfs_log_perror( "Mapping pairs space is 0" );
goto error;
}
NAttrSetSparse( na );
a->flags |= ATTR_IS_SPARSE;
a->compression_unit = STANDARD_COMPRESSION_UNIT; /* Windows
set it so, even if attribute is not actually compressed. */
memmove( ( u8* )a + le16_to_cpu( a->name_offset ) + 8,
( u8* )a + le16_to_cpu( a->name_offset ),
a->name_length * sizeof( ntfschar ) );
a->name_offset = cpu_to_le16( le16_to_cpu( a->name_offset ) + 8 );
a->mapping_pairs_offset =
cpu_to_le16( le16_to_cpu( a->mapping_pairs_offset ) + 8 );
}
/* Attribute no longer sparse. */
if ( !sparse && ( a->flags & ATTR_IS_SPARSE ) &&
!( a->flags & ATTR_IS_COMPRESSED ) )
{
NAttrClearSparse( na );
a->flags &= ~ATTR_IS_SPARSE;
a->compression_unit = 0;
memmove( ( u8* )a + le16_to_cpu( a->name_offset ) - 8,
( u8* )a + le16_to_cpu( a->name_offset ),
a->name_length * sizeof( ntfschar ) );
if ( le16_to_cpu( a->name_offset ) >= 8 )
a->name_offset = cpu_to_le16( le16_to_cpu( a->name_offset ) - 8 );
a->mapping_pairs_offset =
cpu_to_le16( le16_to_cpu( a->mapping_pairs_offset ) - 8 );
}
/* Update compressed size if required. */
if ( sparse || ( na->data_flags & ATTR_COMPRESSION_MASK ) )
{
s64 new_compr_size;
new_compr_size = ntfs_rl_get_compressed_size( na->ni->vol, na->rl );
if ( new_compr_size == -1 )
goto error;
na->compressed_size = new_compr_size;
a->compressed_size = cpu_to_sle64( new_compr_size );
}
/*
* Set FILE_NAME dirty flag, to update sparse bit and
* allocated size in the index.
*/
if ( na->type == AT_DATA && na->name == AT_UNNAMED )
{
if ( sparse || ( na->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 )
{
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 ( 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, 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 );
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 )
{
LCN lcn_seek_from;
VCN first_free_vcn;
ntfs_volume *vol;
ntfs_attr_search_ctx *ctx;
runlist *rl, *rln;
s64 org_alloc_size;
int err;
ntfs_log_trace( "Inode %lld, attr 0x%x, new size %lld old size %lld\n",
( unsigned long long )na->ni->mft_no, na->type,
( long long )newsize, ( long long )na->data_size );
vol = na->ni->vol;
/*
* Check the attribute type and the corresponding maximum size
* against @newsize and fail if @newsize is too big.
*/
if ( ntfs_attr_size_bounds_check( vol, na->type, newsize ) < 0 )
{
if ( errno == ENOENT )
errno = EIO;
ntfs_log_perror( "%s: bounds check failed", __FUNCTION__ );
return -1;
}
/* Save for future use. */
org_alloc_size = na->allocated_size;
/* The first cluster outside the new allocation. */
first_free_vcn = ( newsize + vol->cluster_size - 1 ) >>
vol->cluster_size_bits;
/*
* Compare the new allocation with the old one and only allocate
* clusters if there is a change.
*/
if ( ( na->allocated_size >> vol->cluster_size_bits ) < first_free_vcn )
{
if ( ntfs_attr_map_whole_runlist( na ) )
{
ntfs_log_perror( "ntfs_attr_map_whole_runlist failed" );
return -1;
}
/*
* If we extend $DATA attribute on NTFS 3+ volume, we can add
* sparse runs instead of real allocation of clusters.
*/
if ( na->type == AT_DATA && vol->major_ver >= 3 )
{
rl = ntfs_malloc( 0x1000 );
if ( !rl )
return -1;
rl[0].vcn = ( na->allocated_size >>
vol->cluster_size_bits );
rl[0].lcn = LCN_HOLE;
rl[0].length = first_free_vcn -
( na->allocated_size >> vol->cluster_size_bits );
rl[1].vcn = first_free_vcn;
rl[1].lcn = LCN_ENOENT;
rl[1].length = 0;
}
else
{
/*
* Determine first after last LCN of attribute.
* We will start seek clusters from this LCN to avoid
* fragmentation. If there are no valid LCNs in the
* attribute let the cluster allocator choose the
* starting LCN.
*/
lcn_seek_from = -1;
if ( na->rl->length )
{
/* Seek to the last run list element. */
for ( rl = na->rl; ( rl + 1 )->length; rl++ )
;
/*
* If the last LCN is a hole or similar seek
* back to last valid LCN.
*/
while ( rl->lcn < 0 && rl != na->rl )
rl--;
/*
* Only set lcn_seek_from it the LCN is valid.
*/
if ( rl->lcn >= 0 )
lcn_seek_from = rl->lcn + rl->length;
}
rl = ntfs_cluster_alloc( vol, na->allocated_size >>
vol->cluster_size_bits, first_free_vcn -
( na->allocated_size >>
vol->cluster_size_bits ), lcn_seek_from,
DATA_ZONE );
if ( !rl )
{
ntfs_log_perror( "Cluster allocation failed "
"(%lld)",
( long long )first_free_vcn -
( ( long long )na->allocated_size >>
vol->cluster_size_bits ) );
return -1;
}
}
/* Append new clusters to attribute runlist. */
rln = ntfs_runlists_merge( na->rl, rl );
if ( !rln )
{
/* Failed, free just allocated clusters. */
err = errno;
ntfs_log_perror( "Run list merge failed" );
ntfs_cluster_free_from_rl( vol, rl );
free( rl );
errno = err;
return -1;
}
na->rl = rln;
/* Prepare to mapping pairs update. */
na->allocated_size = first_free_vcn << vol->cluster_size_bits;
/* Write mapping pairs for new runlist. */
if ( ntfs_attr_update_mapping_pairs( na, 0 /*na->allocated_size >>
vol->cluster_size_bits*/ ) )
{
err = errno;
ntfs_log_perror( "Mapping pairs update failed" );
goto rollback;
}
}
ctx = ntfs_attr_get_search_ctx( na->ni, NULL );
if ( !ctx )
{
err = errno;
if ( na->allocated_size == org_alloc_size )
{
errno = err;
return -1;
}
else
goto rollback;
}
if ( ntfs_attr_lookup( na->type, na->name, na->name_len, CASE_SENSITIVE,
0, NULL, 0, ctx ) )
{
err = errno;
ntfs_log_perror( "Lookup of first attribute extent failed" );
if ( err == ENOENT )
err = EIO;
if ( na->allocated_size != org_alloc_size )
{
ntfs_attr_put_search_ctx( ctx );
goto rollback;
}
else
goto put_err_out;
}
/* Update data size. */
na->data_size = newsize;
ctx->attr->data_size = cpu_to_sle64( newsize );
/* Update data size in the index. */
if ( na->ni->mrec->flags & MFT_RECORD_IS_DIRECTORY )
{
if ( na->type == AT_INDEX_ROOT && na->name == NTFS_INDEX_I30 )
{
na->ni->data_size = na->data_size;
na->ni->allocated_size = na->allocated_size;
set_nino_flag( na->ni, KnownSize );
}
}
else
{
if ( na->type == AT_DATA && na->name == AT_UNNAMED )
{
na->ni->data_size = na->data_size;
NInoFileNameSetDirty( na->ni );
}
}
/* Set the inode dirty so it is written out later. */
ntfs_inode_mark_dirty( ctx->ntfs_ino );
/* Done! */
ntfs_attr_put_search_ctx( ctx );
return 0;
rollback:
/* Free allocated clusters. */
if ( ntfs_cluster_free( vol, na, org_alloc_size >>
vol->cluster_size_bits, -1 ) < 0 )
{
err = EIO;
ntfs_log_perror( "Leaking clusters" );
}
/* Now, truncate the runlist itself. */
if ( ntfs_rl_truncate( &na->rl, org_alloc_size >>
vol->cluster_size_bits ) )
{
/*
* Failed to truncate the runlist, so just throw it away, it
* will be mapped afresh on next use.
*/
free( na->rl );
na->rl = NULL;
ntfs_log_perror( "Couldn't truncate runlist. Rollback failed" );
}
else
{
/* Prepare to mapping pairs update. */
na->allocated_size = org_alloc_size;
/* Restore mapping pairs. */
if ( ntfs_attr_update_mapping_pairs( na, 0 /*na->allocated_size >>
vol->cluster_size_bits*/ ) )
{
ntfs_log_perror( "Failed to restore old mapping pairs" );
}
}
errno = err;
return -1;
put_err_out:
ntfs_attr_put_search_ctx( ctx );
errno = err;
return -1;
}
static int ntfs_non_resident_attr_expand( ntfs_attr *na, const s64 newsize )
{
int ret;
ntfs_log_enter( "Entering\n" );
ret = ntfs_non_resident_attr_expand_i( na, newsize );
ntfs_log_leave( "\n" );
return ret;
}
/**
* ntfs_attr_truncate - resize an ntfs attribute
* @na: open ntfs attribute to resize
* @newsize: new size (in bytes) to which to resize the attribute
*
* Change the size of an open ntfs attribute @na to @newsize bytes. If the
* attribute is made bigger and the attribute is resident the newly
* "allocated" space is cleared and if the attribute is non-resident the
* newly allocated space is marked as not initialised and no real allocation
* on disk is performed.
*
* On success return 0.
* On error return values are:
* STATUS_RESIDENT_ATTRIBUTE_FILLED_MFT
* STATUS_ERROR - otherwise
* The following error codes are defined:
* EINVAL - Invalid arguments were passed to the function.
* EOPNOTSUPP - The desired resize is not implemented yet.
* EACCES - Encrypted attribute.
*/
int ntfs_attr_truncate( ntfs_attr *na, const s64 newsize )
{
int ret = STATUS_ERROR;
s64 fullsize;
BOOL compressed;
if ( !na || newsize < 0 ||
( na->ni->mft_no == FILE_MFT && na->type == AT_DATA ) )
{
ntfs_log_trace( "Invalid arguments passed.\n" );
errno = EINVAL;
return STATUS_ERROR;
}
ntfs_log_enter( "Entering for inode %lld, attr 0x%x, size %lld\n",
( unsigned long long )na->ni->mft_no, na->type,
( long long )newsize );
if ( na->data_size == newsize )
{
ntfs_log_trace( "Size is already ok\n" );
ret = STATUS_OK;
goto out;
}
/*
* Encrypted attributes are not supported. We return access denied,
* which is what Windows NT4 does, too.
*/
if ( na->data_flags & ATTR_IS_ENCRYPTED )
{
errno = EACCES;
ntfs_log_trace( "Cannot truncate encrypted attribute\n" );
goto out;
}
/*
* TODO: Implement making handling of compressed attributes.
* Currently we can only expand the attribute or delete it,
* and only for ATTR_IS_COMPRESSED. This is however possible
* for resident attributes when there is no open fuse context
* (important case : $INDEX_ROOT:$I30)
*/
compressed = ( na->data_flags & ATTR_COMPRESSION_MASK )
!= const_cpu_to_le16( 0 );
if ( compressed
&& NAttrNonResident( na )
&& ( ( na->data_flags & ATTR_COMPRESSION_MASK ) != ATTR_IS_COMPRESSED ) )
{
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 );
else
ret = ntfs_non_resident_attr_shrink( na, fullsize );
}
else
ret = ntfs_resident_attr_resize( na, newsize );
out:
ntfs_log_leave( "Return status %d\n", ret );
return ret;
}
/*
* Stuff a hole in a compressed file
*
* An unallocated hole must be aligned on compression block size.
* If needed current block and target block are stuffed with zeroes.
*
* Returns 0 if succeeded,
* -1 if it failed (as explained in errno)
*/
static int stuff_hole( ntfs_attr *na, const s64 pos )
{
s64 size;
s64 begin_size;
s64 end_size;
char *buf;
int ret;
ret = 0;
/*
* If the attribute is resident, the compression block size
* is not defined yet and we can make no decision.
* So we first try resizing to the target and if the
* attribute is still resident, we're done
*/
if ( !NAttrNonResident( na ) )
{
ret = ntfs_resident_attr_resize( na, pos );
if ( !ret && !NAttrNonResident( na ) )
na->initialized_size = na->data_size = pos;
}
if ( !ret && NAttrNonResident( na ) )
{
/* does the hole span over several compression block ? */
if ( ( pos ^ na->initialized_size )
& ~( na->compression_block_size - 1 ) )
{
begin_size = ( ( na->initialized_size - 1 )
| ( na->compression_block_size - 1 ) )
+ 1 - na->initialized_size;
end_size = pos & ( na->compression_block_size - 1 );
size = ( begin_size > end_size ? begin_size : end_size );
}
else
{
/* short stuffing in a single compression block */
begin_size = size = pos - na->initialized_size;
end_size = 0;
}
if ( size )
buf = ( char* )ntfs_malloc( size );
else
buf = ( char* )NULL;
if ( buf || !size )
{
memset( buf, 0, size );
/* stuff into current block */
if ( begin_size
&& ( ntfs_attr_pwrite( na,
na->initialized_size, begin_size, buf )
!= begin_size ) )
ret = -1;
/* create an unstuffed hole */
if ( !ret
&& ( ( na->initialized_size + end_size ) < pos )
&& ntfs_non_resident_attr_expand( na,
pos - end_size ) )
ret = -1;
else
na->initialized_size
= na->data_size = pos - end_size;
/* stuff into the target block */
if ( !ret && end_size
&& ( ntfs_attr_pwrite( na,
na->initialized_size, end_size, buf )
!= end_size ) )
ret = -1;
if ( buf )
free( buf );
}
else
ret = -1;
}
/* make absolutely sure we have reached the target */
if ( !ret && ( na->initialized_size != pos ) )
{
ntfs_log_error( "Failed to stuff a compressed file"
"target %lld reached %lld\n",
( long long )pos, ( long long )na->initialized_size );
errno = EIO;
ret = -1;
}
return ( ret );
}
/**
* ntfs_attr_readall - read the entire data from an ntfs attribute
* @ni: open ntfs inode in which the ntfs attribute resides
* @type: attribute type
* @name: attribute name in little endian Unicode or AT_UNNAMED or NULL
* @name_len: length of attribute @name in Unicode characters (if @name given)
* @data_size: if non-NULL then store here the data size
*
* This function will read the entire content of an ntfs attribute.
* If @name is AT_UNNAMED then look specifically for an unnamed attribute.
* If @name is NULL then the attribute could be either named or not.
* In both those cases @name_len is not used at all.
*
* On success a buffer is allocated with the content of the attribute
* and which needs to be freed when it's not needed anymore. If the
* @data_size parameter is non-NULL then the data size is set there.
*
* On error NULL is returned with errno set to the error code.
*/
void *ntfs_attr_readall( ntfs_inode *ni, const ATTR_TYPES type,
ntfschar *name, u32 name_len, s64 *data_size )
{
ntfs_attr *na;
void *data, *ret = NULL;
s64 size;
ntfs_log_enter( "Entering\n" );
na = ntfs_attr_open( ni, type, name, name_len );
if ( !na )
{
ntfs_log_perror( "ntfs_attr_open failed" );
goto err_exit;
}
data = ntfs_malloc( na->data_size );
if ( !data )
goto out;
size = ntfs_attr_pread( na, 0, na->data_size, data );
if ( size != na->data_size )
{
ntfs_log_perror( "ntfs_attr_pread failed" );
free( data );
goto out;
}
ret = data;
if ( data_size )
*data_size = size;
out:
ntfs_attr_close( na );
err_exit:
ntfs_log_leave( "\n" );
return ret;
}
int ntfs_attr_exist( ntfs_inode *ni, const ATTR_TYPES type, ntfschar *name,
u32 name_len )
{
ntfs_attr_search_ctx *ctx;
int ret;
ntfs_log_trace( "Entering\n" );
ctx = ntfs_attr_get_search_ctx( ni, NULL );
if ( !ctx )
return 0;
ret = ntfs_attr_lookup( type, name, name_len, CASE_SENSITIVE, 0, NULL, 0,
ctx );
ntfs_attr_put_search_ctx( ctx );
return !ret;
}
int ntfs_attr_remove( ntfs_inode *ni, const ATTR_TYPES type, ntfschar *name,
u32 name_len )
{
ntfs_attr *na;
int ret;
ntfs_log_trace( "Entering\n" );
if ( !ni )
{
ntfs_log_error( "%s: NULL inode pointer", __FUNCTION__ );
errno = EINVAL;
return -1;
}
na = ntfs_attr_open( ni, type, name, name_len );
if ( !na )
{
/* do not log removal of non-existent stream */
if ( type != AT_DATA )
{
ntfs_log_perror( "Failed to open attribute 0x%02x of inode "
"0x%llx", type, ( unsigned long long )ni->mft_no );
}
return -1;
}
ret = ntfs_attr_rm( na );
if ( ret )
ntfs_log_perror( "Failed to remove attribute 0x%02x of inode "
"0x%llx", type, ( unsigned long long )ni->mft_no );
ntfs_attr_close( na );
return ret;
}
/* Below macros are 32-bit ready. */
#define BCX(x) ((x) - (((x) >> 1) & 0x77777777) - \
(((x) >> 2) & 0x33333333) - \
(((x) >> 3) & 0x11111111))
#define BITCOUNT(x) (((BCX(x) + (BCX(x) >> 4)) & 0x0F0F0F0F) % 255)
static u8 *ntfs_init_lut256( void )
{
int i;
u8 *lut;
lut = ntfs_malloc( 256 );
if ( lut )
for ( i = 0; i < 256; i++ )
*( lut + i ) = 8 - BITCOUNT( i );
return lut;
}
s64 ntfs_attr_get_free_bits( ntfs_attr *na )
{
u8 *buf, *lut;
s64 br = 0;
s64 total = 0;
s64 nr_free = 0;
lut = ntfs_init_lut256();
if ( !lut )
return -1;
buf = ntfs_malloc( 65536 );
if ( !buf )
goto out;
while ( 1 )
{
u32 *p;
br = ntfs_attr_pread( na, total, 65536, buf );
if ( br <= 0 )
break;
total += br;
p = ( u32 * )buf + br / 4 - 1;
for ( ; ( u8 * )p >= buf; p-- )
{
nr_free += lut[ *p & 255] +
lut[( *p >> 8 ) & 255] +
lut[( *p >> 16 ) & 255] +
lut[( *p >> 24 ) ];
}
switch ( br % 4 )
{
case 3: nr_free += lut[*( buf + br - 3 )];
case 2: nr_free += lut[*( buf + br - 2 )];
case 1: nr_free += lut[*( buf + br - 1 )];
}
}
free( buf );
out:
free( lut );
if ( !total || br < 0 )
return -1;
return nr_free;
}