/**
* attrib.c - Attribute handling code. Part of the Linux-NTFS project.
*
* Copyright (c) 2000-2006 Anton Altaparmakov
* Copyright (c) 2002-2005 Richard Russon
* Copyright (c) 2002-2006 Szabolcs Szakacsits
* Copyright (c) 2004-2007 Yura Pakhuchiy
*
* 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.
*
* 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 Linux-NTFS
* 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
#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 "support.h"
#include "crypto.h"
/**
* ntfs_get_attribute_value_length - Find the length of an attribute
* @a:
*
* Description...
*
* Returns:
*/
{
if (!a) {
return 0;
}
errno = 0;
if (a->non_resident)
}
/**
* ntfs_get_attribute_value - Get a copy of an attribute
* @vol:
* @a:
* @b:
*
* Description...
*
* Returns:
*/
const ATTR_RECORD *a, u8 *b)
{
int i;
/* Sanity checks. */
if (!vol || !a || !b) {
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.
*/
ntfs_log_error("Non-zero (%04x) attribute flags. Cannot handle "
errno = EOPNOTSUPP;
return 0;
}
if (!a->non_resident) {
/* Attribute is resident. */
/* Sanity check. */
> le32_to_cpu(a->length)) {
return 0;
}
errno = 0;
}
/* Attribute is not resident. */
/* If no data, return 0. */
errno = 0;
return 0;
}
/*
* FIXME: What about attribute lists?!? (AIA)
*/
/* Decompress the mapping pairs array into a runlist. */
if (!rl) {
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. */
/*
* 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.
*/
if (!intbuf) {
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.
*/
if (r == -1) {
vol->cluster_size_bits) {
"input data.\n");
} else {
}
return 0;
}
total);
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.
*/
b + total);
if (r == -1) {
"input data.\n");
} else {
}
return 0;
}
total += r;
}
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.
*/
{
if (name)
else
}
/**
* 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.
*/
const u8 compression_unit)
{
if (!NAttrInitialized(na)) {
if (non_resident)
if (compressed)
if (encrypted)
if (sparse)
if (compressed || sparse) {
}
}
}
/**
* 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.
*/
{
ATTR_RECORD *a;
int err;
ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x.\n",
return NULL;
}
/* Check cache, maybe this attribute already opened? */
ntfs_log_trace("Found this attribute in cache, "
"increment reference count and "
"return it.\n");
tmp_na->nr_references++;
return tmp_na;
}
}
/* Search failed. Properly open attrbute. */
if (!na)
return NULL;
if (!name) {
return NULL;
}
}
if (!ctx) {
goto err_out;
}
goto put_err_out;
}
/*
* 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 (!name) {
if (a->name_length) {
a->name_offset)), a->name_length);
if (!name) {
goto put_err_out;
}
name_len = a->name_length;
} else {
name = AT_UNNAMED;
name_len = 0;
}
}
if (a->non_resident) {
} else {
}
if (NAttrEncrypted(na))
return na;
return NULL;
}
/**
* ntfs_attr_close - free an ntfs attribute structure
* @na: ntfs attribute structure to free
*
* Release all memory associated with the ntfs attribute @na and then release
* @na itself.
*/
{
if (!na)
return;
na->nr_references--;
if (na->nr_references) {
ntfs_log_trace("There are %d more references left to "
return;
}
ntfs_log_trace("There are no more references left to this attribute\n");
if (NAttrEncrypted(na))
/* Don't release if using an internal constant. */
}
/**
* 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.
*/
{
ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x, vcn 0x%llx.\n",
return 0;
if (!ctx)
return -1;
/* Find the attribute in the mft record. */
/* Decode the runlist. */
if (rl) {
return 0;
}
}
return -1;
}
/**
* ntfs_attr_map_runlist_range - map (a part of) a runlist of an ntfs attribute
* @na: ntfs attribute for which to map (part of) a runlist
* @from_vcn: map runlist part starting this vcn
* @to_vcn: map runlist part ending this vcn
*
* Map the part of a runlist from containing the @from_vcn to containing the
* @to_vcn of an ntfs attribute @na. It is OK for @to_vcn to be beyond last run.
*
* Return 0 on success and -1 on error with errno set to the error code.
*/
{
ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x, "
"from_vcn 0x%llx, to_vcn 0x%llx.\n",
/* Map extent with @from_vcn. */
goto err_out;
/* Skip not interesting to us runs. */
rl++;
continue;
}
/* We reached the end of runlist, just exit. */
break;
/* Check for errors. */
goto err_out;
}
/* Runlist is not mapped here. */
if (!ctx) {
if (!ctx)
goto err_out;
}
/* Find the attribute in the mft record. */
ctx))
goto err_out;
/* Decode the runlist. */
if (!rl)
goto err_out;
}
ntfs_log_trace("Done.\n");
return 0;
ntfs_log_trace("Failed.\n");
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.
*/
{
ATTR_RECORD *a;
int err;
ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x.\n",
if (!ctx)
return -1;
/* Map all attribute extents one by one. */
a = NULL;
while (1) {
int not_mapped = 0;
not_mapped = 1;
break;
if (not_mapped) {
/* Decode the runlist. */
if (!rl)
goto err_out;
}
/* Are we in the first extent? */
if (!next_vcn) {
if (a->u.nonres.lowest_vcn) {
ntfs_log_trace("First extent of attribute has "
"non zero lowest_vcn. "
"Inode is corrupt.\n");
goto err_out;
}
/* Get the last vcn in the attribute. */
}
/* Get the lowest vcn for the next extent. */
/* Only one extent or error, which we catch below. */
if (next_vcn <= 0) {
break;
}
/* Avoid endless loops due to corruption. */
ntfs_log_trace("Inode has corrupt attribute list "
"attribute.\n");
goto err_out;
}
}
if (!a) {
ntfs_log_trace("Attribute not found. "
"Inode is corrupt.\n");
else
ntfs_log_trace("Inode is corrupt.\n");
goto err_out;
}
ntfs_log_trace("Failed to load the complete run list for the "
"attribute. Bug or corrupt inode.\n");
ntfs_log_trace("highest_vcn = 0x%llx, last_vcn - 1 = 0x%llx\n",
(long long)highest_vcn,
(long long)last_vcn - 1);
goto err_out;
}
return 0;
return -1;
goto out_now;
}
/**
* ntfs_attr_vcn_to_lcn - convert a vcn into a lcn given an ntfs attribute
* @na: ntfs attribute whose runlist to use for conversion
* @vcn: vcn to convert
*
* Convert the virtual cluster number @vcn of an attribute into a logical
* cluster number (lcn) of a device using the runlist @na->rl to map vcns to
* their corresponding lcns.
*
* If the @vcn is not mapped yet, attempt to map the attribute extent
* containing the @vcn and retry the vcn to lcn conversion.
*
* Since lcns must be >= 0, we use negative return values with special meaning:
*
* Return value Meaning / Description
* ==========================================
* -1 = LCN_HOLE Hole / not allocated on disk.
* -3 = LCN_ENOENT There is no such vcn in the attribute.
* -4 = LCN_EINVAL Input parameter error.
* -5 = LCN_EIO Corrupt fs, disk i/o error, or not enough memory.
*/
{
return (LCN)LCN_EINVAL;
ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x.\n", (unsigned long
/* Convert vcn to lcn. If that fails map the runlist and retry once. */
if (lcn >= 0)
return lcn;
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...
*/
/* 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.
*/
{
return NULL;
}
ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x, vcn %llx\n",
(long long)vcn);
if (!rl)
goto map_rl;
goto map_rl;
return rl;
break;
}
rl++;
}
case (LCN)LCN_RL_NOT_MAPPED:
goto map_rl;
case (LCN)LCN_ENOENT:
break;
case (LCN)LCN_EINVAL:
break;
default:
break;
}
return NULL;
/* The @vcn is in an unmapped region, map the runlist and retry. */
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.
*/
return NULL;
}
/**
* ntfs_attr_pread - read from an attribute specified by an ntfs_attr structure
* @na: ntfs attribute to read from
* @pos: byte position in the attribute to begin reading from
* @count: number of bytes to read
* @b: output data buffer
*
* This function will read @count bytes starting at offset @pos from the ntfs
* attribute @na into the data buffer @b.
*
* On success, return the number of successfully read bytes. If this number is
* lower than @count this means that the read reached end of file or that an
* error was encountered during the read so that the read is partial. 0 means
* end of file or nothing was read (also return 0 when @count is 0).
*
* On error and nothing has been read, return -1 with errno set appropriately
* to the return code of ntfs_pread(), or to EINVAL in case of invalid
* arguments.
*/
{
ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x, pos 0x%llx, "
return -1;
}
/*
* If this is a compressed attribute it needs special treatment, but
* only if it is non-resident.
*/
/*
* Encrypted non-resident attributes are not supported. We return
* access denied, which is what Windows NT4 does, too.
*/
if (!count)
return 0;
/* Truncate reads beyond end of attribute. */
return 0;
}
/* If it is a resident attribute, get the value from the mft record. */
if (!NAttrNonResident(na)) {
char *val;
if (!ctx)
return -1;
int eo;
return -1;
}
goto res_err_out;
}
return count;
}
/* Zero out reads beyond initialized size. */
return count;
}
}
/* Find the runlist element containing the vcn. */
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.
*/
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.
*/
if (!rl) {
goto rl_err_out;
}
/* Needed for case when runs merged. */
}
goto rl_err_out;
goto rl_err_out;
/* It is a hole, just zero the matching @b range. */
/* Update progress counters. */
continue;
}
/* It is a real lcn, read it into @dst. */
ofs);
ntfs_log_trace("Reading 0x%llx bytes from vcn 0x%llx, "
/* If everything ok, update progress counters and continue. */
if (br > 0) {
continue;
}
/* If the syscall was interrupted, try again. */
goto retry;
if (total)
return total;
if (!br)
return -1;
}
/* Finally, return the number of bytes read. */
if (total)
return total;
return -1;
}
/**
* 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.
*/
{
int eo;
struct {
} need_to = { 0, 0, 0 };
ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x, pos 0x%llx, "
return -1;
}
/*
* Encrypted non-resident attributes are not supported. We return
* access denied, which is what Windows NT4 does, too.
*/
return -1;
}
/* If this is a compressed attribute it needs special treatment. */
if (NAttrCompressed(na)) {
// TODO: Implement writing compressed attributes! (AIA)
// return ntfs_attr_pwrite_compressed(ntfs_attr *na,
// const s64 pos, s64 count, void *b);
errno = EOPNOTSUPP;
return -1;
}
if (!count)
return 0;
/* If the write reaches beyond the end, extend the attribute. */
ntfs_log_trace("Attribute extend failed.\n");
return -1;
}
}
/* If it is a resident attribute, write the data to the mft record. */
if (!NAttrNonResident(na)) {
char *val;
if (!ctx)
goto err_out;
goto err_out;
goto err_out;
}
/*
* NOTE: We are in a bad state at this moment. We have
* dirtied the mft record but we failed to commit it to
* disk. Since we have read the mft record ok before,
* it is unlikely to fail writing it, so is ok to just
* return error here... (AIA)
*/
goto err_out;
}
return count;
}
total = 0;
/* Handle writes beyond initialized_size. */
/*
* Map runlist between initialized size and place we start
* writing at.
*/
goto err_out;
/* Set initialized_size to @pos + @count. */
if (!ctx)
goto err_out;
goto err_out;
/* If write starts beyond initialized_size, zero the gap. */
goto err_out;
/*
* Undo the change in the in-memory copy and send it
* back for writing.
*/
goto err_out;
}
/*
* 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.
*/
}
/* Find the runlist element containing the vcn. */
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.
*/
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.
*/
if (!rl) {
goto rl_err_out;
}
/* Needed for case when runs merged. */
}
goto rl_err_out;
}
goto rl_err_out;
}
/* Instantiate the hole. */
ntfs_log_trace("Instantiate hole with vcn 0x%llx.\n",
cur_vcn);
/*
* Map whole runlist to be able update mapping pairs
* later.
*/
goto err_out;
/*
* Restore @rl, it probably get lost during runlist
* mapping.
*/
if (!rl) {
ntfs_log_error("BUG! Failed to find run after "
"mapping whole runlist. Please "
"report to the %s.\n",
goto err_out;
}
/*
* Search backwards to find the best lcn to start
* seek from.
*/
rlc--;
break;
}
}
if (lcn_seek_from == -1) {
/* Backwards search failed, search forwards. */
rlc++;
if (lcn_seek_from < -1)
lcn_seek_from = -1;
break;
}
}
}
/* Allocate clusters to instantiate the hole. */
if (!rlc) {
ntfs_log_trace("Failed to allocate clusters "
"for hole instantiating.\n");
goto err_out;
}
/* Merge runlists. */
if (!rl) {
ntfs_log_trace("Failed to merge runlists.\n");
ntfs_log_trace("Failed to free just "
"allocated clusters. Leaving "
"inconsistent metadata. "
"Run chkdsk\n");
}
goto err_out;
}
if (update_from == -1)
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("BUG! Failed to find run after "
"instantiating. Please report "
"to the %s.\n", NTFS_DEV_LIST);
goto err_out;
}
/* If leaved part of the hole go to the next run. */
rl++;
/* Now LCN shouldn't be less than 0. */
ntfs_log_error("BUG! LCN is lesser than 0. "
"Please report to the %s.\n",
goto err_out;
}
/*
* Clusters that replaced hole are merged with
* previous run, so we need to update offset.
*/
}
/*
* We left part of the hole, so update we need
* to update offset
*/
}
/*
* Clear region between start of @rl->vcn cluster and
* @ofs if necessary.
*/
goto err_out;
}
/* It is a real lcn, write it to the volume. */
ofs);
ntfs_log_trace("Writing 0x%llx bytes to vcn 0x%llx, lcn 0x%llx,"
ofs);
if (!NVolReadOnly(vol)) {
PAGE_SIZE here. Eg., on IA64. */
/*
* Write 4096 size blocks if it's possible. This will
* cause the kernel not to seek and read disk blocks for
* filling the end of the buffer which increases write
* speed.
*/
na->initialized_size) {
char *cb;
~(bsize - 1);
if (!cb)
goto err_out;
cb);
} else
b);
} else
/* If everything ok, update progress counters and continue. */
if (written > 0) {
continue;
}
/* If the syscall was interrupted, try again. */
goto retry;
if (!written)
goto rl_err_out;
}
done:
if (ctx)
/* Update mapping pairs if needed. */
if (need_to.update_mapping_pairs) {
/* FIXME: We want rollback here. */
ntfs_log_perror("%s(): Failed to update mapping pairs. "
"Leaving inconsistent metadata. "
"Run chkdsk!", "ntfs_attr_pwrite");
return -1;
}
}
/* Finally, return the number of bytes written. */
return total;
if (total) {
if (need_to.undo_initialized_size) {
goto done;
/*
* TODO: Need to try to change initialized_size. If it
* succeeds goto done, otherwise goto err_out. (AIA)
*/
errno = EOPNOTSUPP;
goto err_out;
}
goto done;
}
if (need_to.undo_initialized_size) {
int err;
err = 0;
if (!ctx) {
if (!ctx)
err = 1;
} else
if (ctx) {
if (!err) {
}
}
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)
/* Update mapping pairs if needed. */
/* Restore original data_size if needed. */
ntfs_log_trace("Failed to restore data_size.\n");
return -1;
}
/**
* 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.
*/
{
ntfs_log_trace("Entering for inode 0x%llx, attr type 0x%x, "
return -1;
}
if (br <= 0)
return br;
/* 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.
*/
{
ntfs_log_trace("Entering for inode 0x%llx, attr type 0x%x, "
return -1;
}
if (!bk_cnt)
return 0;
/* Prepare data for writing. */
for (i = 0; i < bk_cnt; ++i) {
int err;
if (err < 0) {
/* Abort write at this position. */
if (!i)
return err;
bk_cnt = i;
break;
}
}
/* Write the prepared data. */
/* Quickly deprotect the data again. */
for (i = 0; i < bk_cnt; ++i)
bk_size));
if (written <= 0)
return written;
/* Finally, return the number of complete blocks written. */
}
/**
* 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!
*/
{
ATTR_RECORD *a;
} else {
return -1;
}
upcase_len = 0;
}
/*
* Iterate over attributes in mft record starting at @ctx->attr, or the
* attribute following that, if @ctx->is_first is TRUE.
*/
} else
break;
le32_to_cpu(type))) ||
return -1;
}
if (!a->length)
break;
/* If this is an enumeration return this attribute. */
return 0;
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) {
return -1;
}
register int rc;
(ntfschar*)((char*)a +
le16_to_cpu(a->name_offset)),
upcase, upcase_len);
/*
* If @name collates before a->name, there is no
* matching attribute.
*/
if (rc == -1) {
return -1;
}
/* If the strings are not equal, continue search. */
if (rc)
continue;
(ntfschar*)((char*)a +
le16_to_cpu(a->name_offset)),
upcase, upcase_len);
if (rc == -1) {
return -1;
}
if (rc)
continue;
}
/*
* The names match or @name not present and attribute is
* unnamed. If no @val specified, we have found the attribute
* and are done.
*/
if (!val)
return 0;
/* @val is present; compare values. */
else {
register int rc;
/*
* If @val collates before the current attribute's
* value, there is no matching attribute.
*/
if (!rc) {
return 0;
return -1;
}
} else if (rc < 0) {
return -1;
}
}
}
ntfs_log_debug("ntfs_attr_find(): File is corrupt. Run chkdsk.\n");
return -1;
}
/**
* 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
* 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).
*
* 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.
*/
{
ATTR_RECORD *a;
ntfs_log_trace("Entering for inode 0x%llx, attribute type 0x%x.\n",
if (!base_ni) {
/* First call happens with the base mft record. */
}
goto not_found;
}
/*
* Iterate over entries in attribute list starting at @ctx->al_entry,
* or the entry following that, if @ctx->is_first is TRUE.
*/
/*
* If an enumeration and the first attribute is higher than
* the attribute list itself, need to return the attribute list
* attribute.
*/
goto find_attr_list_attr;
} else {
/*
* 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.
*/
int rc;
/* Check for bogus calls. */
return -1;
}
/* We want the base record. */
/* Sanity checks are performed elsewhere. */
/* Find the attribute list attribute. */
/*
* Setup the search context so the correct
* attribute is returned next time round.
*/
/* Got it. Done. */
if (!rc)
return 0;
/* Error! If other than not found return it. */
return rc;
/* Not found?!? Absurd! Must be a bug... )-: */
ntfs_log_trace("BUG! Attribute list attribute not "
"found but it exists! "
"Returning error (EINVAL).\n");
return -1;
}
}
for (;; al_entry = next_al_entry) {
/* Out of bounds check. */
break; /* Inode is corrupt. */
/* Catch the end of the attribute list. */
goto not_found;
break;
break;
goto not_found;
continue;
}
/*
* If !@type we want the attribute represented by this
* attribute list entry.
*/
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;
vol->upcase_len)) {
register int rc;
/*
* If @name collates before al_name, there is no
* matching attribute.
*/
if (rc == -1)
goto not_found;
/* If the strings are not equal, continue search. */
if (rc)
continue;
/*
* FIXME: Reverse engineering showed 0, IGNORE_CASE but
* that is inconsistent with ntfs_attr_find(). The
* subsequent rc checks were also different. Perhaps I
* made a mistake in one of the two. Need to recheck
* which is correct or at least see what is going
* on... (AIA)
*/
if (rc == -1)
goto not_found;
if (rc)
continue;
}
/*
* The names match or @name not present and attribute is
* unnamed. Now check @lowest_vcn. Continue search if the
* next attribute list entry still fits @lowest_vcn. Otherwise
* we have reached the right one or the search has failed.
*/
lowest_vcn &&
ntfs_names_are_equal((ntfschar*)((char*)
continue;
ntfs_log_debug("Found stale mft reference in "
"attribute list!\n");
break;
}
} else { /* Mft references do not match. */
/* Do we want the base record back? */
} else {
/* We want an extent record. */
if (!ni) {
ntfs_log_perror("Failed to map extent "
"inode");
break;
}
}
}
/*
* 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.
*/
break;
continue;
if (!a->length)
break;
goto do_next_attr;
/*
* attribute list entry and the attribute record, there is
* corruption so we break and return error EIO.
*/
break;
if (!ntfs_names_are_equal((ntfschar*)((char*)a +
le16_to_cpu(a->name_offset)),
a->name_length, al_name,
break;
/*
* 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.
*/
return 0;
}
/* Proceed to the next attribute in the current mft record. */
goto do_next_attr_loop;
}
}
ntfs_log_debug("Inode is corrupt.\n");
return -1;
/*
* 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.
*/
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.
*/
/*
* 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 {
} 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.
*
* 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.
*/
{
return -1;
}
if (ctx->base_ntfs_ino)
else
ctx);
}
/**
* ntfs_attr_init_search_ctx - initialize an attribute search context
* @ctx: attribute search context to initialize
* @ni: ntfs inode with which to initialize the search context
* @mrec: mft record with which to initialize the search context
*
* Initialize the attribute search context @ctx with @ni and @mrec.
*/
{
if (!mrec)
/* Sanity checks are performed elsewhere. */
}
/**
* 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.
*/
{
if (!ctx->base_ntfs_ino) {
/* No attribute list. */
/* Sanity checks are performed elsewhere. */
/*
* This needs resetting due to ntfs_external_attr_find() which
* can leave it set despite having zeroed ctx->base_ntfs_ino.
*/
return;
} /* Attribute list. */
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 to ENOMEM.
*
* @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()
* if you
*/
{
return NULL;
}
if (ctx)
return ctx;
}
/**
* ntfs_attr_put_search_ctx - release an attribute search context
* @ctx: attribute search context to free
*
* Release the attribute search context @ctx. This function does not change
* errno and doing nothing if NULL passed to it.
*/
{
}
/**
* 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).
*/
const ATTR_TYPES type)
{
return NULL;
}
/* We haven't found it yet, carry on searching. */
continue;
/* We found the attribute; return it. */
return ad;
/* We have gone too far already. No point in continuing. */
break;
}
/* Attribute not found?!? */
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).
*/
{
if (size < 0) {
return -1;
}
/*
* $ATTRIBUTE_LIST should be not greater than 0x40000, but this is not
* listed in the AttrDef.
*/
return -1;
}
if (!ad)
return -1;
/* We found the attribute. - Do the bounds check. */
/* @size is out of range! */
return -1;
}
return 0;
}
/**
* ntfs_attr_can_be_non_resident - check if an attribute can be non-resident
* @vol: ntfs volume to which the attribute belongs
* @type: attribute type which to check
*
* Check whether the attribute of @type on the ntfs volume @vol is allowed to
* be non-resident. This information is obtained from $AttrDef system file.
*
* Return 0 if the attribute is allowed to be non-resident and -1 if not or an
* error occurred. On error the error code is stored in errno. The following
* error codes are defined:
* EPERM - The attribute is not allowed to be non-resident.
* ENOENT - The attribute @type is not specified in $AttrDef.
* EINVAL - Invalid parameters (e.g. @vol is not valid).
*/
{
/* Find the attribute definition record in $AttrDef. */
if (!ad)
return -1;
/* Check the flags and return the result. */
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.
*/
{
return -1;
}
if (type != AT_INDEX_ALLOCATION)
return 0;
ntfs_log_trace("Attribute can't be resident\n");
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.
*/
{
ntfs_log_trace("Entering for pos 0x%d, size %u.\n",
/* Make size 8-byte alignment. */
/* Rigorous consistency checks. */
return -1;
}
/* The -8 is for the attribute terminator. */
return -1;
}
/* Nothing to do. */
if (!size)
return 0;
/* Do we have enough space? */
ntfs_log_trace("Not enough space in the MFT record\n");
return -1;
}
/* Move everything after pos to pos + size. */
/* Update mft record. */
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.
*/
{
ATTR_RECORD *a;
MFT_RECORD *m;
ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x, flags 0x%x.\n",
return -1;
}
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. */
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.
*/
ctx)) {
ntfs_log_trace("Attribute already present.\n");
goto put_err_out;
}
goto put_err_out;
}
/* Make room for attribute. */
ntfs_log_trace("Failed to make room for attribute.\n");
goto put_err_out;
}
/* Setup record fields. */
a->non_resident = 0;
a->name_length = name_len;
a->instance = m->next_attr_instance;
if (val)
else
if (type == AT_FILE_NAME)
else
a->u.res.resident_flags = 0;
if (name_len)
m->next_attr_instance =
else
if (ntfs_attrlist_entry_add(ni, a)) {
ntfs_attr_record_resize(m, a, 0);
ntfs_log_trace("Failed add attribute entry to "
"ATTRIBUTE_LIST.\n");
goto put_err_out;
}
}
return offset;
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.
*/
{
ATTR_RECORD *a;
MFT_RECORD *m;
ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x, lowest_vcn %lld, "
"dataruns_size %d, flags 0x%x.\n",
(long long) lowest_vcn, dataruns_size,
le16_to_cpu(flags));
return -1;
}
ntfs_log_trace("Attribute can't be non resident.\n");
else
ntfs_log_trace("ntfs_attr_can_be_non_resident() "
"failed.\n");
return -1;
}
/* Locate place where record should be. */
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.
*/
ctx)) {
ntfs_log_trace("Attribute already present.\n");
goto put_err_out;
}
goto put_err_out;
}
/* Make room for attribute. */
sizeof(a->u.nonres.compressed_size) : 0);
ntfs_log_trace("Failed to make room for attribute.\n");
goto put_err_out;
}
/* Setup record fields. */
a->non_resident = 1;
a->name_length = name_len;
sizeof(a->u.nonres.compressed_size) : 0));
a->instance = m->next_attr_instance;
/* If @lowest_vcn == 0, than setup empty attribute. */
if (!lowest_vcn) {
a->u.nonres.allocated_size = 0;
a->u.nonres.initialized_size = 0;
/* Set empty mapping pairs. */
}
if (name_len)
m->next_attr_instance =
else
if (ntfs_attrlist_entry_add(ni, a)) {
ntfs_attr_record_resize(m, a, 0);
ntfs_log_trace("Failed add attribute entry to "
"ATTRIBUTE_LIST.\n");
goto put_err_out;
}
}
/*
* 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_log_trace("Attribute lookup failed. Probably leaving "
"inconsistent metadata.\n");
return -1;
}
return offset;
return -1;
}
/**
* ntfs_attr_record_rm - remove attribute extent
* @ctx: search context describing the attribute which should be removed
*
* 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 err;
return -1;
}
ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x.\n",
if (ctx->base_ntfs_ino)
else
/* Remove attribute itself. */
ntfs_log_trace("Couldn't remove attribute record. "
"Bug or damaged MFT record.\n");
ntfs_log_trace("Rollback failed. Leaving "
"inconsistent metadata.\n");
return -1;
}
/*
* Remove record from $ATTRIBUTE_LIST if present and we don't want
* delete $ATTRIBUTE_LIST itself.
*/
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) {
}
/* Free MFT record, if it isn't contain attributes. */
// FIXME: We need rollback here.
ntfs_log_trace("Couldn't free MFT record.\n");
return -1;
}
/* Remove done if we freed base inode. */
return 0;
}
return 0;
/* Remove attribute list if we don't need it any more. */
if (!ntfs_attrlist_need(base_ni)) {
/*
* 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 (!al_rl) {
ntfs_log_trace("Couldn't decompress attribute "
"list runlist. Succeed "
"anyway.\n");
return 0;
}
ntfs_log_trace("Leaking clusters! Run chkdsk. "
"Couldn't free clusters from "
"attribute list runlist.\n");
}
}
/* 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.
*/
{
ntfs_log_trace("Invalid arguments passed.\n");
return -1;
}
ntfs_log_trace("Entering for inode 0x%llx, attr %x, size %lld.\n",
/* Check the attribute type and the size. */
ntfs_log_trace("Size bounds check failed.\n");
ntfs_log_trace("Invalid attribute type. Aborting...\n");
}
return -1;
}
/* Sanity checks for always resident attributes. */
ntfs_log_trace("ntfs_attr_can_be_non_resident() "
"failed.\n");
goto err_out;
}
/* @val is mandatory. */
if (!val) {
ntfs_log_trace("@val is mandatory for always resident "
"attributes.\n");
return -1;
}
ntfs_log_trace("Attribute is too big.\n");
return -1;
}
}
/* Check whether attribute can be resident. */
ntfs_log_trace("ntfs_attr_can_be_resident() failed.\n");
goto err_out;
}
is_resident = FALSE;
}
/* Calculate attribute record size. */
if (is_resident)
else /* We add 8 for space for mapping pairs. */
/*
* If we have enough free space for the new attribute in the base MFT
* record, then add attribute to it.
*/
goto add_attr_record;
}
/* Try to add to extent inodes. */
if (ntfs_inode_attach_all_extents(ni)) {
ntfs_log_trace("Failed to attach all extents to inode.\n");
goto err_out;
}
for (i = 0; i < ni->nr_extents; i++) {
goto add_attr_record;
}
/*
* If failed to find space for resident attribute, then try to find
* space for non resident one.
*/
if (is_resident && !always_resident) {
is_resident = FALSE;
goto retry;
}
/*
* FIXME: Try to make other attributes non-resident here. Factor out
* code from ntfs_resident_attr_resize.
*/
/* 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)) {
ntfs_log_trace("Failed to add attribute list.\n");
goto err_out;
}
}
/* Allocate new extent for attribute. */
if (!attr_ni) {
ntfs_log_trace("Failed to allocate extent record.\n");
goto err_out;
}
/*
* Determine resident or not will be attribute using heuristics and
* calculate attribute record size. FIXME: small code duplication here.
*/
is_resident = TRUE;
} else { /* We add 8 for space for mapping pairs. */
is_resident = FALSE;
}
if (is_resident) {
/* Add resident attribute. */
if (offset < 0) {
ntfs_log_trace("Failed to add resident attribute.\n");
goto free_err_out;
}
return 0;
}
/* Add non resident attribute. */
name_len, 0, 8, 0);
if (offset < 0) {
ntfs_log_trace("Failed to add non resident attribute.\n");
goto free_err_out;
}
/* If @size == 0, we are done. */
if (!size)
return 0;
/* Open new attribute and resize it. */
if (!na) {
ntfs_log_trace("Failed to open just added attribute.\n");
goto rm_attr_err_out;
}
/* Resize and set attribute value. */
ntfs_log_trace("Failed to initialize just added attribute.\n");
if (ntfs_attr_rm(na))
ntfs_log_trace("Failed to remove just added attribute. "
"Probably leaving inconsistent "
"metadata.\n");
goto err_out;
}
/* Done !*/
return 0;
/* Remove just added attribute. */
ntfs_log_trace("Failed to remove just added attribute.\n");
}
/* Free MFT record, if it isn't contain attributes. */
ntfs_log_trace("Failed to free MFT record. Leaving "
"inconsistent metadata.\n");
}
}
return -1;
}
/**
* ntfs_attr_rm - remove attribute from ntfs inode
* @na: opened ntfs attribute to delete
*
* Remove attribute and all it's extents from ntfs inode. If attribute was non
* resident also free all clusters allocated by attribute. This function always
* closes @na upon exit (both on success and failure).
*
* Return 0 on success or -1 on error with errno set to the error code.
*/
{
int ret = 0;
if (!na) {
ntfs_log_trace("Invalid arguments passed.\n");
return -1;
}
ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x.\n",
/* Free cluster allocation. */
if (NAttrNonResident(na)) {
if (ntfs_attr_map_whole_runlist(na)) {
return -1;
}
ntfs_log_trace("Failed to free cluster allocation. "
"Leaving inconsistent metadata.\n");
ret = -1;
}
}
/* Search for attribute extents and remove them all. */
if (!ctx) {
return -1;
}
if (ntfs_attr_record_rm(ctx)) {
ntfs_log_trace("Failed to remove attribute extent. "
"Leaving inconsistent metadata.\n");
ret = -1;
}
}
ntfs_log_trace("Attribute lookup failed. "
"Probably leaving inconsistent metadata.\n");
ret = -1;
}
/* Throw away now non-exist attribute. */
/* Done. */
return ret;
}
/**
* ntfs_attr_record_resize - resize an attribute record
* @m: mft record containing attribute record
* @a: attribute record to resize
* @new_size: new size in bytes to which to resize the attribute record @a
*
* Resize the attribute record @a, i.e. the resident part of the attribute, in
* the mft record @m to @new_size bytes.
*
* Return 0 on success and -1 on error with errno set to the error code.
* The following error codes are defined:
* ENOSPC - Not enough space in the mft record @m to perform the resize.
* Note that on error no modifications have been performed whatsoever.
*
* Warning: If you make a record smaller without having copied all the data you
* are interested in the data may be overwritten!
*/
{
/* Align to 8 bytes, just in case the caller hasn't. */
/* If the actual attribute length has changed, move things around. */
/* Not enough space in this mft record. */
return -1;
}
/* Move attributes following @a to their new location. */
/* Adjust @m to reflect the change in used space. */
/* Adjust @a to reflect the 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.
*/
{
/*
* Check that the attribute name hasn't been placed after the
* attribute value. Chkdsk treat this as corruption.
*/
ntfs_log_trace("Name is placed after the attribute value. "
"Corrupted inode. Run chkdsk. Aborting...\n");
return -1;
}
/* Resize the resident part of the attribute record. */
ntfs_log_trace("Attribute record resize failed. "
"Aborting...\n");
}
return -1;
}
/*
* If we made the attribute value bigger, clear the area between the
* old size and @new_size.
*/
/* Finally update the length of the attribute value. */
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
*
* use it anymore.
*
* Return 0 on success and -1 on error with errno set to the error code.
*/
{
ATTR_RECORD *a;
int err;
ntfs_log_trace("Invalid arguments passed.\n");
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",
return 0;
ntfs_log_trace("Inode should contain attribute list to use "
"this function.\n");
return -1;
}
/* Find place in MFT record where attribute will be moved. */
if (!nctx) {
ntfs_log_trace("Couldn't obtain search context.\n");
return -1;
}
/*
* Use ntfs_attr_find instead of ntfs_attr_lookup to find place for
* attribute in @ni->mrec, not any extent inode in case if @ni is base
* file record.
*/
0, nctx)) {
ntfs_log_trace("Attribute of such type, with same name already "
"present in this MFT record.\n");
goto put_err_out;
}
ntfs_log_debug("Attribute lookup failed.\n");
goto put_err_out;
}
/* Make space and move attribute. */
le32_to_cpu(a->length))) {
ntfs_log_trace("Couldn't make space for attribute.\n");
goto put_err_out;
}
/* Update attribute list. */
return 0;
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.
*
* use it anymore.
*
* Return 0 on success and -1 on error with errno set to the error code.
*/
{
MFT_RECORD *m;
int i;
ntfs_log_trace("Invalid arguments passed.\n");
return -1;
}
ntfs_log_trace("Entering for attr 0x%x, inode 0x%llx.\n",
else
if (!NInoAttrList(base_ni)) {
ntfs_log_trace("Inode should contain attribute list to use "
"this function.\n");
return -1;
}
ntfs_log_trace("Couldn't attach extent inode.\n");
return -1;
}
/* Walk through all extents and try to move attribute to them. */
for (i = 0; i < base_ni->nr_extents; i++) {
continue;
if (le32_to_cpu(m->bytes_allocated) -
le32_to_cpu(m->bytes_in_use) <
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.
*/
return 0;
}
/*
* Failed to move attribute to one of the current extents, so allocate
* new extent and move attribute to it.
*/
if (!ni) {
ntfs_log_trace("Couldn't allocate new MFT record.\n");
return -1;
}
ntfs_log_trace("Couldn't move attribute to new MFT record.\n");
return -1;
}
return 0;
}
/**
* ntfs_attr_make_non_resident - convert a resident to a non-resident attribute
* @na: open ntfs attribute to make non-resident
* @ctx: ntfs search context describing the attribute
*
* Convert a resident ntfs attribute to a non-resident one.
*
* Return 0 on success and -1 on error with errno set to the error code. The
* following error codes are defined:
* EPERM - The attribute is not allowed to be non-resident.
* TODO: others...
*
* NOTE to self: No changes in the attribute list are required to move from
* a resident to a non-resident attribute.
*
* Warning: We do not set the inode dirty and we do not write out anything!
* We expect the caller to do this as this is a fairly low level
* function and it is likely there will be further changes made.
*/
{
ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x.\n", (unsigned long
/* Some preliminary sanity checking. */
if (NAttrNonResident(na)) {
ntfs_log_trace("Eeek! Trying to make non-resident attribute "
"non-resident. Aborting...\n");
return -1;
}
/* Check that the attribute is allowed to be non-resident. */
return -1;
/*
* Check that the attribute name hasn't been placed after the
* attribute value. Chkdsk treat this as corruption.
*/
ntfs_log_trace("Name is placed after the attribute value. "
"Corrupted inode. Run chkdsk. Aborting...\n");
return -1;
}
if (new_allocated_size > 0) {
/* Start by allocating clusters to hold the attribute value. */
if (!rl) {
ntfs_log_trace("Eeek! Failed to allocate "
"cluster(s). Aborting...\n");
}
return -1;
}
} else
/*
* Setup the in-memory attribute structure to be non-resident so that
* we can use ntfs_attr_pwrite().
*/
/*
* FIXME: For now just clear all of these as we don't support them when
* writing.
*/
if (rl) {
/* Now copy the attribute value to the allocated cluster(s). */
ntfs_log_debug("Failed to write out attribute value "
"(bw = %lli, errno = %i). "
if (bw >= 0)
goto cluster_free_err_out;
}
}
/* Determine the size of the mapping pairs array. */
if (mp_size < 0) {
ntfs_log_debug("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. */
/*
* Determine the size of the resident part of the non-resident
* attribute record. (Not compressed thus no compressed_size element
* present.)
*/
/* Resize the resident part of the attribute record. */
ntfs_log_trace("Failed to resize attribute record. "
"Aborting...\n");
}
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)
a->name_length * sizeof(ntfschar));
/* Update the flags to match the in-memory ones. */
/* Setup the fields specific to non-resident attributes. */
a->u.nonres.compression_unit = 0;
/* Generate the mapping pairs array in the attribute record. */
// 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;
ntfs_log_trace("Failed to release allocated clusters in error "
"code path. Leaving inconsistent metadata...\n");
return -1;
}
/**
* ntfs_resident_attr_resize - resize a resident, open ntfs attribute
* @na: resident ntfs attribute to resize
* @newsize: new size (in bytes) to which to resize the attribute
*
* Change the size of a resident, open ntfs attribute @na to @newsize bytes.
*
* On success return 0 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 on the volume to allocate
* new clusters or in base mft to resize $ATTRIBUTE_LIST.
* EOVERFLOW - Resident attribute can not become non resident and
* already filled whole MFT record, but had not reached
* @newsize bytes length.
*/
{
int err;
ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x, new size %lld.\n",
(long long)newsize);
/* Get the attribute record that needs modification. */
if (!ctx)
return -1;
ctx)) {
goto put_err_out;
}
/*
* Check the attribute type and the corresponding minimum and maximum
* sizes against @newsize and fail if @newsize is out of bounds.
*/
ntfs_log_trace("Size bounds check failed. "
"Aborting...\n");
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.
*/
/* Perform the resize of the attribute record. */
newsize)) {
/* Update attribute size everywhere. */
}
goto resize_done;
}
/* Error! If not enough space, just continue. */
ntfs_log_trace("Failed to resize resident part "
"of attribute. Aborting...\n");
goto put_err_out;
}
}
/* There is not enough space in the MFT record to perform the resize. */
/* Make the attribute non-resident if possible. */
/* Resize non-resident attribute */
ntfs_log_trace("Failed to make attribute non-resident. "
"Aborting...\n");
goto put_err_out;
}
/* Try to make other attributes non-resident and retry each time. */
ATTR_RECORD *a;
if (a->non_resident)
continue;
/*
* Check out whether convert is reasonable. Assume that mapping
* pairs will take 8 bytes.
*/
continue;
if (!tna) {
ntfs_log_trace("Couldn't open attribute.\n");
goto put_err_out;
}
continue;
}
}
/* Check whether error occurred. */
ntfs_log_trace("Attribute lookup failed.\n");
goto put_err_out;
}
/* We can't move out attribute list, thus move out others. */
ntfs_log_trace("Couldn't free space in the MFT record "
"to make attribute list non "
"resident.\n");
return -1;
}
}
/*
* 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_log_trace("Attribute lookup failed.\n");
goto put_err_out;
}
/*
* Force index allocation creation instead of moving out index root
* from the base MFT record.
*/
sizeof(INDEX_ENTRY_HEADER) + sizeof(VCN)) {
goto put_err_out;
}
}
/*
* Check whether attribute is already single in the this MFT record.
* 8 added for the attribute terminator.
*/
goto put_err_out;
}
/* Add attribute list if not present. */
else
if (!NInoAttrList(ni)) {
if (ntfs_inode_add_attrlist(ni))
return -1;
}
/* Allocate new MFT record. */
if (!ni) {
ntfs_log_trace("Couldn't allocate new MFT record.\n");
goto put_err_out;
}
/* Move attribute to it. */
ntfs_log_trace("Couldn't move attribute to new MFT record.\n");
goto put_err_out;
}
/* Update ntfs attribute. */
/* Try to perform resize once again. */
/*
* Set the inode (and its base inode if it exists) dirty so it is
* written out later.
*/
/* Done! */
return 0;
return -1;
}
/**
* 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.
*/
{
ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x.\n", (unsigned long
/* Should be called for the first extent of the attribute. */
ntfs_log_trace("Should be called for the first extent of the "
"attribute. Aborting...\n");
return -1;
}
/* Some preliminary sanity checking. */
if (!NAttrNonResident(na)) {
ntfs_log_trace("Trying to make resident attribute resident. "
"Aborting...\n");
return -1;
}
/* Make sure this is not $MFT/$BITMAP or Windows will not boot! */
return -1;
}
/* Check that the attribute is allowed to be resident. */
return -1;
/*
* Check that the attribute name hasn't been placed after the
* mapping pairs array. Chkdsk treat this as corruption.
*/
ntfs_log_trace("Damaged attribute. Name is placed after the "
"mapping pairs array. Run chkdsk. Aborting.\n");
return -1;
}
ntfs_log_trace("Making compressed or encrypted files resident "
"is not implemented yet.\n");
errno = EOPNOTSUPP;
return -1;
}
/* Work out offsets into and size of the resident attribute. */
/* Sanity check the size before we start modifying the attribute. */
ntfs_log_trace("Not enough space to make attribute resident\n");
return -1;
}
/* Read and cache the whole runlist if not already done. */
return -1;
/* Move the attribute name if it exists and update the offset. */
if (a->name_length) {
a->name_length * sizeof(ntfschar));
}
/* Resize the resident part of the attribute record. */
/*
* 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",
return -1;
}
/* Convert the attribute record to describe a resident attribute. */
a->non_resident = 0;
a->flags = 0;
/*
* 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)
else
a->u.res.resident_flags = 0;
/* Sanity fixup... Shouldn't really happen. (AIA) */
/* Copy data from run list to resident attribute value. */
if (bytes_read >= 0)
ntfs_log_trace("Eeek! Failed to read attribute data. Leaving "
"inconsistent metadata. Run chkdsk. "
"Aborting...\n");
return -1;
}
/* Clear memory in gap between initialized_size and data_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.
*/
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. */
/* Update in-memory struct ntfs_attr. */
na->compression_block_size = 0;
return 0;
}
/**
* 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
* 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.
*
* New runlist should be fully formed starting @from_vcn. Runs before @from_vcn
* can be mapped or not, but on-disk structures should not be modified before
* call to this function so they can be mapped if necessary.
*
* FIXME: Make it O(1) for sparse files too, not only for normal.
*
* FIXME: Rewrite without using NTFS_VCN_DELETE_MARK define.
*
* NOTE: Be careful in the future with updating bits on compressed files (at
* this function).
*
* 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.
*/
{
MFT_RECORD *m;
ATTR_RECORD *a;
ntfs_log_trace("Invalid parameters passed.\n");
return -1;
}
if (!NAttrNonResident(na)) {
ntfs_log_trace("Attribute should be non resident.\n");
return -1;
}
ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x, from vcn 0x%lld."
from_vcn);
else
if (!ctx) {
ntfs_log_trace("Couldn't get search context.\n");
return -1;
}
/* Fill attribute records with new mapping pairs. */
stop_vcn = 0;
/*
* 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.
*/
from_vcn = 0;
/*
* Check whether the first run we need to update is
* the last run in runlist, if so, then deallocate
* all attribute extents starting this one.
*/
if (first_lcn == LCN_EINVAL) {
ntfs_log_trace("BUG! Incorrect runlist.\n");
goto put_err_out;
}
if (first_lcn == LCN_ENOENT ||
}
/*
* Check whether we finished mapping pairs build, if so mark
* extent as need to delete (by setting highest vcn to
* NTFS_VCN_DELETE_MARK (-2), we shall check it later and
* delete extent) and continue search.
*/
if (finished_build) {
ntfs_log_trace("Mark attr 0x%x for delete in inode "
"0x%llx.\n", (unsigned)le32_to_cpu(
continue;
}
/*
* Check that the attribute name hasn't been placed after the
* mapping pairs array. Windows treat this as a corruption.
*/
if (a->name_length) {
if (le16_to_cpu(a->name_offset) >=
ntfs_log_error("Damaged attribute. Name is "
"placed after the mapping "
"pairs array. Run chkdsk.\n");
goto put_err_out;
}
}
/*
* update allocated and compressed size.
*/
if (!a->u.nonres.lowest_vcn) {
int sparse;
/* Update allocated size. */
/*
* Check whether part of runlist we are updating is
* sparse.
*/
if (sparse == -1) {
ntfs_log_trace("Bad runlist.\n");
goto put_err_out;
}
/*
* If new part or on-disk attribute is not sparse, then
* we should fully map runlist to make final decision.
*/
0, from_vcn - 1)) {
ntfs_log_trace("Failed to map runlist "
"before @from_vcn.\n");
goto put_err_out;
}
/*
* Reconsider whether whole runlist is sparse
* if new part is not.
*/
if (!sparse) {
if (sparse == -1) {
ntfs_log_trace("Bad "
"runlist.\n");
goto put_err_out;
}
}
}
/* Attribute becomes sparse/compressed. */
ATTR_IS_COMPRESSED))) {
/*
* We need to move attribute to another mft
* record, if attribute is to small to add
* compressed_size field to it and we have no
* free space in the current mft record.
*/
a->u.nonres.mapping_pairs_offset)
== 8) && !(le32_to_cpu(
m->bytes_allocated) -
le32_to_cpu(m->bytes_in_use))) {
return -1;
goto retry;
}
8)) {
ntfs_log_trace("Failed to move "
"attribute to another "
"extent. Aborting..\n");
goto put_err_out;
}
goto retry;
}
a->u.nonres.mapping_pairs_offset))) {
ntfs_log_trace("Size of the space "
"allocated for mapping "
"pairs should not be 0."
" Aborting ...\n");
goto put_err_out;
}
a->flags |= ATTR_IS_SPARSE;
even if attribute
is not actually
compressed. */
a->name_length * sizeof(ntfschar));
a->name_offset) + 8);
a->u.nonres.mapping_pairs_offset =
/*
* We should update all mapping pairs, because
* we shifted their starting position.
*/
from_vcn = 0;
}
/* Attribute becomes normal. */
!(a->flags & ATTR_IS_COMPRESSED)) {
a->flags &= ~ATTR_IS_SPARSE;
a->u.nonres.compression_unit = 0;
a->name_length * sizeof(ntfschar));
/*
* Windows defragmentation tool do not update
* name offset correctly for unnamed
* attributes, but chkdsk do not like when it
* negative, so do not change it at all if it
* would become negative.
*/
a->name_offset = cpu_to_le16(
a->name_offset) - 8);
a->u.nonres.mapping_pairs_offset =
/*
* We should update all mapping pairs, because
* we shifted their starting position.
*/
from_vcn = 0;
}
/* Update compressed size if required. */
if (new_compr_size == -1) {
ntfs_log_trace("BUG! Leaving "
"inconsistent "
"metadata.\n");
goto put_err_out;
}
}
/*
* Set FILE_NAME dirty flag, to update sparse bit and
* allocated size in the index.
*/
if (sparse)
else
}
/*
* We do want to do anything for the first extent in
* case we are updating mapping pairs not from the
* begging.
*/
from_vcn = 0;
else {
if (from_vcn)
continue;
}
}
/* Get the size for the rest of mapping pairs array. */
stop_vcn);
if (mp_size <= 0) {
ntfs_log_trace("Get size for mapping pairs failed.\n");
goto put_err_out;
}
/*
* Determine maximum possible length of mapping pairs,
* if we shall *not* expand space for mapping pairs.
*/
/*
* Determine maximum possible length of mapping pairs in the
* current mft record, if we shall expand space for mapping
* pairs.
*/
/* 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.
*/
cur_max_mp_size)) {
return -1;
ntfs_log_error("Attribute list mapping "
"pairs size to big, "
"can't fit them in the "
"base MFT record. "
"Defragment volume and "
"try once again.\n");
return -1;
}
goto retry;
}
/* Add attribute list if it isn't present, and retry. */
if (!NInoAttrList(base_ni)) {
if (ntfs_inode_add_attrlist(base_ni)) {
ntfs_log_trace("Couldn't add attribute "
"list.\n");
return -1;
}
goto retry;
}
/*
* Set mapping pairs size to maximum possible for this
* mft record. We shall write the rest of mapping pairs
* to another MFT records.
*/
}
/* Change space for mapping pairs if we need it. */
if (ntfs_attr_record_resize(m, a,
mp_size)) {
ntfs_log_error("BUG! Ran out of space in mft "
"record. Please run chkdsk and "
"if that doesn't find any "
"errors please report you saw "
"this message to %s.\n",
goto put_err_out;
}
}
/* Update lowest vcn. */
}
/*
* Generate the new mapping pairs array directly into the
* correct destination, i.e. the attribute record itself.
*/
ntfs_log_error("BUG! Mapping pairs build failed. "
"Please run chkdsk and if that doesn't "
"find any errors please report you saw "
"this message to %s.\n", NTFS_DEV_LIST);
goto put_err_out;
}
}
/* Check whether error occurred. */
ntfs_log_trace("Attribute lookup failed.\n");
goto put_err_out;
}
/* Sanity check. */
if (from_vcn) {
ntfs_log_error("Library BUG! @from_vcn is nonzero, please "
"report to %s.\n", NTFS_DEV_LIST);
goto put_err_out;
}
/* Deallocate not used attribute extents and return with success. */
if (finished_build) {
ntfs_log_trace("Deallocate marked extents.\n");
continue;
/* Remove unused attribute record. */
if (ntfs_attr_record_rm(ctx)) {
ntfs_log_trace("Couldn't remove unused "
"attribute record.\n");
goto put_err_out;
}
}
ntfs_log_trace("Attribute lookup failed.\n");
goto put_err_out;
}
ntfs_log_trace("Deallocate done.\n");
ntfs_log_trace("Done!");
return 0;
}
/* Allocate new MFT records for the rest of mapping pairs. */
while (1) {
/* Calculate size of rest mapping pairs. */
if (mp_size <= 0) {
ntfs_log_trace("Get size for mapping pairs failed.\n");
goto put_err_out;
}
/* Allocate new mft record. */
if (!ni) {
ntfs_log_trace("Couldn't allocate new MFT record.\n");
goto put_err_out;
}
/*
* If mapping size exceed available space, set them to
* possible maximum.
*/
le32_to_cpu(m->bytes_in_use) -
sizeof(a->u.nonres.compressed_size) : 0)) -
if (mp_size > cur_max_mp_size)
/* Add attribute extent to new record. */
if (err == -1) {
ntfs_log_trace("Couldn't add attribute extent into the "
"MFT record.\n");
ntfs_log_trace("Couldn't free MFT record.\n");
}
goto put_err_out;
}
ntfs_log_error("BUG! Mapping pairs build failed. "
"Please run chkdsk and if that doesn't "
"find any errors please report you saw "
"this message to %s.\n", NTFS_DEV_LIST);
ntfs_log_trace("Couldn't free MFT record.\n");
goto put_err_out;
}
/* All mapping pairs has been written. */
if (!err)
break;
}
ntfs_log_trace("Done!\n");
return 0;
if (ctx)
return -1;
}
/**
* 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.
*/
{
int err;
ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x, newsize %lld.\n",
(long long)newsize);
/*
* Check the attribute type and the corresponding minimum size
* against @newsize and fail if @newsize is too small.
*/
ntfs_log_trace("Eeek! Size bounds check failed. "
"Aborting...\n");
return -1;
}
/* The first cluster outside the new allocation. */
/*
* Compare the new allocation with the old one and only deallocate
* clusters if there is a change.
*/
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. */
-1);
if (nr_freed_clusters < 0) {
ntfs_log_trace("Eeek! Freeing of clusters failed. "
"Aborting...\n");
return -1;
}
/* Truncate the runlist itself. */
/*
* Failed to truncate the runlist, so just throw it
* away, it will be mapped afresh on next use.
*/
ntfs_log_trace("Eeek! Run list truncation failed.\n");
return -1;
}
/* Prepare to mapping pairs update. */
/* Write mapping pairs for new runlist. */
ntfs_log_trace("Eeek! Mapping pairs update failed. "
"Leaving inconsistent metadata. "
"Run chkdsk.\n");
return -1;
}
}
/* Get the first attribute record. */
if (!ctx) {
ntfs_log_trace("Couldn't get attribute search context.\n");
return -1;
}
ntfs_log_trace("Eeek! Lookup of first attribute extent failed. "
"Leaving inconsistent metadata.\n");
goto put_err_out;
}
/* Update data and initialized size. */
}
/* Update data size in the index. */
}
/* If the attribute now has zero size, make it resident. */
if (!newsize) {
/* If couldn't make resident, just continue. */
ntfs_log_error("Failed to make attribute "
"resident. Leaving as is...\n");
}
}
/* Set the inode dirty so it is written out later. */
/* Done! */
return 0;
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
* @sparse: if TRUE then will create hole if possible
*
* 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.
*/
{
int err;
ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x, new size %lld, "
"current size %lld.\n",
/*
* Check the attribute type and the corresponding maximum size
* against @newsize and fail if @newsize is too big.
*/
ntfs_log_trace("Eeek! Size bounds check failed. "
"Aborting...\n");
return -1;
}
/* Save for future use. */
/* The first cluster outside the new allocation. */
/*
* Compare the new allocation with the old one and only allocate
* clusters if there is a change.
*/
/* Map required part of runlist. */
vol->cluster_size_bits)) {
ntfs_log_error("Failed to map runlist.\n");
return -1;
}
/*
* If we extend $DATA attribute on NTFS 3+ volume, we can add
* sparse runs instead of real allocation of clusters.
*/
if (!rl)
return -1;
} 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;
/* Seek to the last run list element. */
;
/*
* If the last LCN is a hole or similar seek
* back to last valid LCN.
*/
rl--;
/*
* Only set lcn_seek_from it the LCN is valid.
*/
}
(na->allocated_size >>
if (!rl) {
ntfs_log_trace("Cluster allocation failed.\n");
return -1;
}
}
/* Append new clusters to attribute runlist. */
if (!rln) {
/* Failed, free just allocated clusters. */
ntfs_log_trace("Run list merge failed.\n");
return -1;
}
/* Prepare to mapping pairs update. */
/* Write mapping pairs for new runlist. */
vol->cluster_size_bits)) {
ntfs_log_trace("Mapping pairs update failed.\n");
goto rollback;
}
}
if (!ctx) {
ntfs_log_trace("Failed to get search context.\n");
return -1;
}
goto rollback;
}
ntfs_log_trace("Lookup of first attribute extent failed.\n");
goto rollback;
} else
goto put_err_out;
}
/* Update data size. */
/* Update data size in the index. */
}
/* Set the inode dirty so it is written out later. */
/* Done! */
return 0;
/* Free allocated clusters. */
ntfs_log_trace("Eeek! Leaking clusters. Run chkdsk!\n");
}
/* Now, truncate the runlist itself. */
vol->cluster_size_bits)) {
/*
* Failed to truncate the runlist, so just throw it away, it
* will be mapped afresh on next use.
*/
ntfs_log_trace("Couldn't truncate runlist. Rollback failed.\n");
} else {
/* Prepare to mapping pairs update. */
/* Restore mapping pairs. */
vol->cluster_size_bits)) {
ntfs_log_trace("Failed to restore old mapping pairs. "
"Rollback failed.\n");
}
}
return -1;
return -1;
}
/**
* __ntfs_attr_truncate - resize an ntfs attribute
* @na: open ntfs attribute to resize
* @newsize: new size (in bytes) to which to resize the attribute
* @sparse: if TRUE then will create hole if possible
*
* 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 and on error return -1 with errno set to the error code.
* The following error codes are defined:
* EINVAL - Invalid arguments were passed to the function.
* EACCES - Attribute is encrypted.
* ERANGE - @newsize is not valid for the attribute type of @na.
* ENOSPC - There is no enough space on the volume to allocate
* new clusters or in base mft to resize $ATTRIBUTE_LIST.
* EOVERFLOW - Resident attribute can not become non resident and
* already filled whole MFT record, but had not reached
* @newsize bytes length.
* EOPNOTSUPP - The desired resize is not implemented yet.
*/
{
int ret;
ntfs_log_trace("Invalid arguments passed.\n");
return -1;
}
ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x.\n", (unsigned long
return 0;
/*
* Encrypted attributes are not supported. We return access denied,
* which is what Windows NT4 does, too.
*/
if (NAttrEncrypted(na)) {
ntfs_log_trace("Failed (encrypted).\n");
return -1;
}
/*
* TODO: Implement making handling of compressed attributes.
*/
if (NAttrCompressed(na)) {
errno = EOPNOTSUPP;
ntfs_log_trace("Failed (compressed).\n");
return -1;
}
if (NAttrNonResident(na)) {
sparse);
else
} else
if (!ret)
ntfs_log_trace("Done!\n");
else
ntfs_log_trace("Failed.\n");
return ret;
}
/**
* Wrapper around __ntfs_attr_truncate that always tries to creates hole
*/
{
}
/**
* 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.
*/
{
if (!na) {
ntfs_log_perror("ntfs_attr_open failed");
return NULL;
}
if (!data)
goto out;
ntfs_log_perror("ntfs_attr_pread failed");
goto out;
}
if (data_size)
out:
return ret;
}
/**
* ntfs_attr_exist - FIXME: description
*/
{
int ret;
ntfs_log_trace("Entering.\n");
if (!ctx)
return 0;
ctx);
return !ret;
}