#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef HAVE_STDIO_H
#include <stdio.h>
#endif
#ifdef HAVE_STRING_H
#include <string.h>
#endif
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#ifdef HAVE_ERRNO_H
#include <errno.h>
#endif
#ifdef HAVE_LIMITS_H
#include <limits.h>
#endif
#include "param.h"
#include "compat.h"
#include "attrib.h"
#include "attrlist.h"
#include "device.h"
#include "mft.h"
#include "debug.h"
#include "mst.h"
#include "volume.h"
#include "types.h"
#include "layout.h"
#include "inode.h"
#include "runlist.h"
#include "lcnalloc.h"
#include "dir.h"
#include "compress.h"
#include "bitmap.h"
#include "logging.h"
#include "misc.h"
#include "efs.h"
ntfschar AT_UNNAMED[] = { const_cpu_to_le16('\0') };
ntfschar STREAM_SDS[] = { const_cpu_to_le16('$'),
const_cpu_to_le16('S'),
const_cpu_to_le16('D'),
const_cpu_to_le16('S'),
const_cpu_to_le16('\0') };
ntfschar TXF_DATA[] = { const_cpu_to_le16('$'),
const_cpu_to_le16('T'),
const_cpu_to_le16('X'),
const_cpu_to_le16('F'),
const_cpu_to_le16('_'),
const_cpu_to_le16('D'),
const_cpu_to_le16('A'),
const_cpu_to_le16('T'),
const_cpu_to_le16('A'),
const_cpu_to_le16('\0') };
static int NAttrFlag(ntfs_attr *na, FILE_ATTR_FLAGS flag)
{
if (na->type == AT_DATA && na->name == AT_UNNAMED)
return (na->ni->flags & flag);
return 0;
}
static void NAttrSetFlag(ntfs_attr *na, FILE_ATTR_FLAGS flag)
{
if (na->type == AT_DATA && na->name == AT_UNNAMED)
na->ni->flags |= flag;
else
ntfs_log_trace("Denied setting flag %d for not unnamed data "
"attribute\n", le32_to_cpu(flag));
}
static void NAttrClearFlag(ntfs_attr *na, FILE_ATTR_FLAGS flag)
{
if (na->type == AT_DATA && na->name == AT_UNNAMED)
na->ni->flags &= ~flag;
}
#define GenNAttrIno(func_name, flag) \
int NAttr##func_name(ntfs_attr *na) { return NAttrFlag (na, flag); } \
void NAttrSet##func_name(ntfs_attr *na) { NAttrSetFlag (na, flag); } \
void NAttrClear##func_name(ntfs_attr *na){ NAttrClearFlag(na, flag); }
GenNAttrIno(Compressed, FILE_ATTR_COMPRESSED)
GenNAttrIno(Encrypted, FILE_ATTR_ENCRYPTED)
GenNAttrIno(Sparse, FILE_ATTR_SPARSE_FILE)
s64 ntfs_get_attribute_value_length(const ATTR_RECORD *a)
{
if (!a) {
errno = EINVAL;
return 0;
}
errno = 0;
if (a->non_resident)
return sle64_to_cpu(a->data_size);
return (s64)le32_to_cpu(a->value_length);
}
s64 ntfs_get_attribute_value(const ntfs_volume *vol,
const ATTR_RECORD *a, u8 *b)
{
runlist *rl;
s64 total, r;
int i;
if (!vol || !a || !b) {
errno = EINVAL;
return 0;
}
if (a->type != AT_ATTRIBUTE_LIST && a->flags) {
ntfs_log_error("Non-zero (%04x) attribute flags. Cannot handle "
"this yet.\n", le16_to_cpu(a->flags));
errno = EOPNOTSUPP;
return 0;
}
if (!a->non_resident) {
if (le32_to_cpu(a->value_length) + le16_to_cpu(a->value_offset)
> le32_to_cpu(a->length)) {
return 0;
}
memcpy(b, (const char*)a + le16_to_cpu(a->value_offset),
le32_to_cpu(a->value_length));
errno = 0;
return (s64)le32_to_cpu(a->value_length);
}
if (!(a->data_size)) {
errno = 0;
return 0;
}
rl = ntfs_mapping_pairs_decompress(vol, a, NULL);
if (!rl) {
errno = EINVAL;
return 0;
}
for (i = 0, total = 0; rl[i].length; i++) {
if (total + (rl[i].length << vol->cluster_size_bits) >=
sle64_to_cpu(a->data_size)) {
unsigned char *intbuf = NULL;
s64 intlth;
intlth = (sle64_to_cpu(a->data_size) - total
+ vol->cluster_size - 1)
>> vol->cluster_size_bits;
if (rl[i].length < intlth)
intlth = rl[i].length;
intbuf = (u8*)ntfs_malloc(intlth
<< vol->cluster_size_bits);
if (!intbuf) {
free(rl);
return 0;
}
r = ntfs_pread(vol->dev,
rl[i].lcn << vol->cluster_size_bits,
intlth << vol->cluster_size_bits,
intbuf);
if (r != intlth << vol->cluster_size_bits) {
#define ESTR "Error reading attribute value"
if (r == -1)
ntfs_log_perror(ESTR);
else if (r < intlth <<
vol->cluster_size_bits) {
ntfs_log_debug(ESTR ": Ran out of input data.\n");
errno = EIO;
} else {
ntfs_log_debug(ESTR ": unknown error\n");
errno = EIO;
}
#undef ESTR
free(rl);
free(intbuf);
return 0;
}
memcpy(b + total, intbuf, sle64_to_cpu(a->data_size) -
total);
free(intbuf);
total = sle64_to_cpu(a->data_size);
break;
}
r = ntfs_pread(vol->dev, rl[i].lcn << vol->cluster_size_bits,
rl[i].length << vol->cluster_size_bits,
b + total);
if (r != rl[i].length << vol->cluster_size_bits) {
#define ESTR "Error reading attribute value"
if (r == -1)
ntfs_log_perror(ESTR);
else if (r < rl[i].length << vol->cluster_size_bits) {
ntfs_log_debug(ESTR ": Ran out of input data.\n");
errno = EIO;
} else {
ntfs_log_debug(ESTR ": unknown error\n");
errno = EIO;
}
#undef ESTR
free(rl);
return 0;
}
total += r;
}
free(rl);
return total;
}
static void __ntfs_attr_init(ntfs_attr *na, ntfs_inode *ni,
const ATTR_TYPES type, ntfschar *name, const u32 name_len)
{
na->rl = NULL;
na->ni = ni;
na->type = type;
na->name = name;
if (name)
na->name_len = name_len;
else
na->name_len = 0;
}
void ntfs_attr_init(ntfs_attr *na, const BOOL non_resident,
const ATTR_FLAGS data_flags,
const BOOL encrypted, const BOOL sparse,
const s64 allocated_size, const s64 data_size,
const s64 initialized_size, const s64 compressed_size,
const u8 compression_unit)
{
if (!NAttrInitialized(na)) {
na->data_flags = data_flags;
if (non_resident)
NAttrSetNonResident(na);
if (data_flags & ATTR_COMPRESSION_MASK)
NAttrSetCompressed(na);
if (encrypted)
NAttrSetEncrypted(na);
if (sparse)
NAttrSetSparse(na);
na->allocated_size = allocated_size;
na->data_size = data_size;
na->initialized_size = initialized_size;
if ((data_flags & ATTR_COMPRESSION_MASK) || sparse) {
ntfs_volume *vol = na->ni->vol;
na->compressed_size = compressed_size;
na->compression_block_clusters = 1 << compression_unit;
na->compression_block_size = 1 << (compression_unit +
vol->cluster_size_bits);
na->compression_block_size_bits = ffs(
na->compression_block_size) - 1;
}
NAttrSetInitialized(na);
}
}
ntfs_attr *ntfs_attr_open(ntfs_inode *ni, const ATTR_TYPES type,
ntfschar *name, u32 name_len)
{
ntfs_attr_search_ctx *ctx;
ntfs_attr *na = NULL;
ntfschar *newname = NULL;
ATTR_RECORD *a;
le16 cs;
ntfs_log_enter("Entering for inode %lld, attr 0x%x.\n",
(unsigned long long)ni->mft_no, le32_to_cpu(type));
if (!ni || !ni->vol || !ni->mrec) {
errno = EINVAL;
goto out;
}
na = ntfs_calloc(sizeof(ntfs_attr));
if (!na)
goto out;
if (!name_len)
name = (ntfschar*)NULL;
if (name && name != AT_UNNAMED && name != NTFS_INDEX_I30) {
if (ntfs_ucsnlen(name, name_len) != name_len) {
ntfs_log_error("Null character in attribute name"
" of inode %lld\n",(long long)ni->mft_no);
goto err_out;
}
name = ntfs_ucsndup(name, name_len);
if (!name)
goto err_out;
newname = name;
}
ctx = ntfs_attr_get_search_ctx(ni, NULL);
if (!ctx)
goto err_out;
if (ntfs_attr_lookup(type, name, name_len, 0, 0, NULL, 0, ctx))
goto put_err_out;
a = ctx->attr;
if (!name) {
if (a->name_length) {
ntfschar *attr_name;
attr_name = (ntfschar*)((u8*)a
+ le16_to_cpu(a->name_offset));
if (ntfs_ucsnlen(attr_name, a->name_length)
!= a->name_length) {
ntfs_log_error("Null character in attribute"
" name in inode %lld\n",
(long long)ni->mft_no);
goto put_err_out;
}
name = ntfs_ucsndup(attr_name, a->name_length);
if (!name)
goto put_err_out;
newname = name;
name_len = a->name_length;
} else {
name = AT_UNNAMED;
name_len = 0;
}
}
__ntfs_attr_init(na, ni, type, name, name_len);
if (type == AT_ATTRIBUTE_LIST)
a->flags = const_cpu_to_le16(0);
if ((type == AT_DATA)
&& (a->non_resident ? !a->initialized_size : !a->value_length)) {
a->flags &= ~ATTR_COMPRESSION_MASK;
if ((ni->flags & FILE_ATTR_COMPRESSED)
&& (ni->vol->major_ver >= 3)
&& NVolCompression(ni->vol)
&& (ni->vol->cluster_size <= MAX_COMPRESSION_CLUSTER_SIZE))
a->flags |= ATTR_IS_COMPRESSED;
}
cs = a->flags & (ATTR_IS_COMPRESSED | ATTR_IS_SPARSE);
if (na->type == AT_DATA && na->name == AT_UNNAMED &&
(((a->flags & ATTR_IS_SPARSE) && !NAttrSparse(na)) ||
(!(a->flags & ATTR_IS_ENCRYPTED) != !NAttrEncrypted(na)))) {
errno = EIO;
ntfs_log_perror("Inode %lld has corrupt attribute flags "
"(0x%x <> 0x%x)",(unsigned long long)ni->mft_no,
le16_to_cpu(a->flags), le32_to_cpu(na->ni->flags));
goto put_err_out;
}
if (a->non_resident) {
if (((a->flags & ATTR_COMPRESSION_MASK)
|| a->compression_unit)
&& (ni->vol->major_ver < 3)) {
errno = EIO;
ntfs_log_perror("Compressed inode %lld not allowed"
" on NTFS %d.%d",
(unsigned long long)ni->mft_no,
ni->vol->major_ver,
ni->vol->major_ver);
goto put_err_out;
}
if ((a->flags & ATTR_COMPRESSION_MASK)
&& !a->compression_unit) {
errno = EIO;
ntfs_log_perror("Compressed inode %lld attr 0x%x has "
"no compression unit",
(unsigned long long)ni->mft_no, le32_to_cpu(type));
goto put_err_out;
}
if ((a->flags & ATTR_COMPRESSION_MASK)
&& (a->compression_unit
!= STANDARD_COMPRESSION_UNIT)) {
errno = EIO;
ntfs_log_perror("Compressed inode %lld attr 0x%lx has "
"an unsupported compression unit %d",
(unsigned long long)ni->mft_no,
(long)le32_to_cpu(type),
(int)a->compression_unit);
goto put_err_out;
}
ntfs_attr_init(na, TRUE, a->flags,
a->flags & ATTR_IS_ENCRYPTED,
a->flags & ATTR_IS_SPARSE,
sle64_to_cpu(a->allocated_size),
sle64_to_cpu(a->data_size),
sle64_to_cpu(a->initialized_size),
cs ? sle64_to_cpu(a->compressed_size) : 0,
cs ? a->compression_unit : 0);
} else {
s64 l = le32_to_cpu(a->value_length);
ntfs_attr_init(na, FALSE, a->flags,
a->flags & ATTR_IS_ENCRYPTED,
a->flags & ATTR_IS_SPARSE, (l + 7) & ~7, l, l,
cs ? (l + 7) & ~7 : 0, 0);
}
ntfs_attr_put_search_ctx(ctx);
out:
ntfs_log_leave("\n");
return na;
put_err_out:
ntfs_attr_put_search_ctx(ctx);
err_out:
free(newname);
free(na);
na = NULL;
goto out;
}
void ntfs_attr_close(ntfs_attr *na)
{
if (!na)
return;
if (NAttrNonResident(na) && na->rl)
free(na->rl);
if (na->name != AT_UNNAMED && na->name != NTFS_INDEX_I30
&& na->name != STREAM_SDS)
free(na->name);
free(na);
}
int ntfs_attr_map_runlist(ntfs_attr *na, VCN vcn)
{
LCN lcn;
ntfs_attr_search_ctx *ctx;
ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x, vcn 0x%llx.\n",
(unsigned long long)na->ni->mft_no, le32_to_cpu(na->type), (long long)vcn);
lcn = ntfs_rl_vcn_to_lcn(na->rl, vcn);
if (lcn >= 0 || lcn == LCN_HOLE || lcn == LCN_ENOENT)
return 0;
ctx = ntfs_attr_get_search_ctx(na->ni, NULL);
if (!ctx)
return -1;
if (!ntfs_attr_lookup(na->type, na->name, na->name_len, CASE_SENSITIVE,
vcn, NULL, 0, ctx)) {
runlist_element *rl;
rl = ntfs_mapping_pairs_decompress(na->ni->vol, ctx->attr,
na->rl);
if (rl) {
na->rl = rl;
ntfs_attr_put_search_ctx(ctx);
return 0;
}
}
ntfs_attr_put_search_ctx(ctx);
return -1;
}
#if PARTIAL_RUNLIST_UPDATING
static int ntfs_attr_map_partial_runlist(ntfs_attr *na, VCN vcn)
{
VCN last_vcn;
VCN highest_vcn;
VCN needed;
runlist_element *rl;
ATTR_RECORD *a;
BOOL startseen;
ntfs_attr_search_ctx *ctx;
BOOL done;
BOOL newrunlist;
if (NAttrFullyMapped(na))
return 0;
ctx = ntfs_attr_get_search_ctx(na->ni, NULL);
if (!ctx)
return -1;
last_vcn = na->allocated_size >> na->ni->vol->cluster_size_bits;
needed = vcn;
highest_vcn = 0;
startseen = FALSE;
done = FALSE;
rl = (runlist_element*)NULL;
do {
newrunlist = FALSE;
if (!ntfs_attr_lookup(na->type, na->name, na->name_len, CASE_SENSITIVE,
needed, NULL, 0, ctx)) {
a = ctx->attr;
if (ntfs_rl_vcn_to_lcn(na->rl, needed)
== LCN_RL_NOT_MAPPED) {
rl = ntfs_mapping_pairs_decompress(na->ni->vol,
a, na->rl);
newrunlist = TRUE;
} else
rl = na->rl;
if (rl) {
na->rl = rl;
highest_vcn = sle64_to_cpu(a->highest_vcn);
if (highest_vcn < needed) {
if (newrunlist
&& ((highest_vcn + 1) < last_vcn)) {
ntfs_log_error("Corrupt attribute list\n");
rl = (runlist_element*)NULL;
errno = EIO;
}
done = TRUE;
}
needed = highest_vcn + 1;
if (!a->lowest_vcn)
startseen = TRUE;
}
} else {
done = TRUE;
}
} while (rl && !done && (needed < last_vcn));
ntfs_attr_put_search_ctx(ctx);
if (done && newrunlist && (needed < last_vcn)) {
ntfs_log_error("End of runlist not reached\n");
rl = (runlist_element*)NULL;
errno = EIO;
}
if (rl && startseen)
NAttrSetFullyMapped(na);
return (rl ? 0 : -1);
}
#endif
int ntfs_attr_map_whole_runlist(ntfs_attr *na)
{
VCN next_vcn, last_vcn, highest_vcn;
ntfs_attr_search_ctx *ctx;
ntfs_volume *vol = na->ni->vol;
ATTR_RECORD *a;
int ret = -1;
int not_mapped;
ntfs_log_enter("Entering for inode %llu, attr 0x%x.\n",
(unsigned long long)na->ni->mft_no, le32_to_cpu(na->type));
if (NAttrFullyMapped(na)) {
ret = 0;
goto out;
}
ctx = ntfs_attr_get_search_ctx(na->ni, NULL);
if (!ctx)
goto out;
next_vcn = last_vcn = highest_vcn = 0;
a = NULL;
while (1) {
runlist_element *rl;
not_mapped = 0;
if (ntfs_rl_vcn_to_lcn(na->rl, next_vcn) == LCN_RL_NOT_MAPPED)
not_mapped = 1;
if (ntfs_attr_lookup(na->type, na->name, na->name_len,
CASE_SENSITIVE, next_vcn, NULL, 0, ctx))
break;
a = ctx->attr;
if (not_mapped) {
rl = ntfs_mapping_pairs_decompress(na->ni->vol,
a, na->rl);
if (!rl)
goto err_out;
na->rl = rl;
}
if (!next_vcn) {
if (a->lowest_vcn) {
errno = EIO;
ntfs_log_perror("First extent of inode %llu "
"attribute has non-zero lowest_vcn",
(unsigned long long)na->ni->mft_no);
goto err_out;
}
last_vcn = sle64_to_cpu(a->allocated_size) >>
vol->cluster_size_bits;
}
highest_vcn = sle64_to_cpu(a->highest_vcn);
next_vcn = highest_vcn + 1;
if (next_vcn <= 0) {
errno = ENOENT;
break;
}
if (next_vcn < sle64_to_cpu(a->lowest_vcn)) {
errno = EIO;
ntfs_log_perror("Inode %llu has corrupt attribute list",
(unsigned long long)na->ni->mft_no);
goto err_out;
}
}
if (!a) {
ntfs_log_perror("Couldn't find attribute for runlist mapping");
goto err_out;
}
if (not_mapped && highest_vcn && highest_vcn != last_vcn - 1) {
errno = EIO;
ntfs_log_perror("Failed to load full runlist: inode: %llu "
"highest_vcn: 0x%llx last_vcn: 0x%llx",
(unsigned long long)na->ni->mft_no,
(long long)highest_vcn, (long long)last_vcn);
goto err_out;
}
if (errno == ENOENT) {
NAttrSetFullyMapped(na);
ret = 0;
}
err_out:
ntfs_attr_put_search_ctx(ctx);
out:
ntfs_log_leave("\n");
return ret;
}
LCN ntfs_attr_vcn_to_lcn(ntfs_attr *na, const VCN vcn)
{
LCN lcn;
BOOL is_retry = FALSE;
if (!na || !NAttrNonResident(na) || vcn < 0)
return (LCN)LCN_EINVAL;
ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x.\n", (unsigned long
long)na->ni->mft_no, le32_to_cpu(na->type));
retry:
lcn = ntfs_rl_vcn_to_lcn(na->rl, vcn);
if (lcn >= 0)
return lcn;
if (!is_retry && !ntfs_attr_map_runlist(na, vcn)) {
is_retry = TRUE;
goto retry;
}
if (!is_retry || lcn == (LCN)LCN_RL_NOT_MAPPED)
return (LCN)LCN_EIO;
return lcn;
}
runlist_element *ntfs_attr_find_vcn(ntfs_attr *na, const VCN vcn)
{
runlist_element *rl;
BOOL is_retry = FALSE;
if (!na || !NAttrNonResident(na) || vcn < 0) {
errno = EINVAL;
return NULL;
}
ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x, vcn %llx\n",
(unsigned long long)na->ni->mft_no, le32_to_cpu(na->type),
(long long)vcn);
retry:
rl = na->rl;
if (!rl)
goto map_rl;
if (vcn < rl[0].vcn)
goto map_rl;
while (rl->length) {
if (vcn < rl[1].vcn) {
if (rl->lcn >= (LCN)LCN_HOLE)
return rl;
break;
}
rl++;
}
switch (rl->lcn) {
case (LCN)LCN_RL_NOT_MAPPED:
goto map_rl;
case (LCN)LCN_ENOENT:
errno = ENOENT;
break;
case (LCN)LCN_EINVAL:
errno = EINVAL;
break;
default:
errno = EIO;
break;
}
return NULL;
map_rl:
if (!is_retry && !ntfs_attr_map_runlist(na, vcn)) {
is_retry = TRUE;
goto retry;
}
if (is_retry || errno == EINVAL || errno == ENOENT)
errno = EIO;
return NULL;
}
static s64 ntfs_attr_pread_i(ntfs_attr *na, const s64 pos, s64 count, void *b)
{
s64 br, to_read, ofs, total, total2, max_read, max_init;
ntfs_volume *vol;
runlist_element *rl;
u16 efs_padding_length;
if ((na->data_flags & ATTR_COMPRESSION_MASK) && NAttrNonResident(na)) {
if ((na->data_flags & ATTR_COMPRESSION_MASK)
== ATTR_IS_COMPRESSED)
return ntfs_compressed_attr_pread(na, pos, count, b);
else {
errno = EOPNOTSUPP;
return -1;
}
}
vol = na->ni->vol;
if (!vol->efs_raw && NAttrEncrypted(na) && NAttrNonResident(na)) {
errno = EACCES;
return -1;
}
if (!count)
return 0;
max_read = na->data_size;
max_init = na->initialized_size;
if (na->ni->vol->efs_raw
&& (na->data_flags & ATTR_IS_ENCRYPTED)
&& NAttrNonResident(na)) {
if (na->data_size != na->initialized_size) {
ntfs_log_error("uninitialized encrypted file not supported\n");
errno = EINVAL;
return -1;
}
max_init = max_read = ((na->data_size + 511) & ~511) + 2;
}
if (pos + count > max_read) {
if (pos >= max_read)
return 0;
count = max_read - pos;
}
if (!NAttrNonResident(na)) {
ntfs_attr_search_ctx *ctx;
char *val;
ctx = ntfs_attr_get_search_ctx(na->ni, NULL);
if (!ctx)
return -1;
if (ntfs_attr_lookup(na->type, na->name, na->name_len, 0,
0, NULL, 0, ctx)) {
res_err_out:
ntfs_attr_put_search_ctx(ctx);
return -1;
}
val = (char*)ctx->attr + le16_to_cpu(ctx->attr->value_offset);
if (val < (char*)ctx->attr || val +
le32_to_cpu(ctx->attr->value_length) >
(char*)ctx->mrec + vol->mft_record_size) {
errno = EIO;
ntfs_log_perror("%s: Sanity check failed", __FUNCTION__);
goto res_err_out;
}
memcpy(b, val + pos, count);
ntfs_attr_put_search_ctx(ctx);
return count;
}
total = total2 = 0;
if (pos + count > max_init) {
if (pos >= max_init) {
memset(b, 0, count);
return count;
}
total2 = pos + count - max_init;
count -= total2;
memset((u8*)b + count, 0, total2);
}
if (na->ni->vol->efs_raw &&
(na->data_flags & ATTR_IS_ENCRYPTED) &&
((pos + count) > max_init-2)) {
efs_padding_length = 511 - ((na->data_size - 1) & 511);
if (pos+count == max_init) {
if (count == 1) {
*((u8*)b+count-1) = (u8)(efs_padding_length >> 8);
count--;
total2++;
} else {
*(le16*)((u8*)b+count-2) = cpu_to_le16(efs_padding_length);
count -= 2;
total2 +=2;
}
} else {
*((u8*)b+count-1) = (u8)(efs_padding_length & 0xff);
count--;
total2++;
}
}
rl = ntfs_attr_find_vcn(na, pos >> vol->cluster_size_bits);
if (!rl) {
if (errno == ENOENT) {
errno = EIO;
ntfs_log_perror("%s: Failed to find VCN #1", __FUNCTION__);
}
return -1;
}
ofs = pos - (rl->vcn << vol->cluster_size_bits);
for (; count; rl++, ofs = 0) {
if (rl->lcn == LCN_RL_NOT_MAPPED) {
rl = ntfs_attr_find_vcn(na, rl->vcn);
if (!rl) {
if (errno == ENOENT) {
errno = EIO;
ntfs_log_perror("%s: Failed to find VCN #2",
__FUNCTION__);
}
goto rl_err_out;
}
ofs = pos + total - (rl->vcn << vol->cluster_size_bits);
}
if (!rl->length) {
errno = EIO;
ntfs_log_perror("%s: Zero run length", __FUNCTION__);
goto rl_err_out;
}
if (rl->lcn < (LCN)0) {
if (rl->lcn != (LCN)LCN_HOLE) {
ntfs_log_perror("%s: Bad run (%lld)",
__FUNCTION__,
(long long)rl->lcn);
goto rl_err_out;
}
to_read = min(count, (rl->length <<
vol->cluster_size_bits) - ofs);
memset(b, 0, to_read);
total += to_read;
count -= to_read;
b = (u8*)b + to_read;
continue;
}
to_read = min(count, (rl->length << vol->cluster_size_bits) -
ofs);
retry:
ntfs_log_trace("Reading %lld bytes from vcn %lld, lcn %lld, ofs"
" %lld.\n", (long long)to_read, (long long)rl->vcn,
(long long )rl->lcn, (long long)ofs);
br = ntfs_pread(vol->dev, (rl->lcn << vol->cluster_size_bits) +
ofs, to_read, b);
if (br > 0) {
total += br;
count -= br;
b = (u8*)b + br;
}
if (br == to_read)
continue;
if (br == (s64)-1 && errno == EINTR)
goto retry;
if (total)
return total;
if (!br)
errno = EIO;
ntfs_log_perror("%s: ntfs_pread failed", __FUNCTION__);
return -1;
}
return total + total2;
rl_err_out:
if (total)
return total;
errno = EIO;
return -1;
}
s64 ntfs_attr_pread(ntfs_attr *na, const s64 pos, s64 count, void *b)
{
s64 ret;
if (!na || !na->ni || !na->ni->vol || !b || pos < 0 || count < 0) {
errno = EINVAL;
ntfs_log_perror("%s: na=%p b=%p pos=%lld count=%lld",
__FUNCTION__, na, b, (long long)pos,
(long long)count);
return -1;
}
ntfs_log_enter("Entering for inode %lld attr 0x%x pos %lld count "
"%lld\n", (unsigned long long)na->ni->mft_no,
le32_to_cpu(na->type), (long long)pos, (long long)count);
ret = ntfs_attr_pread_i(na, pos, count, b);
ntfs_log_leave("\n");
return ret;
}
static int ntfs_attr_fill_zero(ntfs_attr *na, s64 pos, s64 count)
{
char *buf;
s64 written, size, end = pos + count;
s64 ofsi;
const runlist_element *rli;
ntfs_volume *vol;
int ret = -1;
ntfs_log_trace("pos %lld, count %lld\n", (long long)pos,
(long long)count);
if (!na || pos < 0 || count < 0) {
errno = EINVAL;
goto err_out;
}
buf = ntfs_calloc(NTFS_BUF_SIZE);
if (!buf)
goto err_out;
rli = na->rl;
ofsi = 0;
vol = na->ni->vol;
while (pos < end) {
while (rli->length && (ofsi + (rli->length <<
vol->cluster_size_bits) <= pos)) {
ofsi += (rli->length << vol->cluster_size_bits);
rli++;
}
size = min(end - pos, NTFS_BUF_SIZE);
if ((rli->lcn == (LCN)LCN_HOLE)
&& (ofsi <= pos)
&& (ofsi + (rli->length << vol->cluster_size_bits)
>= (pos + size))) {
size = min(end - pos, ofsi - pos
+ (rli->length << vol->cluster_size_bits));
pos += size;
} else {
written = ntfs_rl_pwrite(vol, rli, ofsi, pos,
size, buf);
if (written <= 0) {
ntfs_log_perror("Failed to zero space");
goto err_free;
}
pos += written;
}
}
ret = 0;
err_free:
free(buf);
err_out:
return ret;
}
static int ntfs_attr_fill_hole(ntfs_attr *na, s64 count, s64 *ofs,
runlist_element **rl, VCN *update_from)
{
s64 to_write;
s64 need;
ntfs_volume *vol = na->ni->vol;
int eo, ret = -1;
runlist *rlc;
LCN lcn_seek_from = -1;
VCN cur_vcn, from_vcn;
if (na->ni->mft_no == FILE_Bitmap) {
ntfs_log_error("Corrupt $BitMap not fully allocated\n");
errno = EIO;
goto err_out;
}
to_write = min(count, ((*rl)->length << vol->cluster_size_bits) - *ofs);
cur_vcn = (*rl)->vcn;
from_vcn = (*rl)->vcn + (*ofs >> vol->cluster_size_bits);
ntfs_log_trace("count: %lld, cur_vcn: %lld, from: %lld, to: %lld, ofs: "
"%lld\n", (long long)count, (long long)cur_vcn,
(long long)from_vcn, (long long)to_write, (long long)*ofs);
#if PARTIAL_RUNLIST_UPDATING
if (!na->rl) {
if (ntfs_attr_map_whole_runlist(na))
goto err_out;
} else {
if ((*rl)->lcn == LCN_HOLE) {
if (ntfs_attr_map_partial_runlist(na,
(cur_vcn ? cur_vcn - 1 : cur_vcn)))
goto err_out;
}
}
#else
if (ntfs_attr_map_whole_runlist(na))
goto err_out;
#endif
*rl = ntfs_attr_find_vcn(na, cur_vcn);
if (!*rl) {
ntfs_log_error("Failed to find run after mapping runlist. "
"Please report to %s.\n", NTFS_DEV_LIST);
errno = EIO;
goto err_out;
}
rlc = *rl;
while (rlc->vcn) {
rlc--;
if (rlc->lcn >= 0) {
if (na->data_flags & ATTR_COMPRESSION_MASK)
lcn_seek_from = rlc->lcn + rlc->length;
else
lcn_seek_from = rlc->lcn + (from_vcn - rlc->vcn);
break;
}
}
if (lcn_seek_from == -1) {
rlc = *rl;
while (rlc->length) {
rlc++;
if (rlc->lcn >= 0) {
lcn_seek_from = rlc->lcn - (rlc->vcn - from_vcn);
if (lcn_seek_from < -1)
lcn_seek_from = -1;
break;
}
}
}
need = ((*ofs + to_write - 1) >> vol->cluster_size_bits)
+ 1 + (*rl)->vcn - from_vcn;
if ((na->data_flags & ATTR_COMPRESSION_MASK)
&& (need < na->compression_block_clusters)) {
VCN alloc_vcn;
if ((from_vcn & -na->compression_block_clusters) <= (*rl)->vcn)
alloc_vcn = (*rl)->vcn;
else
alloc_vcn = from_vcn & -na->compression_block_clusters;
need = (alloc_vcn | (na->compression_block_clusters - 1))
+ 1 - alloc_vcn;
if (need > (*rl)->length) {
ntfs_log_error("Cannot allocate %lld clusters"
" within a hole of %lld\n",
(long long)need,
(long long)(*rl)->length);
errno = EIO;
goto err_out;
}
rlc = ntfs_cluster_alloc(vol, alloc_vcn, need,
lcn_seek_from, DATA_ZONE);
} else
rlc = ntfs_cluster_alloc(vol, from_vcn, need,
lcn_seek_from, DATA_ZONE);
if (!rlc)
goto err_out;
if (na->data_flags & (ATTR_COMPRESSION_MASK | ATTR_IS_SPARSE))
na->compressed_size += need << vol->cluster_size_bits;
*rl = ntfs_runlists_merge(na->rl, rlc);
NAttrSetRunlistDirty(na);
if (*rl && (na->data_flags & ATTR_COMPRESSION_MASK)) {
runlist_element *oldrl = na->rl;
na->rl = *rl;
*rl = ntfs_rl_extend(na,*rl,2);
if (!*rl) na->rl = oldrl;
}
if (!*rl) {
eo = errno;
ntfs_log_perror("Failed to merge runlists");
if (ntfs_cluster_free_from_rl(vol, rlc)) {
ntfs_log_perror("Failed to free hot clusters. "
"Please run chkdsk /f");
}
errno = eo;
goto err_out;
}
na->unused_runs = 2;
na->rl = *rl;
if ((*update_from == -1) || (from_vcn < *update_from))
*update_from = from_vcn;
*rl = ntfs_attr_find_vcn(na, cur_vcn);
if (!*rl) {
ntfs_log_error("Failed to find run after hole instantiation. "
"Please report to %s.\n", NTFS_DEV_LIST);
errno = EIO;
goto err_out;
}
if ((*rl)->lcn < 0)
(*rl)++;
if ((*rl)->lcn < 0) {
ntfs_log_error("BUG! LCN is lesser than 0. "
"Please report to the %s.\n", NTFS_DEV_LIST);
errno = EIO;
goto err_out;
}
if (*ofs) {
if (ntfs_attr_fill_zero(na, cur_vcn << vol->cluster_size_bits,
*ofs))
goto err_out;
}
if ((*rl)->vcn < cur_vcn) {
*ofs += (cur_vcn - (*rl)->vcn) << vol->cluster_size_bits;
}
if ((*rl)->vcn > cur_vcn) {
*ofs -= ((*rl)->vcn - cur_vcn) << vol->cluster_size_bits;
}
ret = 0;
err_out:
return ret;
}
static int stuff_hole(ntfs_attr *na, const s64 pos);
static int split_compressed_hole(ntfs_attr *na, runlist_element **prl,
s64 pos, s64 count, VCN *update_from)
{
int compressed_part;
int cluster_size_bits = na->ni->vol->cluster_size_bits;
runlist_element *rl = *prl;
compressed_part
= na->compression_block_clusters;
if (rl->length > na->compression_block_clusters) {
*prl = ntfs_rl_extend(na,*prl,2);
if (!*prl) {
compressed_part = -1;
} else {
rl = *prl;
na->unused_runs = 2;
}
}
if (*prl && (rl->length > na->compression_block_clusters)) {
int beginwrite = (pos >> cluster_size_bits) - rl->vcn;
s32 endblock = (((pos + count - 1) >> cluster_size_bits)
| (na->compression_block_clusters - 1)) + 1 - rl->vcn;
compressed_part = na->compression_block_clusters
- (rl->length & (na->compression_block_clusters - 1));
if ((beginwrite + compressed_part) >= na->compression_block_clusters)
compressed_part = na->compression_block_clusters;
if (endblock < rl[0].length) {
runlist_element *xrl;
int n;
if (endblock > na->compression_block_clusters) {
if (na->unused_runs < 2) {
ntfs_log_error("No free run, case 1\n");
}
na->unused_runs -= 2;
xrl = rl;
n = 0;
while (xrl->length) {
xrl++;
n++;
}
do {
xrl[2] = *xrl;
xrl--;
} while (xrl != rl);
rl[1].length = na->compression_block_clusters;
rl[2].length = rl[0].length - endblock;
rl[0].length = endblock
- na->compression_block_clusters;
rl[1].lcn = LCN_HOLE;
rl[2].lcn = LCN_HOLE;
rl[1].vcn = rl[0].vcn + rl[0].length;
rl[2].vcn = rl[1].vcn
+ na->compression_block_clusters;
rl = ++(*prl);
} else {
if (!na->unused_runs) {
ntfs_log_error("No free run, case 2\n");
}
na->unused_runs--;
xrl = rl;
n = 0;
while (xrl->length) {
xrl++;
n++;
}
do {
xrl[1] = *xrl;
xrl--;
} while (xrl != rl);
if (beginwrite < endblock) {
rl[1].length = rl[0].length - endblock;
rl[0].length = endblock;
rl[1].vcn = rl[0].vcn + rl[0].length;
rl[1].lcn = LCN_HOLE;
} else {
rl[1].length = rl[0].length - endblock;
rl[0].length = endblock;
rl[1].vcn = rl[0].vcn + rl[0].length;
rl[1].lcn = LCN_HOLE;
rl = ++(*prl);
}
}
} else {
if (rl[1].length) {
runlist_element *xrl;
int n;
if (!na->unused_runs) {
ntfs_log_error("No free run, case 4\n");
}
na->unused_runs--;
xrl = rl;
n = 0;
while (xrl->length) {
xrl++;
n++;
}
do {
xrl[1] = *xrl;
xrl--;
} while (xrl != rl);
} else {
rl[2].lcn = rl[1].lcn;
rl[2].vcn = rl[1].vcn;
rl[2].length = rl[1].length;
}
rl[1].vcn -= na->compression_block_clusters;
rl[1].lcn = LCN_HOLE;
rl[1].length = na->compression_block_clusters;
rl[0].length -= na->compression_block_clusters;
if (pos >= (rl[1].vcn << cluster_size_bits)) {
rl = ++(*prl);
}
}
NAttrSetRunlistDirty(na);
if ((*update_from == -1) || ((*prl)->vcn < *update_from))
*update_from = (*prl)->vcn;
}
return (compressed_part);
}
static int borrow_from_hole(ntfs_attr *na, runlist_element **prl,
s64 pos, s64 count, VCN *update_from, BOOL wasnonresident)
{
int compressed_part = 0;
int cluster_size_bits = na->ni->vol->cluster_size_bits;
runlist_element *rl = *prl;
s32 endblock;
long long allocated;
runlist_element *zrl;
int irl;
BOOL undecided;
BOOL nothole;
endblock = (((pos + count - 1) >> cluster_size_bits) | (na->compression_block_clusters - 1)) + 1 - rl->vcn;
allocated = 0;
zrl = rl;
irl = 0;
while (zrl->length && (zrl->lcn >= 0) && (allocated < endblock)) {
allocated += zrl->length;
zrl++;
irl++;
}
undecided = (allocated < endblock) && (zrl->lcn == LCN_RL_NOT_MAPPED);
nothole = (allocated >= endblock) || (zrl->lcn != LCN_HOLE);
if (undecided || nothole) {
runlist_element *orl = na->rl;
s64 olcn = (*prl)->lcn;
#if PARTIAL_RUNLIST_UPDATING
VCN prevblock;
#endif
irl = *prl - na->rl;
#if PARTIAL_RUNLIST_UPDATING
prevblock = pos >> cluster_size_bits;
if (prevblock)
prevblock--;
if (!NAttrBeingNonResident(na)
&& (NAttrDataAppending(na)
? ntfs_attr_map_partial_runlist(na,prevblock)
: ntfs_attr_map_whole_runlist(na))) {
#else
if (!NAttrBeingNonResident(na)
&& ntfs_attr_map_whole_runlist(na)) {
#endif
rl = (runlist_element*)NULL;
} else {
if ((na->rl != orl) || ((*prl)->lcn != olcn)) {
zrl = &na->rl[irl];
while (zrl->length && (zrl->lcn != olcn))
zrl++;
*prl = zrl;
}
if (!(*prl)->length) {
ntfs_log_error("Mapped run not found,"
" inode %lld lcn 0x%llx\n",
(long long)na->ni->mft_no,
(long long)olcn);
rl = (runlist_element*)NULL;
} else {
rl = ntfs_rl_extend(na,*prl,2);
na->unused_runs = 2;
}
}
*prl = rl;
if (rl && undecided) {
allocated = 0;
zrl = rl;
irl = 0;
while (zrl->length && (zrl->lcn >= 0)
&& (allocated < endblock)) {
allocated += zrl->length;
zrl++;
irl++;
}
}
}
if (rl && (allocated < endblock) && (zrl->lcn == LCN_HOLE)) {
s64 xofs;
if ((allocated + zrl->length) > endblock) {
runlist_element *xrl;
*prl = ntfs_rl_extend(na,*prl,1);
if (*prl) {
rl = *prl;
zrl = &rl[irl];
na->unused_runs = 0;
xrl = zrl;
while (xrl->length) xrl++;
do {
xrl[1] = *xrl;
} while (xrl-- != zrl);
zrl->length = endblock - allocated;
zrl[1].length -= zrl->length;
zrl[1].vcn = zrl->vcn + zrl->length;
NAttrSetRunlistDirty(na);
}
}
if (*prl) {
if (wasnonresident)
compressed_part = na->compression_block_clusters
- zrl->length;
xofs = 0;
if (ntfs_attr_fill_hole(na,
zrl->length << cluster_size_bits,
&xofs, &zrl, update_from))
compressed_part = -1;
else {
while (zrl->vcn > (pos >> cluster_size_bits))
zrl--;
*prl = zrl;
}
}
}
if (!*prl) {
ntfs_log_error("No elements to borrow from a hole\n");
compressed_part = -1;
} else
if ((*update_from == -1) || ((*prl)->vcn < *update_from))
*update_from = (*prl)->vcn;
return (compressed_part);
}
static int ntfs_attr_truncate_i(ntfs_attr *na, const s64 newsize,
hole_type holes);
static s64 ntfs_attr_pwrite_i(ntfs_attr *na, const s64 pos, s64 count,
const void *b)
{
s64 written, to_write, ofs, old_initialized_size, old_data_size;
s64 total = 0;
VCN update_from = -1;
ntfs_volume *vol;
s64 fullcount;
ntfs_attr_search_ctx *ctx = NULL;
runlist_element *rl;
s64 hole_end;
int eo;
int compressed_part;
struct {
unsigned int undo_initialized_size : 1;
unsigned int undo_data_size : 1;
} need_to = { 0, 0 };
BOOL wasnonresident = FALSE;
BOOL compressed;
vol = na->ni->vol;
compressed = (na->data_flags & ATTR_COMPRESSION_MASK)
!= const_cpu_to_le16(0);
na->unused_runs = 0;
if ((na->data_flags & ATTR_IS_ENCRYPTED)
&& (compressed || !vol->efs_raw)) {
errno = EACCES;
goto errno_set;
}
if (compressed
&& (na->type == AT_DATA)
&& (pos > na->initialized_size)
&& stuff_hole(na,pos))
goto errno_set;
wasnonresident = NAttrNonResident(na) != 0;
if (compressed
&& ((na->type != AT_DATA)
|| ((na->data_flags & ATTR_COMPRESSION_MASK)
!= ATTR_IS_COMPRESSED))) {
errno = EOPNOTSUPP;
goto errno_set;
}
if (!count)
goto out;
fullcount = count;
old_data_size = na->data_size;
if ((na->type == AT_DATA) && (pos >= old_data_size)
&& NAttrNonResident(na))
NAttrSetDataAppending(na);
if (pos + count > na->data_size) {
#if PARTIAL_RUNLIST_UPDATING
if (ntfs_attr_truncate_i(na, pos + count,
(NAttrDataAppending(na) ?
HOLES_DELAY : HOLES_OK))) {
ntfs_log_perror("Failed to enlarge attribute");
goto errno_set;
}
if (NAttrDataAppending(na))
need_to.undo_data_size = 1;
#else
if (ntfs_attr_truncate_i(na, pos + count, HOLES_OK)) {
ntfs_log_perror("Failed to enlarge attribute");
goto errno_set;
}
#endif
compressed = (na->data_flags & ATTR_COMPRESSION_MASK)
!= const_cpu_to_le16(0);
need_to.undo_data_size = 1;
}
if (compressed) {
fullcount = (pos | (na->compression_block_size - 1)) + 1 - pos;
if (count > fullcount)
count = fullcount;
}
old_initialized_size = na->initialized_size;
if (!NAttrNonResident(na)) {
char *val;
ctx = ntfs_attr_get_search_ctx(na->ni, NULL);
if (!ctx)
goto err_out;
if (ntfs_attr_lookup(na->type, na->name, na->name_len, 0,
0, NULL, 0, ctx)) {
ntfs_log_perror("%s: lookup failed", __FUNCTION__);
goto err_out;
}
val = (char*)ctx->attr + le16_to_cpu(ctx->attr->value_offset);
if (val < (char*)ctx->attr || val +
le32_to_cpu(ctx->attr->value_length) >
(char*)ctx->mrec + vol->mft_record_size) {
errno = EIO;
ntfs_log_perror("%s: Sanity check failed", __FUNCTION__);
goto err_out;
}
memcpy(val + pos, b, count);
if (ntfs_mft_record_write(vol, ctx->ntfs_ino->mft_no,
ctx->mrec)) {
ntfs_log_perror("%s: failed to write mft record", __FUNCTION__);
goto err_out;
}
ntfs_attr_put_search_ctx(ctx);
total = count;
goto out;
}
if (pos + count > na->initialized_size) {
#if PARTIAL_RUNLIST_UPDATING
if (compressed && !NAttrDataAppending(na)) {
if (ntfs_attr_map_whole_runlist(na))
goto err_out;
} else {
VCN block_begin;
if (NAttrDataAppending(na)
|| (pos < na->initialized_size))
block_begin = pos >> vol->cluster_size_bits;
else
block_begin = na->initialized_size >> vol->cluster_size_bits;
if (compressed)
block_begin &= -na->compression_block_clusters;
if (block_begin)
block_begin--;
if (ntfs_attr_map_partial_runlist(na, block_begin))
goto err_out;
if ((update_from == -1) || (block_begin < update_from))
update_from = block_begin;
}
#else
if (ntfs_attr_map_whole_runlist(na))
goto err_out;
#endif
if (compressed) {
na->rl = ntfs_rl_extend(na,na->rl,2);
if (!na->rl)
goto err_out;
na->unused_runs = 2;
}
ctx = ntfs_attr_get_search_ctx(na->ni, NULL);
if (!ctx)
goto err_out;
if (ntfs_attr_lookup(na->type, na->name, na->name_len, 0,
0, NULL, 0, ctx))
goto err_out;
if (pos > na->initialized_size)
if (ntfs_attr_fill_zero(na, na->initialized_size,
pos - na->initialized_size))
goto err_out;
ctx->attr->initialized_size = cpu_to_sle64(pos + count);
if (compressed) {
na->data_size = pos + count;
ctx->attr->data_size = ctx->attr->initialized_size;
}
if (ntfs_mft_record_write(vol, ctx->ntfs_ino->mft_no,
ctx->mrec)) {
ctx->attr->initialized_size =
cpu_to_sle64(old_initialized_size);
ntfs_mft_record_write(vol, ctx->ntfs_ino->mft_no,
ctx->mrec);
goto err_out;
}
na->initialized_size = pos + count;
#if CACHE_NIDATA_SIZE
if (na->ni->mrec->flags & MFT_RECORD_IS_DIRECTORY
? na->type == AT_INDEX_ROOT && na->name == NTFS_INDEX_I30
: na->type == AT_DATA && na->name == AT_UNNAMED) {
na->ni->data_size = na->data_size;
if ((compressed || NAttrSparse(na))
&& NAttrNonResident(na))
na->ni->allocated_size = na->compressed_size;
else
na->ni->allocated_size = na->allocated_size;
set_nino_flag(na->ni,KnownSize);
}
#endif
ntfs_attr_put_search_ctx(ctx);
ctx = NULL;
need_to.undo_initialized_size = 1;
}
rl = ntfs_attr_find_vcn(na, pos >> vol->cluster_size_bits);
if (!rl) {
if (errno == ENOENT) {
errno = EIO;
ntfs_log_perror("%s: Failed to find VCN #3", __FUNCTION__);
}
goto err_out;
}
compressed_part = 0;
if (compressed) {
if ((rl->lcn == (LCN)LCN_HOLE)
&& wasnonresident) {
if (rl->length < na->compression_block_clusters)
compressed_part
= na->compression_block_clusters
- rl->length;
else {
compressed_part = split_compressed_hole(na,
&rl, pos, count, &update_from);
}
} else {
if (rl->lcn >= 0) {
compressed_part = borrow_from_hole(na,
&rl, pos, count, &update_from,
wasnonresident);
}
}
if (compressed_part < 0)
goto err_out;
if (NAttrBeingNonResident(na)
&& (compressed_part < na->compression_block_clusters))
compressed_part = 0;
}
ofs = pos - (rl->vcn << vol->cluster_size_bits);
for (hole_end = 0; count; rl++, ofs = 0) {
if (rl->lcn == LCN_RL_NOT_MAPPED) {
rl = ntfs_attr_find_vcn(na, rl->vcn);
if (!rl) {
if (errno == ENOENT) {
errno = EIO;
ntfs_log_perror("%s: Failed to find VCN"
" #4", __FUNCTION__);
}
goto rl_err_out;
}
ofs = pos + total - (rl->vcn << vol->cluster_size_bits);
}
if (!rl->length) {
errno = EIO;
ntfs_log_perror("%s: Zero run length", __FUNCTION__);
goto rl_err_out;
}
if (rl->lcn < (LCN)0) {
hole_end = rl->vcn + rl->length;
if (rl->lcn != (LCN)LCN_HOLE) {
errno = EIO;
ntfs_log_perror("%s: Unexpected LCN (%lld)",
__FUNCTION__,
(long long)rl->lcn);
goto rl_err_out;
}
if (ntfs_attr_fill_hole(na, fullcount, &ofs, &rl,
&update_from))
goto err_out;
}
if (compressed) {
while (rl->length
&& (ofs >= (rl->length << vol->cluster_size_bits))) {
ofs -= rl->length << vol->cluster_size_bits;
rl++;
}
}
to_write = min(count, (rl->length << vol->cluster_size_bits) - ofs);
retry:
ntfs_log_trace("Writing %lld bytes to vcn %lld, lcn %lld, ofs "
"%lld.\n", (long long)to_write, (long long)rl->vcn,
(long long)rl->lcn, (long long)ofs);
if (!NVolReadOnly(vol)) {
s64 wpos = (rl->lcn << vol->cluster_size_bits) + ofs;
s64 wend = (rl->vcn << vol->cluster_size_bits) + ofs + to_write;
u32 bsize = vol->cluster_size;
s64 rounding = ((wend + bsize - 1) & ~(s64)(bsize - 1)) - wend;
if (rounding && ((wend == na->initialized_size) ||
(wend < (hole_end << vol->cluster_size_bits)))){
char *cb;
rounding += to_write;
cb = ntfs_malloc(rounding);
if (!cb)
goto err_out;
memcpy(cb, b, to_write);
memset(cb + to_write, 0, rounding - to_write);
if (compressed) {
written = ntfs_compressed_pwrite(na,
rl, wpos, ofs, to_write,
rounding, cb, compressed_part,
&update_from);
} else {
written = ntfs_pwrite(vol->dev, wpos,
rounding, cb);
if (written == rounding)
written = to_write;
}
free(cb);
} else {
if (compressed) {
written = ntfs_compressed_pwrite(na,
rl, wpos, ofs, to_write,
to_write, b, compressed_part,
&update_from);
} else
written = ntfs_pwrite(vol->dev, wpos,
to_write, b);
}
} else
written = to_write;
if (written > 0) {
total += written;
count -= written;
fullcount -= written;
b = (const u8*)b + written;
}
if (written != to_write) {
if (written == (s64)-1 && errno == EINTR)
goto retry;
if (!written)
errno = EIO;
goto rl_err_out;
}
compressed_part = 0;
}
done:
if (ctx)
ntfs_attr_put_search_ctx(ctx);
if (NAttrRunlistDirty(na)) {
if (ntfs_attr_update_mapping_pairs(na,
(update_from < 0 ? 0 : update_from))) {
total = -1;
goto out;
}
if (!wasnonresident)
NAttrClearBeingNonResident(na);
NAttrClearDataAppending(na);
}
out:
return total;
rl_err_out:
eo = errno;
if (total) {
if (need_to.undo_initialized_size) {
if (pos + total > na->initialized_size)
goto done;
goto err_out;
}
goto done;
}
errno = eo;
err_out:
eo = errno;
if (need_to.undo_initialized_size) {
int err;
err = 0;
if (!ctx) {
ctx = ntfs_attr_get_search_ctx(na->ni, NULL);
if (!ctx)
err = 1;
} else
ntfs_attr_reinit_search_ctx(ctx);
if (!err) {
err = ntfs_attr_lookup(na->type, na->name,
na->name_len, 0, 0, NULL, 0, ctx);
if (!err) {
na->initialized_size = old_initialized_size;
ctx->attr->initialized_size = cpu_to_sle64(
old_initialized_size);
err = ntfs_mft_record_write(vol,
ctx->ntfs_ino->mft_no,
ctx->mrec);
}
}
if (err) {
ntfs_log_error("Eeek! Failed to recover from error. "
"Leaving metadata in inconsistent "
"state! Run chkdsk!\n");
}
}
if (ctx)
ntfs_attr_put_search_ctx(ctx);
if (NAttrRunlistDirty(na))
ntfs_attr_update_mapping_pairs(na, 0);
if (need_to.undo_data_size
&& ntfs_attr_truncate_i(na, old_data_size, HOLES_OK))
ntfs_log_perror("Failed to restore data_size");
errno = eo;
errno_set:
total = -1;
goto out;
}
s64 ntfs_attr_pwrite(ntfs_attr *na, const s64 pos, s64 count, const void *b)
{
s64 total;
s64 written;
ntfs_log_enter("Entering for inode %lld, attr 0x%x, pos 0x%llx, count "
"0x%llx.\n", (long long)na->ni->mft_no, le32_to_cpu(na->type),
(long long)pos, (long long)count);
total = 0;
if (!na || !na->ni || !na->ni->vol || !b || pos < 0 || count < 0) {
errno = EINVAL;
written = -1;
ntfs_log_perror("%s", __FUNCTION__);
goto out;
}
do {
written = ntfs_attr_pwrite_i(na, pos + total,
count - total, (const u8*)b + total);
if (written > 0)
total += written;
} while ((written > 0) && (total < count));
out :
ntfs_log_leave("\n");
return (total > 0 ? total : written);
}
int ntfs_attr_pclose(ntfs_attr *na)
{
s64 ofs;
int failed;
BOOL ok = TRUE;
VCN update_from = -1;
ntfs_volume *vol;
ntfs_attr_search_ctx *ctx = NULL;
runlist_element *rl;
int eo;
int compressed_part;
BOOL compressed;
ntfs_log_enter("Entering for inode 0x%llx, attr 0x%x.\n",
(unsigned long long)na->ni->mft_no,
le32_to_cpu(na->type));
if (!na || !na->ni || !na->ni->vol) {
errno = EINVAL;
ntfs_log_perror("%s", __FUNCTION__);
goto errno_set;
}
vol = na->ni->vol;
na->unused_runs = 0;
compressed = (na->data_flags & ATTR_COMPRESSION_MASK)
!= const_cpu_to_le16(0);
if (NAttrEncrypted(na) && NAttrNonResident(na)) {
errno = EACCES;
goto errno_set;
}
if (!compressed || !NAttrNonResident(na))
goto out;
if (NAttrComprClosing(na)) {
errno = EIO;
ntfs_log_error("Bad ntfs_attr_pclose"
" recursion on inode %lld\n",
(long long)na->ni->mft_no);
goto out;
}
NAttrSetComprClosing(na);
if (ntfs_attr_map_whole_runlist(na))
goto err_out;
na->rl = ntfs_rl_extend(na,na->rl,2);
if (!na->rl)
goto err_out;
na->unused_runs = 2;
rl = ntfs_attr_find_vcn(na, (na->initialized_size - 1) >> vol->cluster_size_bits);
if (!rl) {
if (errno == ENOENT) {
errno = EIO;
ntfs_log_perror("%s: Failed to find VCN #5", __FUNCTION__);
}
goto err_out;
}
compressed_part = 0;
if (rl->lcn >= 0) {
runlist_element *xrl;
xrl = rl;
do {
xrl++;
} while (xrl->lcn >= 0);
compressed_part = (-xrl->length)
& (na->compression_block_clusters - 1);
} else
if (rl->lcn == (LCN)LCN_HOLE) {
if (rl->length < na->compression_block_clusters)
compressed_part
= na->compression_block_clusters
- rl->length;
else
compressed_part
= na->compression_block_clusters;
}
if (compressed_part)
goto out;
ofs = na->initialized_size - (rl->vcn << vol->cluster_size_bits);
if (rl->lcn == LCN_RL_NOT_MAPPED) {
rl = ntfs_attr_find_vcn(na, rl->vcn);
if (!rl) {
if (errno == ENOENT) {
errno = EIO;
ntfs_log_perror("%s: Failed to find VCN"
" #6", __FUNCTION__);
}
goto rl_err_out;
}
ofs = na->initialized_size - (rl->vcn << vol->cluster_size_bits);
}
if (!rl->length) {
errno = EIO;
ntfs_log_perror("%s: Zero run length", __FUNCTION__);
goto rl_err_out;
}
if (rl->lcn < (LCN)0) {
if (rl->lcn != (LCN)LCN_HOLE) {
errno = EIO;
ntfs_log_perror("%s: Unexpected LCN (%lld)",
__FUNCTION__,
(long long)rl->lcn);
goto rl_err_out;
}
if (ntfs_attr_fill_hole(na, (s64)0, &ofs, &rl, &update_from))
goto err_out;
}
while (rl->length
&& (ofs >= (rl->length << vol->cluster_size_bits))) {
ofs -= rl->length << vol->cluster_size_bits;
rl++;
}
retry:
failed = 0;
if (update_from < 0) update_from = 0;
if (!NVolReadOnly(vol)) {
failed = ntfs_compressed_close(na, rl, ofs, &update_from);
#if CACHE_NIDATA_SIZE
if (na->ni->mrec->flags & MFT_RECORD_IS_DIRECTORY
? na->type == AT_INDEX_ROOT && na->name == NTFS_INDEX_I30
: na->type == AT_DATA && na->name == AT_UNNAMED) {
na->ni->data_size = na->data_size;
na->ni->allocated_size = na->compressed_size;
set_nino_flag(na->ni,KnownSize);
}
#endif
}
if (failed) {
if (errno == EINTR)
goto retry;
else
goto rl_err_out;
}
if (ctx)
ntfs_attr_put_search_ctx(ctx);
if (NAttrFullyMapped(na))
if (ntfs_attr_update_mapping_pairs(na, update_from)) {
ok = FALSE;
goto out;
}
out:
NAttrClearComprClosing(na);
ntfs_log_leave("\n");
return (!ok);
rl_err_out:
err_out:
eo = errno;
if (ctx)
ntfs_attr_put_search_ctx(ctx);
if (NAttrFullyMapped(na))
ntfs_attr_update_mapping_pairs(na, 0);
errno = eo;
errno_set:
ok = FALSE;
goto out;
}
s64 ntfs_attr_mst_pread(ntfs_attr *na, const s64 pos, const s64 bk_cnt,
const u32 bk_size, void *dst)
{
s64 br;
u8 *end;
BOOL warn;
ntfs_log_trace("Entering for inode 0x%llx, attr type 0x%x, pos 0x%llx.\n",
(unsigned long long)na->ni->mft_no, le32_to_cpu(na->type),
(long long)pos);
if (bk_cnt < 0 || bk_size % NTFS_BLOCK_SIZE) {
errno = EINVAL;
ntfs_log_perror("%s", __FUNCTION__);
return -1;
}
br = ntfs_attr_pread(na, pos, bk_cnt * bk_size, dst);
if (br <= 0)
return br;
br /= bk_size;
warn = !na->ni || !na->ni->vol || !NVolNoFixupWarn(na->ni->vol);
for (end = (u8*)dst + br * bk_size; (u8*)dst < end; dst = (u8*)dst +
bk_size)
ntfs_mst_post_read_fixup_warn((NTFS_RECORD*)dst, bk_size, warn);
return br;
}
s64 ntfs_attr_mst_pwrite(ntfs_attr *na, const s64 pos, s64 bk_cnt,
const u32 bk_size, void *src)
{
s64 written, i;
ntfs_log_trace("Entering for inode 0x%llx, attr type 0x%x, pos 0x%llx.\n",
(unsigned long long)na->ni->mft_no, le32_to_cpu(na->type),
(long long)pos);
if (bk_cnt < 0 || bk_size % NTFS_BLOCK_SIZE) {
errno = EINVAL;
return -1;
}
if (!bk_cnt)
return 0;
for (i = 0; i < bk_cnt; ++i) {
int err;
err = ntfs_mst_pre_write_fixup((NTFS_RECORD*)
((u8*)src + i * bk_size), bk_size);
if (err < 0) {
ntfs_log_perror("%s #1", __FUNCTION__);
if (!i)
return err;
bk_cnt = i;
break;
}
}
written = ntfs_attr_pwrite(na, pos, bk_cnt * bk_size, src);
if (written <= 0) {
ntfs_log_perror("%s: written=%lld", __FUNCTION__,
(long long)written);
}
for (i = 0; i < bk_cnt; ++i)
ntfs_mst_post_write_fixup((NTFS_RECORD*)((u8*)src + i *
bk_size));
if (written <= 0)
return written;
return written / bk_size;
}
static int ntfs_attr_find(const ATTR_TYPES type, const ntfschar *name,
const u32 name_len, const IGNORE_CASE_BOOL ic,
const u8 *val, const u32 val_len, ntfs_attr_search_ctx *ctx)
{
ATTR_RECORD *a;
ntfs_volume *vol;
ntfschar *upcase;
ptrdiff_t offs;
ptrdiff_t space;
u32 upcase_len;
ntfs_log_trace("attribute type 0x%x.\n", le32_to_cpu(type));
if (ctx->ntfs_ino) {
vol = ctx->ntfs_ino->vol;
upcase = vol->upcase;
upcase_len = vol->upcase_len;
} else {
if (name && name != AT_UNNAMED) {
errno = EINVAL;
ntfs_log_perror("%s", __FUNCTION__);
return -1;
}
vol = NULL;
upcase = NULL;
upcase_len = 0;
}
if (ctx->is_first) {
a = ctx->attr;
ctx->is_first = FALSE;
} else
a = (ATTR_RECORD*)((char*)ctx->attr +
le32_to_cpu(ctx->attr->length));
for (;; a = (ATTR_RECORD*)((char*)a + le32_to_cpu(a->length))) {
offs = p2n(a) - p2n(ctx->mrec);
space = le32_to_cpu(ctx->mrec->bytes_in_use) - offs;
if ((offs < 0)
|| (((space < (ptrdiff_t)offsetof(ATTR_RECORD,
resident_end))
|| (space < (ptrdiff_t)le32_to_cpu(a->length)))
&& ((space < 4) || (a->type != AT_END))))
break;
ctx->attr = a;
if (((type != AT_UNUSED) && (le32_to_cpu(a->type) >
le32_to_cpu(type))) ||
(a->type == AT_END)) {
errno = ENOENT;
return -1;
}
if (!a->length)
break;
if (type == AT_UNUSED)
return 0;
if (a->type != type)
continue;
if (name == AT_UNNAMED) {
if (a->name_length) {
errno = ENOENT;
return -1;
}
} else {
register int rc;
if (a->name_length
&& ((le16_to_cpu(a->name_offset)
+ a->name_length * sizeof(ntfschar))
> le32_to_cpu(a->length))) {
ntfs_log_error("Corrupt attribute name"
" in MFT record %lld\n",
(long long)ctx->ntfs_ino->mft_no);
break;
}
if (name && ((rc = ntfs_names_full_collate(name,
name_len, (ntfschar*)((char*)a +
le16_to_cpu(a->name_offset)),
a->name_length, ic,
upcase, upcase_len)))) {
if (rc < 0) {
errno = ENOENT;
return -1;
}
continue;
}
}
if (!val)
return 0;
else {
register int rc;
rc = memcmp(val, (char*)a +le16_to_cpu(a->value_offset),
min(val_len,
le32_to_cpu(a->value_length)));
if (!rc) {
register u32 avl;
avl = le32_to_cpu(a->value_length);
if (val_len == avl)
return 0;
if (val_len < avl) {
errno = ENOENT;
return -1;
}
} else if (rc < 0) {
errno = ENOENT;
return -1;
}
}
}
errno = EIO;
ntfs_log_perror("%s: Corrupt inode (%lld)", __FUNCTION__,
ctx->ntfs_ino ? (long long)ctx->ntfs_ino->mft_no : -1);
return -1;
}
void ntfs_attr_name_free(char **name)
{
if (*name) {
free(*name);
*name = NULL;
}
}
char *ntfs_attr_name_get(const ntfschar *uname, const int uname_len)
{
char *name = NULL;
int name_len;
name_len = ntfs_ucstombs(uname, uname_len, &name, 0);
if (name_len < 0) {
ntfs_log_perror("ntfs_ucstombs");
return NULL;
} else if (name_len > 0)
return name;
ntfs_attr_name_free(&name);
return NULL;
}
static int ntfs_external_attr_find(ATTR_TYPES type, const ntfschar *name,
const u32 name_len, const IGNORE_CASE_BOOL ic,
const VCN lowest_vcn, const u8 *val, const u32 val_len,
ntfs_attr_search_ctx *ctx)
{
ntfs_inode *base_ni, *ni;
ntfs_volume *vol;
ATTR_LIST_ENTRY *al_entry, *next_al_entry;
u8 *al_start, *al_end;
ATTR_RECORD *a;
ntfschar *al_name;
ptrdiff_t offs;
ptrdiff_t space;
u32 al_name_len;
BOOL is_first_search = FALSE;
ni = ctx->ntfs_ino;
base_ni = ctx->base_ntfs_ino;
ntfs_log_trace("Entering for inode %lld, attribute type 0x%x.\n",
(unsigned long long)ni->mft_no, le32_to_cpu(type));
if (!base_ni) {
base_ni = ctx->base_ntfs_ino = ctx->ntfs_ino;
ctx->base_mrec = ctx->mrec;
}
if (ni == base_ni)
ctx->base_attr = ctx->attr;
if (type == AT_END)
goto not_found;
vol = base_ni->vol;
al_start = base_ni->attr_list;
al_end = al_start + base_ni->attr_list_size;
if (!ctx->al_entry) {
ctx->al_entry = (ATTR_LIST_ENTRY*)al_start;
is_first_search = TRUE;
}
if (ctx->is_first) {
al_entry = ctx->al_entry;
ctx->is_first = FALSE;
if ((type == AT_UNUSED) && is_first_search &&
le32_to_cpu(al_entry->type) >
le32_to_cpu(AT_ATTRIBUTE_LIST))
goto find_attr_list_attr;
} else {
if (((p2n(al_end) - p2n(ctx->al_entry))
< (long)offsetof(ATTR_LIST_ENTRY, name))
|| (le16_to_cpu(ctx->al_entry->length) & 7)
|| (le16_to_cpu(ctx->al_entry->length)
< offsetof(ATTR_LIST_ENTRY, name)))
goto corrupt;
al_entry = (ATTR_LIST_ENTRY*)((char*)ctx->al_entry +
le16_to_cpu(ctx->al_entry->length));
if ((u8*)al_entry == al_end)
goto not_found;
if ((p2n(al_end) - p2n(al_entry))
< (long)offsetof(ATTR_LIST_ENTRY, name))
goto corrupt;
if ((type == AT_UNUSED) && le32_to_cpu(ctx->al_entry->type) <
le32_to_cpu(AT_ATTRIBUTE_LIST) &&
le32_to_cpu(al_entry->type) >
le32_to_cpu(AT_ATTRIBUTE_LIST)) {
int rc;
find_attr_list_attr:
if (name || name_len || val || val_len || lowest_vcn) {
errno = EINVAL;
ntfs_log_perror("%s", __FUNCTION__);
return -1;
}
ctx->ntfs_ino = base_ni;
ctx->mrec = ctx->base_mrec;
ctx->is_first = TRUE;
ctx->attr = (ATTR_RECORD*)((u8*)ctx->mrec +
le16_to_cpu(ctx->mrec->attrs_offset));
rc = ntfs_attr_find(AT_ATTRIBUTE_LIST, NULL, 0,
IGNORE_CASE, NULL, 0, ctx);
ctx->al_entry = al_entry;
ctx->is_first = TRUE;
if (!rc)
return 0;
if (errno != ENOENT)
return rc;
errno = EIO;
ntfs_log_error("Attribute list wasn't found");
return -1;
}
}
for (;; al_entry = next_al_entry) {
if ((u8*)al_entry < base_ni->attr_list ||
(u8*)al_entry > al_end)
break;
ctx->al_entry = al_entry;
if ((u8*)al_entry == al_end)
goto not_found;
if ((((u8*)al_entry + offsetof(ATTR_LIST_ENTRY, name)) > al_end)
|| ((u8*)al_entry + le16_to_cpu(al_entry->length) > al_end)
|| (le16_to_cpu(al_entry->length) & 7)
|| (le16_to_cpu(al_entry->length)
< offsetof(ATTR_LIST_ENTRY, name_length))
|| (al_entry->name_length
&& ((u8*)al_entry + al_entry->name_offset
+ al_entry->name_length * sizeof(ntfschar))
> al_end))
break;
next_al_entry = (ATTR_LIST_ENTRY*)((u8*)al_entry +
le16_to_cpu(al_entry->length));
if (type != AT_UNUSED) {
if (le32_to_cpu(al_entry->type) > le32_to_cpu(type))
goto not_found;
if (type != al_entry->type)
continue;
}
al_name_len = al_entry->name_length;
al_name = (ntfschar*)((u8*)al_entry + al_entry->name_offset);
if (type == AT_UNUSED)
goto is_enumeration;
if (name == AT_UNNAMED) {
if (al_name_len)
goto not_found;
} else {
int rc;
if (name && ((rc = ntfs_names_full_collate(name,
name_len, al_name, al_name_len, ic,
vol->upcase, vol->upcase_len)))) {
if (rc < 0)
goto not_found;
continue;
}
}
if (lowest_vcn && (u8*)next_al_entry >= al_start &&
(u8*)next_al_entry + 6 < al_end &&
(u8*)next_al_entry + le16_to_cpu(
next_al_entry->length) <= al_end &&
sle64_to_cpu(next_al_entry->lowest_vcn) <=
lowest_vcn &&
next_al_entry->type == al_entry->type &&
next_al_entry->name_length == al_name_len &&
ntfs_names_are_equal((ntfschar*)((char*)
next_al_entry +
next_al_entry->name_offset),
next_al_entry->name_length,
al_name, al_name_len, CASE_SENSITIVE,
vol->upcase, vol->upcase_len))
continue;
is_enumeration:
if (MREF_LE(al_entry->mft_reference) == ni->mft_no) {
if (MSEQNO_LE(al_entry->mft_reference) !=
le16_to_cpu(
ni->mrec->sequence_number)) {
ntfs_log_error("Found stale mft reference in "
"attribute list!\n");
break;
}
} else {
if (MREF_LE(al_entry->mft_reference) ==
base_ni->mft_no) {
ni = ctx->ntfs_ino = base_ni;
ctx->mrec = ctx->base_mrec;
} else {
if (!vol->mft_na) {
ntfs_log_perror("$MFT not ready for "
"opening an extent to inode %lld\n",
(long long)base_ni->mft_no);
break;
}
ni = ntfs_extent_inode_open(base_ni,
al_entry->mft_reference);
if (!ni)
break;
ctx->ntfs_ino = ni;
ctx->mrec = ni->mrec;
}
}
a = ctx->attr = (ATTR_RECORD*)((char*)ctx->mrec +
le16_to_cpu(ctx->mrec->attrs_offset));
do_next_attr_loop:
offs = p2n(a) - p2n(ctx->mrec);
space = le32_to_cpu(ctx->mrec->bytes_in_use) - offs;
if (offs < 0)
break;
if ((space >= 4) && (a->type == AT_END))
continue;
if ((space < (ptrdiff_t)offsetof(ATTR_RECORD, resident_end))
|| (space < (ptrdiff_t)le32_to_cpu(a->length)))
break;
if (al_entry->instance != a->instance)
goto do_next_attr;
if (al_entry->type != a->type)
break;
if (!ntfs_names_are_equal((ntfschar*)((char*)a +
le16_to_cpu(a->name_offset)),
a->name_length, al_name,
al_name_len, CASE_SENSITIVE,
vol->upcase, vol->upcase_len))
break;
ctx->attr = a;
if ((type == AT_UNUSED) || !val || (!a->non_resident &&
le32_to_cpu(a->value_length) == val_len &&
!memcmp((char*)a + le16_to_cpu(a->value_offset),
val, val_len))) {
return 0;
}
do_next_attr:
a = (ATTR_RECORD*)((char*)a + le32_to_cpu(a->length));
goto do_next_attr_loop;
}
corrupt :
if (ni != base_ni) {
ctx->ntfs_ino = base_ni;
ctx->mrec = ctx->base_mrec;
ctx->attr = ctx->base_attr;
}
errno = EIO;
ntfs_log_error("Corrupt attribute list entry in MFT record %lld\n",
(long long)base_ni->mft_no);
return -1;
not_found:
if (type == AT_UNUSED || type == AT_END) {
ntfs_attr_reinit_search_ctx(ctx);
return ntfs_attr_find(AT_END, name, name_len, ic, val, val_len,
ctx);
}
ctx->mrec = ctx->base_mrec;
ctx->attr = (ATTR_RECORD*)((u8*)ctx->mrec +
le16_to_cpu(ctx->mrec->attrs_offset));
ctx->is_first = TRUE;
ctx->ntfs_ino = ctx->base_ntfs_ino;
ctx->base_ntfs_ino = NULL;
ctx->base_mrec = NULL;
ctx->base_attr = NULL;
{
int ret;
do {
ret = ntfs_attr_find(type, name, name_len, ic, val,
val_len, ctx);
} while (!ret);
return ret;
}
}
int ntfs_attr_inconsistent(const ATTR_RECORD *a, const MFT_REF mref)
{
const FILE_NAME_ATTR *fn;
const INDEX_ROOT *ir;
u64 inum;
int ret;
ret = 0;
inum = MREF(mref);
if (a->non_resident) {
if ((a->non_resident != 1)
|| (le32_to_cpu(a->length)
< offsetof(ATTR_RECORD, non_resident_end))
|| (le16_to_cpu(a->mapping_pairs_offset)
>= le32_to_cpu(a->length))
|| (a->name_length
&& (((u32)le16_to_cpu(a->name_offset)
+ a->name_length * sizeof(ntfschar))
> le32_to_cpu(a->length)))
|| (le64_to_cpu(a->highest_vcn)
< le64_to_cpu(a->lowest_vcn))) {
ntfs_log_error("Corrupt non resident attribute"
" 0x%x in MFT record %lld\n",
(int)le32_to_cpu(a->type),
(long long)inum);
errno = EIO;
ret = -1;
}
} else {
if ((le32_to_cpu(a->length)
< offsetof(ATTR_RECORD, resident_end))
|| (le32_to_cpu(a->value_length) & 0xff000000)
|| (a->value_length
&& ((le16_to_cpu(a->value_offset)
+ le32_to_cpu(a->value_length))
> le32_to_cpu(a->length)))
|| (a->name_length
&& (((u32)le16_to_cpu(a->name_offset)
+ a->name_length * sizeof(ntfschar))
> le32_to_cpu(a->length)))) {
ntfs_log_error("Corrupt resident attribute"
" 0x%x in MFT record %lld\n",
(int)le32_to_cpu(a->type),
(long long)inum);
errno = EIO;
ret = -1;
}
}
if (!ret) {
switch(a->type) {
case AT_FILE_NAME :
fn = (const FILE_NAME_ATTR*)((const u8*)a
+ le16_to_cpu(a->value_offset));
if (a->non_resident
|| (le32_to_cpu(a->value_length)
< offsetof(FILE_NAME_ATTR, file_name))
|| !fn->file_name_length
|| ((fn->file_name_length * sizeof(ntfschar)
+ offsetof(FILE_NAME_ATTR, file_name))
> le32_to_cpu(a->value_length))) {
ntfs_log_error("Corrupt file name"
" attribute in MFT record %lld.\n",
(long long)inum);
errno = EIO;
ret = -1;
}
break;
case AT_INDEX_ROOT :
ir = (const INDEX_ROOT*)((const u8*)a +
le16_to_cpu(a->value_offset));
if (a->non_resident
|| (le32_to_cpu(a->value_length)
< offsetof(INDEX_ROOT, index.reserved))
|| (le32_to_cpu(ir->index.entries_offset)
< sizeof(INDEX_HEADER))
|| (le32_to_cpu(ir->index.index_length)
< le32_to_cpu(ir->index.entries_offset))
|| (le32_to_cpu(ir->index.allocated_size)
< le32_to_cpu(ir->index.index_length))
|| (le32_to_cpu(a->value_length)
< (le32_to_cpu(ir->index.allocated_size)
+ offsetof(INDEX_ROOT, reserved)))) {
ntfs_log_error("Corrupt index root"
" in MFT record %lld.\n",
(long long)inum);
errno = EIO;
ret = -1;
}
break;
case AT_STANDARD_INFORMATION :
if (a->non_resident
|| (le32_to_cpu(a->value_length)
< offsetof(STANDARD_INFORMATION,
v1_end))) {
ntfs_log_error("Corrupt standard information"
" in MFT record %lld\n",
(long long)inum);
errno = EIO;
ret = -1;
}
break;
case AT_OBJECT_ID :
if (a->non_resident
|| (le32_to_cpu(a->value_length)
< sizeof(GUID))) {
ntfs_log_error("Corrupt object id"
" in MFT record %lld\n",
(long long)inum);
errno = EIO;
ret = -1;
}
break;
case AT_VOLUME_NAME :
case AT_EA_INFORMATION :
if (a->non_resident) {
ntfs_log_error("Attribute 0x%x in MFT record"
" %lld should be resident.\n",
(int)le32_to_cpu(a->type),
(long long)inum);
errno = EIO;
ret = -1;
}
break;
case AT_VOLUME_INFORMATION :
if (a->non_resident
|| (le32_to_cpu(a->value_length)
< sizeof(VOLUME_INFORMATION))) {
ntfs_log_error("Corrupt volume information"
" in MFT record %lld\n",
(long long)inum);
errno = EIO;
ret = -1;
}
break;
case AT_INDEX_ALLOCATION :
if (!a->non_resident) {
ntfs_log_error("Corrupt index allocation"
" in MFT record %lld",
(long long)inum);
errno = EIO;
ret = -1;
}
break;
default :
break;
}
}
return (ret);
}
int ntfs_attr_lookup(const ATTR_TYPES type, const ntfschar *name,
const u32 name_len, const IGNORE_CASE_BOOL ic,
const VCN lowest_vcn, const u8 *val, const u32 val_len,
ntfs_attr_search_ctx *ctx)
{
ntfs_volume *vol;
ntfs_inode *base_ni;
int ret = -1;
ntfs_log_enter("Entering for attribute type 0x%x\n", le32_to_cpu(type));
if (!ctx || !ctx->mrec || !ctx->attr || (name && name != AT_UNNAMED &&
(!ctx->ntfs_ino || !(vol = ctx->ntfs_ino->vol) ||
!vol->upcase || !vol->upcase_len))) {
errno = EINVAL;
ntfs_log_perror("%s", __FUNCTION__);
goto out;
}
if (ctx->base_ntfs_ino)
base_ni = ctx->base_ntfs_ino;
else
base_ni = ctx->ntfs_ino;
if (!base_ni || !NInoAttrList(base_ni) || type == AT_ATTRIBUTE_LIST)
ret = ntfs_attr_find(type, name, name_len, ic, val, val_len, ctx);
else
ret = ntfs_external_attr_find(type, name, name_len, ic,
lowest_vcn, val, val_len, ctx);
out:
ntfs_log_leave("\n");
return ret;
}
int ntfs_attr_position(const ATTR_TYPES type, ntfs_attr_search_ctx *ctx)
{
if (ntfs_attr_lookup(type, NULL, 0, CASE_SENSITIVE, 0, NULL, 0, ctx)) {
if (errno != ENOENT)
return -1;
if (ctx->attr->type == AT_END) {
errno = ENOSPC;
return -1;
}
}
return 0;
}
static void ntfs_attr_init_search_ctx(ntfs_attr_search_ctx *ctx,
ntfs_inode *ni, MFT_RECORD *mrec)
{
if (!mrec)
mrec = ni->mrec;
ctx->mrec = mrec;
ctx->attr = (ATTR_RECORD*)((u8*)mrec + le16_to_cpu(mrec->attrs_offset));
ctx->is_first = TRUE;
ctx->ntfs_ino = ni;
ctx->al_entry = NULL;
ctx->base_ntfs_ino = NULL;
ctx->base_mrec = NULL;
ctx->base_attr = NULL;
}
void ntfs_attr_reinit_search_ctx(ntfs_attr_search_ctx *ctx)
{
if (!ctx->base_ntfs_ino) {
ctx->is_first = TRUE;
ctx->attr = (ATTR_RECORD*)((u8*)ctx->mrec +
le16_to_cpu(ctx->mrec->attrs_offset));
ctx->al_entry = NULL;
return;
}
ntfs_attr_init_search_ctx(ctx, ctx->base_ntfs_ino, ctx->base_mrec);
return;
}
ntfs_attr_search_ctx *ntfs_attr_get_search_ctx(ntfs_inode *ni, MFT_RECORD *mrec)
{
ntfs_attr_search_ctx *ctx;
if (!ni && !mrec) {
errno = EINVAL;
ntfs_log_perror("NULL arguments");
return NULL;
}
ctx = ntfs_malloc(sizeof(ntfs_attr_search_ctx));
if (ctx)
ntfs_attr_init_search_ctx(ctx, ni, mrec);
return ctx;
}
void ntfs_attr_put_search_ctx(ntfs_attr_search_ctx *ctx)
{
free(ctx);
}
ATTR_DEF *ntfs_attr_find_in_attrdef(const ntfs_volume *vol,
const ATTR_TYPES type)
{
ATTR_DEF *ad;
if (!vol || !vol->attrdef || !type) {
errno = EINVAL;
ntfs_log_perror("%s: type=%d", __FUNCTION__, le32_to_cpu(type));
return NULL;
}
for (ad = vol->attrdef; ((ptrdiff_t)((u8*)ad - (u8*)vol->attrdef
+ sizeof(ATTR_DEF)) <= vol->attrdef_len)
&& ad->type; ++ad) {
if (le32_to_cpu(ad->type) < le32_to_cpu(type))
continue;
if (ad->type == type)
return ad;
break;
}
errno = ENOENT;
ntfs_log_perror("%s: type=%d", __FUNCTION__, le32_to_cpu(type));
return NULL;
}
int ntfs_attr_size_bounds_check(const ntfs_volume *vol, const ATTR_TYPES type,
const s64 size)
{
ATTR_DEF *ad;
s64 min_size, max_size;
if (size < 0) {
errno = EINVAL;
ntfs_log_perror("%s: size=%lld", __FUNCTION__,
(long long)size);
return -1;
}
if (type == AT_ATTRIBUTE_LIST && size > 0x40000) {
errno = ERANGE;
ntfs_log_perror("Too large attrlist (%lld)", (long long)size);
return -1;
}
ad = ntfs_attr_find_in_attrdef(vol, type);
if (!ad)
return -1;
min_size = sle64_to_cpu(ad->min_size);
max_size = sle64_to_cpu(ad->max_size);
if(type == AT_VOLUME_NAME)
min_size = 0;
if ((min_size && (size < min_size)) ||
((max_size > 0) && (size > max_size))) {
errno = ERANGE;
ntfs_log_perror("Attr type %d size check failed (min,size,max="
"%lld,%lld,%lld)", le32_to_cpu(type), (long long)min_size,
(long long)size, (long long)max_size);
return -1;
}
return 0;
}
static int ntfs_attr_can_be_non_resident(const ntfs_volume *vol, const ATTR_TYPES type,
const ntfschar *name, int name_len)
{
ATTR_DEF *ad;
BOOL allowed;
if ((type == AT_LOGGED_UTILITY_STREAM)
&& name
&& ntfs_names_are_equal(TXF_DATA, 9, name, name_len,
CASE_SENSITIVE, vol->upcase, vol->upcase_len))
allowed = FALSE;
else {
ad = ntfs_attr_find_in_attrdef(vol, type);
if (!ad)
return -1;
allowed = !(ad->flags & ATTR_DEF_RESIDENT);
}
if (!allowed) {
errno = EPERM;
ntfs_log_trace("Attribute can't be non-resident\n");
return -1;
}
return 0;
}
int ntfs_attr_can_be_resident(const ntfs_volume *vol, const ATTR_TYPES type)
{
if (!vol || !vol->attrdef || !type) {
errno = EINVAL;
return -1;
}
if (type != AT_INDEX_ALLOCATION)
return 0;
ntfs_log_trace("Attribute can't be resident\n");
errno = EPERM;
return -1;
}
int ntfs_make_room_for_attr(MFT_RECORD *m, u8 *pos, u32 size)
{
u32 biu;
ntfs_log_trace("Entering for pos 0x%d, size %u.\n",
(int)(pos - (u8*)m), (unsigned) size);
size = (size + 7) & ~7;
if (!m || !pos || pos < (u8*)m) {
errno = EINVAL;
ntfs_log_perror("%s: pos=%p m=%p", __FUNCTION__, pos, m);
return -1;
}
if (pos - (u8*)m > (int)le32_to_cpu(m->bytes_in_use) - 8) {
errno = EINVAL;
return -1;
}
if (!size)
return 0;
biu = le32_to_cpu(m->bytes_in_use);
if (biu + size > le32_to_cpu(m->bytes_allocated) ||
pos + size > (u8*)m + le32_to_cpu(m->bytes_allocated)) {
errno = ENOSPC;
ntfs_log_trace("No enough space in the MFT record\n");
return -1;
}
memmove(pos + size, pos, biu - (pos - (u8*)m));
m->bytes_in_use = cpu_to_le32(biu + size);
return 0;
}
int ntfs_resident_attr_record_add(ntfs_inode *ni, ATTR_TYPES type,
const ntfschar *name, u8 name_len, const u8 *val,
u32 size, ATTR_FLAGS data_flags)
{
ntfs_attr_search_ctx *ctx;
u32 length;
ATTR_RECORD *a;
MFT_RECORD *m;
int err, offset;
ntfs_inode *base_ni;
ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x, flags 0x%x.\n",
(long long) ni->mft_no, (unsigned) le32_to_cpu(type), (unsigned) le16_to_cpu(data_flags));
if (!ni || (!name && name_len)) {
errno = EINVAL;
return -1;
}
if (ntfs_attr_can_be_resident(ni->vol, type)) {
if (errno == EPERM)
ntfs_log_trace("Attribute can't be resident.\n");
else
ntfs_log_trace("ntfs_attr_can_be_resident failed.\n");
return -1;
}
ctx = ntfs_attr_get_search_ctx(ni, NULL);
if (!ctx)
return -1;
if (!ntfs_attr_find(type, name, name_len, CASE_SENSITIVE, val, size,
ctx)) {
err = EEXIST;
ntfs_log_trace("Attribute already present.\n");
goto put_err_out;
}
if (errno != ENOENT) {
err = EIO;
goto put_err_out;
}
a = ctx->attr;
m = ctx->mrec;
length = offsetof(ATTR_RECORD, resident_end) +
((name_len * sizeof(ntfschar) + 7) & ~7) +
((size + 7) & ~7);
if (ntfs_make_room_for_attr(ctx->mrec, (u8*) ctx->attr, length)) {
err = errno;
ntfs_log_trace("Failed to make room for attribute.\n");
goto put_err_out;
}
offset = ((u8*)a - (u8*)m);
a->type = type;
a->length = cpu_to_le32(length);
a->non_resident = 0;
a->name_length = name_len;
a->name_offset = (name_len
? const_cpu_to_le16(offsetof(ATTR_RECORD, resident_end))
: const_cpu_to_le16(0));
a->flags = data_flags;
a->instance = m->next_attr_instance;
a->value_length = cpu_to_le32(size);
a->value_offset = cpu_to_le16(length - ((size + 7) & ~7));
if (val)
memcpy((u8*)a + le16_to_cpu(a->value_offset), val, size);
else
memset((u8*)a + le16_to_cpu(a->value_offset), 0, size);
if (type == AT_FILE_NAME)
a->resident_flags = RESIDENT_ATTR_IS_INDEXED;
else
a->resident_flags = 0;
if (name_len)
memcpy((u8*)a + le16_to_cpu(a->name_offset),
name, sizeof(ntfschar) * name_len);
m->next_attr_instance =
cpu_to_le16((le16_to_cpu(m->next_attr_instance) + 1) & 0xffff);
if (ni->nr_extents == -1)
base_ni = ni->base_ni;
else
base_ni = ni;
if (type != AT_ATTRIBUTE_LIST && NInoAttrList(base_ni)) {
if (ntfs_attrlist_entry_add(ni, a)) {
err = errno;
ntfs_attr_record_resize(m, a, 0);
ntfs_log_trace("Failed add attribute entry to "
"ATTRIBUTE_LIST.\n");
goto put_err_out;
}
}
if (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY
? type == AT_INDEX_ROOT && name == NTFS_INDEX_I30
: type == AT_DATA && name == AT_UNNAMED) {
ni->data_size = size;
ni->allocated_size = (size + 7) & ~7;
set_nino_flag(ni,KnownSize);
}
ntfs_inode_mark_dirty(ni);
ntfs_attr_put_search_ctx(ctx);
return offset;
put_err_out:
ntfs_attr_put_search_ctx(ctx);
errno = err;
return -1;
}
int ntfs_non_resident_attr_record_add(ntfs_inode *ni, ATTR_TYPES type,
const ntfschar *name, u8 name_len, VCN lowest_vcn, int dataruns_size,
ATTR_FLAGS flags)
{
ntfs_attr_search_ctx *ctx;
u32 length;
ATTR_RECORD *a;
MFT_RECORD *m;
ntfs_inode *base_ni;
int err, offset;
ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x, lowest_vcn %lld, "
"dataruns_size %d, flags 0x%x.\n",
(long long) ni->mft_no, (unsigned) le32_to_cpu(type),
(long long) lowest_vcn, dataruns_size, (unsigned) le16_to_cpu(flags));
if (!ni || dataruns_size <= 0 || (!name && name_len)) {
errno = EINVAL;
return -1;
}
if (ntfs_attr_can_be_non_resident(ni->vol, type, name, name_len)) {
if (errno == EPERM)
ntfs_log_perror("Attribute can't be non resident");
else
ntfs_log_perror("ntfs_attr_can_be_non_resident failed");
return -1;
}
ctx = ntfs_attr_get_search_ctx(ni, NULL);
if (!ctx)
return -1;
if (!ntfs_attr_find(type, name, name_len, CASE_SENSITIVE, NULL, 0,
ctx)) {
err = EEXIST;
ntfs_log_perror("Attribute 0x%x already present", le32_to_cpu(type));
goto put_err_out;
}
if (errno != ENOENT) {
ntfs_log_perror("ntfs_attr_find failed");
err = EIO;
goto put_err_out;
}
a = ctx->attr;
m = ctx->mrec;
dataruns_size = (dataruns_size + 7) & ~7;
length = offsetof(ATTR_RECORD, compressed_size) + ((sizeof(ntfschar) *
name_len + 7) & ~7) + dataruns_size +
((flags & (ATTR_IS_COMPRESSED | ATTR_IS_SPARSE)) ?
sizeof(a->compressed_size) : 0);
if (ntfs_make_room_for_attr(ctx->mrec, (u8*) ctx->attr, length)) {
err = errno;
ntfs_log_perror("Failed to make room for attribute");
goto put_err_out;
}
a->type = type;
a->length = cpu_to_le32(length);
a->non_resident = 1;
a->name_length = name_len;
a->name_offset = cpu_to_le16(offsetof(ATTR_RECORD, compressed_size) +
((flags & (ATTR_IS_COMPRESSED | ATTR_IS_SPARSE)) ?
sizeof(a->compressed_size) : 0));
a->flags = flags;
a->instance = m->next_attr_instance;
a->lowest_vcn = cpu_to_sle64(lowest_vcn);
a->mapping_pairs_offset = cpu_to_le16(length - dataruns_size);
a->compression_unit = (flags & ATTR_IS_COMPRESSED)
? STANDARD_COMPRESSION_UNIT : 0;
if (!lowest_vcn) {
a->highest_vcn = const_cpu_to_sle64(-1);
a->allocated_size = const_cpu_to_sle64(0);
a->data_size = const_cpu_to_sle64(0);
a->initialized_size = const_cpu_to_sle64(0);
*((u8*)a + le16_to_cpu(a->mapping_pairs_offset)) = 0;
}
if (name_len)
memcpy((u8*)a + le16_to_cpu(a->name_offset),
name, sizeof(ntfschar) * name_len);
m->next_attr_instance =
cpu_to_le16((le16_to_cpu(m->next_attr_instance) + 1) & 0xffff);
if (ni->nr_extents == -1)
base_ni = ni->base_ni;
else
base_ni = ni;
if (type != AT_ATTRIBUTE_LIST && NInoAttrList(base_ni)) {
if (ntfs_attrlist_entry_add(ni, a)) {
err = errno;
ntfs_log_perror("Failed add attr entry to attrlist");
ntfs_attr_record_resize(m, a, 0);
goto put_err_out;
}
}
ntfs_inode_mark_dirty(ni);
ntfs_attr_reinit_search_ctx(ctx);
if (ntfs_attr_lookup(type, name, name_len, CASE_SENSITIVE,
lowest_vcn, NULL, 0, ctx)) {
ntfs_log_perror("%s: attribute lookup failed", __FUNCTION__);
ntfs_attr_put_search_ctx(ctx);
return -1;
}
offset = (u8*)ctx->attr - (u8*)ctx->mrec;
ntfs_attr_put_search_ctx(ctx);
return offset;
put_err_out:
ntfs_attr_put_search_ctx(ctx);
errno = err;
return -1;
}
int ntfs_attr_record_rm(ntfs_attr_search_ctx *ctx)
{
ntfs_inode *base_ni, *ni;
ATTR_TYPES type;
if (!ctx || !ctx->ntfs_ino || !ctx->mrec || !ctx->attr) {
errno = EINVAL;
return -1;
}
ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x.\n",
(long long) ctx->ntfs_ino->mft_no,
(unsigned) le32_to_cpu(ctx->attr->type));
type = ctx->attr->type;
ni = ctx->ntfs_ino;
if (ctx->base_ntfs_ino)
base_ni = ctx->base_ntfs_ino;
else
base_ni = ctx->ntfs_ino;
if (ntfs_attr_record_resize(ctx->mrec, ctx->attr, 0)) {
ntfs_log_trace("Couldn't remove attribute record. Bug or damaged MFT "
"record.\n");
if (NInoAttrList(base_ni) && type != AT_ATTRIBUTE_LIST)
if (ntfs_attrlist_entry_add(ni, ctx->attr))
ntfs_log_trace("Rollback failed. Leaving inconstant "
"metadata.\n");
errno = EIO;
return -1;
}
ntfs_inode_mark_dirty(ni);
if (NInoAttrList(base_ni) && type != AT_ATTRIBUTE_LIST) {
if (ntfs_attrlist_entry_rm(ctx)) {
ntfs_log_trace("Couldn't delete record from "
"$ATTRIBUTE_LIST.\n");
return -1;
}
}
if (type == AT_ATTRIBUTE_LIST) {
if (NInoAttrList(base_ni) && base_ni->attr_list)
free(base_ni->attr_list);
base_ni->attr_list = NULL;
NInoClearAttrList(base_ni);
NInoAttrListClearDirty(base_ni);
}
if (le32_to_cpu(ctx->mrec->bytes_in_use) -
le16_to_cpu(ctx->mrec->attrs_offset) == 8) {
if (ntfs_mft_record_free(ni->vol, ni)) {
ntfs_log_trace("Couldn't free MFT record.\n");
errno = EIO;
return -1;
}
if (ni == base_ni)
return 0;
}
if (type == AT_ATTRIBUTE_LIST || !NInoAttrList(base_ni))
return 0;
if (!ntfs_attrlist_need(base_ni)) {
ntfs_attr_reinit_search_ctx(ctx);
if (ntfs_attr_lookup(AT_ATTRIBUTE_LIST, NULL, 0, CASE_SENSITIVE,
0, NULL, 0, ctx)) {
ntfs_log_trace("Couldn't find attribute list. Succeed "
"anyway.\n");
return 0;
}
if (ctx->attr->non_resident) {
runlist *al_rl;
al_rl = ntfs_mapping_pairs_decompress(base_ni->vol,
ctx->attr, NULL);
if (!al_rl) {
ntfs_log_trace("Couldn't decompress attribute list "
"runlist. Succeed anyway.\n");
return 0;
}
if (ntfs_cluster_free_from_rl(base_ni->vol, al_rl)) {
ntfs_log_trace("Leaking clusters! Run chkdsk. "
"Couldn't free clusters from "
"attribute list runlist.\n");
}
free(al_rl);
}
if (ntfs_attr_record_rm(ctx)) {
ntfs_log_trace("Couldn't remove attribute list. Succeed "
"anyway.\n");
return 0;
}
}
return 0;
}
int ntfs_attr_add(ntfs_inode *ni, ATTR_TYPES type,
ntfschar *name, u8 name_len, const u8 *val, s64 size)
{
u32 attr_rec_size;
int err, i, offset;
BOOL is_resident;
BOOL can_be_non_resident = FALSE;
ntfs_inode *attr_ni;
ntfs_attr *na;
ATTR_FLAGS data_flags;
if (!ni || size < 0 || type == AT_ATTRIBUTE_LIST) {
errno = EINVAL;
ntfs_log_perror("%s: ni=%p size=%lld", __FUNCTION__, ni,
(long long)size);
return -1;
}
ntfs_log_trace("Entering for inode %lld, attr %x, size %lld.\n",
(long long)ni->mft_no, le32_to_cpu(type), (long long)size);
if (ni->nr_extents == -1)
ni = ni->base_ni;
if (ntfs_attr_size_bounds_check(ni->vol, type, size)) {
if (errno == ENOENT)
errno = EIO;
return -1;
}
if (ntfs_attr_can_be_non_resident(ni->vol, type, name, name_len)) {
if (errno != EPERM) {
err = errno;
ntfs_log_perror("ntfs_attr_can_be_non_resident failed");
goto err_out;
}
if (!val) {
errno = EINVAL;
ntfs_log_perror("val is mandatory for always resident "
"attributes");
return -1;
}
if (size > ni->vol->mft_record_size) {
errno = ERANGE;
ntfs_log_perror("Attribute is too big");
return -1;
}
} else
can_be_non_resident = TRUE;
if (!ntfs_attr_can_be_resident(ni->vol, type)) {
is_resident = TRUE;
} else {
if (errno != EPERM) {
err = errno;
ntfs_log_perror("ntfs_attr_can_be_resident failed");
goto err_out;
}
is_resident = FALSE;
}
if (is_resident)
attr_rec_size = offsetof(ATTR_RECORD, resident_end) +
((name_len * sizeof(ntfschar) + 7) & ~7) +
((size + 7) & ~7);
else
attr_rec_size = offsetof(ATTR_RECORD, non_resident_end) +
((name_len * sizeof(ntfschar) + 7) & ~7) + 8;
if (le32_to_cpu(ni->mrec->bytes_allocated) -
le32_to_cpu(ni->mrec->bytes_in_use) >= attr_rec_size) {
attr_ni = ni;
goto add_attr_record;
}
if (ntfs_inode_attach_all_extents(ni)) {
err = errno;
ntfs_log_perror("Failed to attach all extents to inode");
goto err_out;
}
for (i = 0; i < ni->nr_extents; i++) {
attr_ni = ni->extent_nis[i];
if (le32_to_cpu(attr_ni->mrec->bytes_allocated) -
le32_to_cpu(attr_ni->mrec->bytes_in_use) >=
attr_rec_size)
goto add_attr_record;
}
if (!NInoAttrList(ni)) {
if (ntfs_inode_add_attrlist(ni)) {
err = errno;
ntfs_log_perror("Failed to add attribute list");
goto err_out;
}
return ntfs_attr_add(ni, type, name, name_len, val, size);
}
attr_ni = ntfs_mft_record_alloc(ni->vol, ni);
if (!attr_ni) {
err = errno;
ntfs_log_perror("Failed to allocate extent record");
goto err_out;
}
add_attr_record:
if ((ni->flags & FILE_ATTR_COMPRESSED)
&& (ni->vol->major_ver >= 3)
&& NVolCompression(ni->vol)
&& (ni->vol->cluster_size <= MAX_COMPRESSION_CLUSTER_SIZE)
&& ((type == AT_DATA)
|| ((type == AT_INDEX_ROOT) && (name == NTFS_INDEX_I30))))
data_flags = ATTR_IS_COMPRESSED;
else
data_flags = const_cpu_to_le16(0);
if (is_resident) {
offset = ntfs_resident_attr_record_add(attr_ni, type, name,
name_len, val, size, data_flags);
if (offset < 0) {
if (errno == ENOSPC && can_be_non_resident)
goto add_non_resident;
err = errno;
ntfs_log_perror("Failed to add resident attribute");
goto free_err_out;
}
return 0;
}
add_non_resident:
offset = ntfs_non_resident_attr_record_add(attr_ni, type, name,
name_len, 0, 8, data_flags);
if (offset < 0) {
err = errno;
ntfs_log_perror("Failed to add non resident attribute");
goto free_err_out;
}
if (!size)
return 0;
na = ntfs_attr_open(ni, type, name, name_len);
if (!na) {
err = errno;
ntfs_log_perror("Failed to open just added attribute");
goto rm_attr_err_out;
}
if (ntfs_attr_truncate_i(na, size, HOLES_OK) ||
(val && (ntfs_attr_pwrite(na, 0, size, val) != size))) {
err = errno;
ntfs_log_perror("Failed to initialize just added attribute");
if (ntfs_attr_rm(na))
ntfs_log_perror("Failed to remove just added attribute");
ntfs_attr_close(na);
goto err_out;
}
ntfs_attr_close(na);
return 0;
rm_attr_err_out:
if (ntfs_attr_record_resize(attr_ni->mrec,
(ATTR_RECORD*)((u8*)attr_ni->mrec + offset), 0))
ntfs_log_perror("Failed to remove just added attribute #2");
free_err_out:
if (le32_to_cpu(attr_ni->mrec->bytes_in_use) -
le16_to_cpu(attr_ni->mrec->attrs_offset) == 8)
if (ntfs_mft_record_free(attr_ni->vol, attr_ni))
ntfs_log_perror("Failed to free MFT record");
err_out:
errno = err;
return -1;
}
int ntfs_attr_set_flags(ntfs_inode *ni, ATTR_TYPES type, const ntfschar *name,
u8 name_len, ATTR_FLAGS flags, ATTR_FLAGS mask)
{
ntfs_attr_search_ctx *ctx;
int res;
res = -1;
ctx = ntfs_attr_get_search_ctx(ni, NULL);
if (ctx) {
if (!ntfs_attr_lookup(type, name, name_len,
CASE_SENSITIVE, 0, NULL, 0, ctx)) {
ctx->attr->flags = (ctx->attr->flags & ~mask)
| (flags & mask);
NInoSetDirty(ni);
res = 0;
}
ntfs_attr_put_search_ctx(ctx);
}
return (res);
}
int ntfs_attr_rm(ntfs_attr *na)
{
ntfs_attr_search_ctx *ctx;
int ret = 0;
if (!na) {
ntfs_log_trace("Invalid arguments passed.\n");
errno = EINVAL;
return -1;
}
ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x.\n",
(long long) na->ni->mft_no, le32_to_cpu(na->type));
if (NAttrNonResident(na)) {
if (ntfs_attr_map_whole_runlist(na))
return -1;
if (ntfs_cluster_free(na->ni->vol, na, 0, -1) < 0) {
ntfs_log_trace("Failed to free cluster allocation. Leaving "
"inconstant metadata.\n");
ret = -1;
}
}
ctx = ntfs_attr_get_search_ctx(na->ni, NULL);
if (!ctx)
return -1;
while (!ntfs_attr_lookup(na->type, na->name, na->name_len,
CASE_SENSITIVE, 0, NULL, 0, ctx)) {
if (ntfs_attr_record_rm(ctx)) {
ntfs_log_trace("Failed to remove attribute extent. Leaving "
"inconstant metadata.\n");
ret = -1;
}
ntfs_attr_reinit_search_ctx(ctx);
}
ntfs_attr_put_search_ctx(ctx);
if (errno != ENOENT) {
ntfs_log_trace("Attribute lookup failed. Probably leaving inconstant "
"metadata.\n");
ret = -1;
}
return ret;
}
int ntfs_attr_record_resize(MFT_RECORD *m, ATTR_RECORD *a, u32 new_size)
{
u32 old_size, alloc_size, attr_size;
old_size = le32_to_cpu(m->bytes_in_use);
alloc_size = le32_to_cpu(m->bytes_allocated);
attr_size = le32_to_cpu(a->length);
ntfs_log_trace("Sizes: old=%u alloc=%u attr=%u new=%u\n",
(unsigned)old_size, (unsigned)alloc_size,
(unsigned)attr_size, (unsigned)new_size);
new_size = (new_size + 7) & ~7;
if (new_size != attr_size) {
u32 new_muse = old_size - attr_size + new_size;
if (new_muse > alloc_size) {
errno = ENOSPC;
ntfs_log_trace("Not enough space in the MFT record "
"(%u > %u)\n", new_muse, alloc_size);
return -1;
}
if (a->type == AT_INDEX_ROOT && new_size > attr_size &&
new_muse + 120 > alloc_size && old_size + 120 <= alloc_size) {
errno = ENOSPC;
ntfs_log_trace("Too big INDEX_ROOT (%u > %u)\n",
new_muse, alloc_size);
return STATUS_RESIDENT_ATTRIBUTE_FILLED_MFT;
}
if (((u8 *)m + old_size) < ((u8 *)a + attr_size)) {
ntfs_log_error("Attribute 0x%x overflows"
" from MFT record\n",
(int)le32_to_cpu(a->type));
errno = EIO;
return (-1);
}
memmove((u8 *)a + new_size, (u8 *)a + attr_size,
old_size - ((u8 *)a - (u8 *)m) - attr_size);
m->bytes_in_use = cpu_to_le32(new_muse);
if (new_size >= offsetof(ATTR_REC, length) + sizeof(a->length))
a->length = cpu_to_le32(new_size);
}
return 0;
}
int ntfs_resident_attr_value_resize(MFT_RECORD *m, ATTR_RECORD *a,
const u32 new_size)
{
int ret;
ntfs_log_trace("Entering for new size %u.\n", (unsigned)new_size);
if (!a->value_length) {
int offset = ((offsetof(ATTR_RECORD, resident_end)
+ a->name_length*sizeof(ntfschar) - 1) | 7) + 1;
a->value_offset = cpu_to_le16(offset);
}
if ((ret = ntfs_attr_record_resize(m, a, (le16_to_cpu(a->value_offset) +
new_size + 7) & ~7)) < 0)
return ret;
if (new_size > le32_to_cpu(a->value_length))
memset((u8*)a + le16_to_cpu(a->value_offset) +
le32_to_cpu(a->value_length), 0, new_size -
le32_to_cpu(a->value_length));
a->value_length = cpu_to_le32(new_size);
return 0;
}
int ntfs_attr_record_move_to(ntfs_attr_search_ctx *ctx, ntfs_inode *ni)
{
ntfs_attr_search_ctx *nctx;
ATTR_RECORD *a;
int err;
if (!ctx || !ctx->attr || !ctx->ntfs_ino || !ni) {
ntfs_log_trace("Invalid arguments passed.\n");
errno = EINVAL;
return -1;
}
ntfs_log_trace("Entering for ctx->attr->type 0x%x, ctx->ntfs_ino->mft_no "
"0x%llx, ni->mft_no 0x%llx.\n",
(unsigned) le32_to_cpu(ctx->attr->type),
(long long) ctx->ntfs_ino->mft_no,
(long long) ni->mft_no);
if (ctx->ntfs_ino == ni)
return 0;
if (!ctx->al_entry) {
ntfs_log_trace("Inode should contain attribute list to use this "
"function.\n");
errno = EINVAL;
return -1;
}
a = ctx->attr;
nctx = ntfs_attr_get_search_ctx(ni, NULL);
if (!nctx)
return -1;
if (!ntfs_attr_find(a->type, (ntfschar*)((u8*)a + le16_to_cpu(
a->name_offset)), a->name_length, CASE_SENSITIVE, NULL,
0, nctx)) {
ntfs_log_trace("Attribute of such type, with same name already "
"present in this MFT record.\n");
err = EEXIST;
goto put_err_out;
}
if (errno != ENOENT) {
err = errno;
ntfs_log_debug("Attribute lookup failed.\n");
goto put_err_out;
}
if (ntfs_make_room_for_attr(ni->mrec, (u8*) nctx->attr,
le32_to_cpu(a->length))) {
err = errno;
ntfs_log_trace("Couldn't make space for attribute.\n");
goto put_err_out;
}
memcpy(nctx->attr, a, le32_to_cpu(a->length));
nctx->attr->instance = nctx->mrec->next_attr_instance;
nctx->mrec->next_attr_instance = cpu_to_le16(
(le16_to_cpu(nctx->mrec->next_attr_instance) + 1) & 0xffff);
ntfs_attr_record_resize(ctx->mrec, a, 0);
ntfs_inode_mark_dirty(ctx->ntfs_ino);
ntfs_inode_mark_dirty(ni);
ctx->al_entry->mft_reference =
MK_LE_MREF(ni->mft_no, le16_to_cpu(ni->mrec->sequence_number));
ctx->al_entry->instance = nctx->attr->instance;
ntfs_attrlist_mark_dirty(ni);
ntfs_attr_put_search_ctx(nctx);
return 0;
put_err_out:
ntfs_attr_put_search_ctx(nctx);
errno = err;
return -1;
}
int ntfs_attr_record_move_away(ntfs_attr_search_ctx *ctx, int extra)
{
ntfs_inode *base_ni, *ni;
MFT_RECORD *m;
int i;
if (!ctx || !ctx->attr || !ctx->ntfs_ino || extra < 0) {
errno = EINVAL;
ntfs_log_perror("%s: ctx=%p ctx->attr=%p extra=%d", __FUNCTION__,
ctx, ctx ? ctx->attr : NULL, extra);
return -1;
}
ntfs_log_trace("Entering for attr 0x%x, inode %llu\n",
(unsigned) le32_to_cpu(ctx->attr->type),
(unsigned long long)ctx->ntfs_ino->mft_no);
if (ctx->ntfs_ino->nr_extents == -1)
base_ni = ctx->base_ntfs_ino;
else
base_ni = ctx->ntfs_ino;
if (!NInoAttrList(base_ni)) {
errno = EINVAL;
ntfs_log_perror("Inode %llu has no attrlist",
(unsigned long long)base_ni->mft_no);
return -1;
}
if (ntfs_inode_attach_all_extents(ctx->ntfs_ino)) {
ntfs_log_perror("Couldn't attach extents, inode=%llu",
(unsigned long long)base_ni->mft_no);
return -1;
}
for (i = 0; i < base_ni->nr_extents; i++) {
ni = base_ni->extent_nis[i];
m = ni->mrec;
if (ctx->ntfs_ino->mft_no == ni->mft_no)
continue;
if (le32_to_cpu(m->bytes_allocated) -
le32_to_cpu(m->bytes_in_use) <
le32_to_cpu(ctx->attr->length) + extra)
continue;
if (!ntfs_attr_record_move_to(ctx, ni))
return 0;
}
ni = ntfs_mft_record_alloc(base_ni->vol, base_ni);
if (!ni) {
ntfs_log_perror("Couldn't allocate MFT record");
return -1;
}
if (ntfs_attr_record_move_to(ctx, ni)) {
ntfs_log_perror("Couldn't move attribute to MFT record");
return -1;
}
return 0;
}
int ntfs_attr_make_non_resident(ntfs_attr *na,
ntfs_attr_search_ctx *ctx)
{
s64 new_allocated_size, bw;
ntfs_volume *vol = na->ni->vol;
ATTR_REC *a = ctx->attr;
runlist *rl;
int mp_size, mp_ofs, name_ofs, arec_size, err;
ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x.\n", (unsigned long
long)na->ni->mft_no, le32_to_cpu(na->type));
if (NAttrNonResident(na)) {
ntfs_log_trace("Eeek! Trying to make non-resident attribute "
"non-resident. Aborting...\n");
errno = EINVAL;
return -1;
}
if (ntfs_attr_can_be_non_resident(vol, na->type, na->name, na->name_len))
return -1;
new_allocated_size = (le32_to_cpu(a->value_length) + vol->cluster_size
- 1) & ~(vol->cluster_size - 1);
if (new_allocated_size > 0) {
if ((a->flags & ATTR_COMPRESSION_MASK)
== ATTR_IS_COMPRESSED) {
new_allocated_size = ((new_allocated_size - 1)
| ((1L << (STANDARD_COMPRESSION_UNIT
+ vol->cluster_size_bits)) - 1)) + 1;
}
rl = ntfs_cluster_alloc(vol, 0, new_allocated_size >>
vol->cluster_size_bits, -1, DATA_ZONE);
if (!rl)
return -1;
} else
rl = NULL;
NAttrSetNonResident(na);
NAttrSetBeingNonResident(na);
na->rl = rl;
na->allocated_size = new_allocated_size;
na->data_size = na->initialized_size = le32_to_cpu(a->value_length);
NAttrClearSparse(na);
NAttrClearEncrypted(na);
if ((a->flags & ATTR_COMPRESSION_MASK) == ATTR_IS_COMPRESSED) {
na->compression_block_size
= 1 << (STANDARD_COMPRESSION_UNIT + vol->cluster_size_bits);
na->compression_block_clusters = 1 << STANDARD_COMPRESSION_UNIT;
}
if (rl) {
bw = ntfs_attr_pwrite(na, 0, le32_to_cpu(a->value_length),
(u8*)a + le16_to_cpu(a->value_offset));
if (bw != le32_to_cpu(a->value_length)) {
err = errno;
ntfs_log_debug("Eeek! Failed to write out attribute value "
"(bw = %lli, errno = %i). "
"Aborting...\n", (long long)bw, err);
if (bw >= 0)
err = EIO;
goto cluster_free_err_out;
}
}
mp_size = ntfs_get_size_for_mapping_pairs(vol, rl, 0, INT_MAX);
if (mp_size < 0) {
err = errno;
ntfs_log_debug("Eeek! Failed to get size for mapping pairs array. "
"Aborting...\n");
goto cluster_free_err_out;
}
if (na->ni->flags & FILE_ATTR_COMPRESSED)
name_ofs = (sizeof(ATTR_REC) + 7) & ~7;
else
name_ofs = (sizeof(ATTR_REC) - sizeof(a->compressed_size) + 7) & ~7;
mp_ofs = (name_ofs + a->name_length * sizeof(ntfschar) + 7) & ~7;
arec_size = (mp_ofs + mp_size + 7) & ~7;
if (ntfs_attr_record_resize(ctx->mrec, a, arec_size) < 0) {
err = errno;
goto cluster_free_err_out;
}
a->non_resident = 1;
if (a->name_length)
memmove((u8*)a + name_ofs, (u8*)a + le16_to_cpu(a->name_offset),
a->name_length * sizeof(ntfschar));
a->name_offset = cpu_to_le16(name_ofs);
a->lowest_vcn = const_cpu_to_sle64(0);
a->highest_vcn = cpu_to_sle64((new_allocated_size - 1) >>
vol->cluster_size_bits);
a->mapping_pairs_offset = cpu_to_le16(mp_ofs);
a->flags &= ~(ATTR_IS_SPARSE | ATTR_IS_ENCRYPTED);
if ((a->flags & ATTR_COMPRESSION_MASK) == ATTR_IS_COMPRESSED) {
a->compression_unit = STANDARD_COMPRESSION_UNIT;
a->compressed_size = const_cpu_to_sle64(0);
} else {
a->compression_unit = 0;
a->flags &= ~ATTR_COMPRESSION_MASK;
na->data_flags = a->flags;
}
memset(&a->reserved1, 0, sizeof(a->reserved1));
a->allocated_size = cpu_to_sle64(new_allocated_size);
a->data_size = a->initialized_size = cpu_to_sle64(na->data_size);
if (ntfs_mapping_pairs_build(vol, (u8*)a + mp_ofs, arec_size - mp_ofs,
rl, 0, NULL) < 0) {
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;
}
return 0;
cluster_free_err_out:
if (rl && ntfs_cluster_free(vol, na, 0, -1) < 0)
ntfs_log_trace("Eeek! Failed to release allocated clusters in error "
"code path. Leaving inconsistent metadata...\n");
NAttrClearNonResident(na);
NAttrClearFullyMapped(na);
na->allocated_size = na->data_size;
na->rl = NULL;
free(rl);
errno = err;
return -1;
}
static int ntfs_resident_attr_resize(ntfs_attr *na, const s64 newsize);
static int ntfs_resident_attr_resize_i(ntfs_attr *na, const s64 newsize,
hole_type holes)
{
ntfs_attr_search_ctx *ctx;
ntfs_volume *vol;
ntfs_inode *ni;
int err, ret = STATUS_ERROR;
ntfs_log_trace("Inode 0x%llx attr 0x%x new size %lld\n",
(unsigned long long)na->ni->mft_no, le32_to_cpu(na->type),
(long long)newsize);
ctx = ntfs_attr_get_search_ctx(na->ni, NULL);
if (!ctx)
return -1;
if (ntfs_attr_lookup(na->type, na->name, na->name_len, 0, 0, NULL, 0,
ctx)) {
err = errno;
ntfs_log_perror("ntfs_attr_lookup failed");
goto put_err_out;
}
vol = na->ni->vol;
if (ntfs_attr_size_bounds_check(vol, na->type, newsize) < 0) {
err = errno;
if (err == ENOENT)
err = EIO;
ntfs_log_perror("%s: bounds check failed", __FUNCTION__);
goto put_err_out;
}
if ((newsize < vol->mft_record_size) && (holes != HOLES_NONRES)) {
if (!(ret = ntfs_resident_attr_value_resize(ctx->mrec, ctx->attr,
newsize))) {
na->data_size = na->initialized_size = newsize;
na->allocated_size = (newsize + 7) & ~7;
if ((na->data_flags & ATTR_COMPRESSION_MASK)
|| NAttrSparse(na))
na->compressed_size = na->allocated_size;
if (na->ni->mrec->flags & MFT_RECORD_IS_DIRECTORY
? na->type == AT_INDEX_ROOT && na->name == NTFS_INDEX_I30
: na->type == AT_DATA && na->name == AT_UNNAMED) {
na->ni->data_size = na->data_size;
if (((na->data_flags & ATTR_COMPRESSION_MASK)
|| NAttrSparse(na))
&& NAttrNonResident(na))
na->ni->allocated_size
= na->compressed_size;
else
na->ni->allocated_size
= na->allocated_size;
set_nino_flag(na->ni,KnownSize);
if (na->type == AT_DATA)
NInoFileNameSetDirty(na->ni);
}
goto resize_done;
}
if (ret == STATUS_RESIDENT_ATTRIBUTE_FILLED_MFT) {
err = errno;
goto put_err_out;
}
}
if (!ntfs_attr_make_non_resident(na, ctx)) {
ntfs_inode_mark_dirty(ctx->ntfs_ino);
ntfs_attr_put_search_ctx(ctx);
if (holes == HOLES_NONRES) {
ret = 0;
if (newsize != na->data_size) {
ntfs_log_error("Cannot change size when"
" forcing non-resident\n");
errno = EIO;
ret = STATUS_ERROR;
}
return (ret);
}
return ntfs_attr_truncate_i(na, newsize, holes);
} else if (errno != ENOSPC && errno != EPERM) {
err = errno;
ntfs_log_perror("Failed to make attribute non-resident");
goto put_err_out;
}
ntfs_attr_init_search_ctx(ctx, NULL, na->ni->mrec);
while (!ntfs_attr_lookup(AT_UNUSED, NULL, 0, 0, 0, NULL, 0, ctx)) {
ntfs_attr *tna;
ATTR_RECORD *a;
a = ctx->attr;
if (a->non_resident)
continue;
if (le32_to_cpu(a->length) <= offsetof(ATTR_RECORD,
compressed_size) + ((a->name_length *
sizeof(ntfschar) + 7) & ~7) + 8)
continue;
tna = ntfs_attr_open(na->ni, a->type, (ntfschar*)((u8*)a +
le16_to_cpu(a->name_offset)), a->name_length);
if (!tna) {
err = errno;
ntfs_log_perror("Couldn't open attribute");
goto put_err_out;
}
if (ntfs_attr_make_non_resident(tna, ctx)) {
ntfs_attr_close(tna);
continue;
}
if ((tna->type == AT_DATA) && !tna->name_len) {
tna->ni->allocated_size = tna->allocated_size;
NInoFileNameSetDirty(tna->ni);
}
if (((tna->data_flags & ATTR_COMPRESSION_MASK)
== ATTR_IS_COMPRESSED)
&& ntfs_attr_pclose(tna)) {
err = errno;
ntfs_attr_close(tna);
goto put_err_out;
}
ntfs_inode_mark_dirty(tna->ni);
ntfs_attr_close(tna);
ntfs_attr_put_search_ctx(ctx);
return ntfs_resident_attr_resize_i(na, newsize, holes);
}
if (errno != ENOENT) {
err = errno;
ntfs_log_perror("%s: Attribute lookup failed 1", __FUNCTION__);
goto put_err_out;
}
if (na->type==AT_STANDARD_INFORMATION || na->type==AT_ATTRIBUTE_LIST) {
ntfs_attr_put_search_ctx(ctx);
if (!NInoAttrList(na->ni) && ntfs_inode_add_attrlist(na->ni)) {
ntfs_log_perror("Could not add attribute list");
return -1;
}
if (ntfs_inode_free_space(na->ni, offsetof(ATTR_RECORD,
non_resident_end) + 8)) {
ntfs_log_perror("Could not free space in MFT record");
return -1;
}
return ntfs_resident_attr_resize_i(na, newsize, holes);
}
ntfs_attr_init_search_ctx(ctx, na->ni, NULL);
if (ntfs_attr_lookup(na->type, na->name, na->name_len, CASE_SENSITIVE,
0, NULL, 0, ctx)) {
ntfs_log_perror("%s: Attribute lookup failed 2", __FUNCTION__);
err = errno;
goto put_err_out;
}
if (le32_to_cpu(ctx->mrec->bytes_in_use) ==
le16_to_cpu(ctx->mrec->attrs_offset) +
le32_to_cpu(ctx->attr->length) + 8) {
err = ENOSPC;
ntfs_log_trace("MFT record is filled with one attribute\n");
ret = STATUS_RESIDENT_ATTRIBUTE_FILLED_MFT;
goto put_err_out;
}
if (na->ni->nr_extents == -1)
ni = na->ni->base_ni;
else
ni = na->ni;
if (!NInoAttrList(ni)) {
ntfs_attr_put_search_ctx(ctx);
if (ntfs_inode_add_attrlist(ni))
return -1;
return ntfs_resident_attr_resize_i(na, newsize, holes);
}
ni = ntfs_mft_record_alloc(vol, ni);
if (!ni) {
err = errno;
ntfs_log_perror("Couldn't allocate new MFT record");
goto put_err_out;
}
if (ntfs_attr_record_move_to(ctx, ni)) {
err = errno;
ntfs_log_perror("Couldn't move attribute to new MFT record");
goto put_err_out;
}
if (na->ni->nr_extents == -1)
na->ni = ni;
ntfs_attr_put_search_ctx(ctx);
return ntfs_resident_attr_resize_i(na, newsize, holes);
resize_done:
ntfs_inode_mark_dirty(ctx->ntfs_ino);
ntfs_attr_put_search_ctx(ctx);
return 0;
put_err_out:
ntfs_attr_put_search_ctx(ctx);
errno = err;
return ret;
}
static int ntfs_resident_attr_resize(ntfs_attr *na, const s64 newsize)
{
int ret;
ntfs_log_enter("Entering\n");
ret = ntfs_resident_attr_resize_i(na, newsize, HOLES_OK);
ntfs_log_leave("\n");
return ret;
}
int ntfs_attr_force_non_resident(ntfs_attr *na)
{
int res;
res = ntfs_resident_attr_resize_i(na, na->data_size, HOLES_NONRES);
if (!res && !NAttrNonResident(na)) {
res = -1;
errno = EIO;
ntfs_log_error("Failed to force non-resident\n");
}
return (res);
}
static int ntfs_attr_make_resident(ntfs_attr *na, ntfs_attr_search_ctx *ctx)
{
ntfs_volume *vol = na->ni->vol;
ATTR_REC *a = ctx->attr;
int name_ofs, val_ofs, err = EIO;
s64 arec_size, bytes_read;
ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x.\n", (unsigned long
long)na->ni->mft_no, le32_to_cpu(na->type));
if (sle64_to_cpu(a->lowest_vcn)) {
ntfs_log_trace("Eeek! Should be called for the first extent of the "
"attribute. Aborting...\n");
errno = EINVAL;
return -1;
}
if (!NAttrNonResident(na)) {
ntfs_log_trace("Eeek! Trying to make resident attribute resident. "
"Aborting...\n");
errno = EINVAL;
return -1;
}
if (na->type == AT_BITMAP && na->ni->mft_no == FILE_MFT) {
errno = EPERM;
return -1;
}
if (ntfs_attr_can_be_resident(vol, na->type))
return -1;
if (na->data_flags & ATTR_IS_ENCRYPTED) {
ntfs_log_trace("Making encrypted streams resident is not "
"implemented yet.\n");
errno = EOPNOTSUPP;
return -1;
}
name_ofs = 24;
val_ofs = (name_ofs + a->name_length * sizeof(ntfschar) + 7) & ~7;
arec_size = (val_ofs + na->data_size + 7) & ~7;
if (le32_to_cpu(ctx->mrec->bytes_in_use) - le32_to_cpu(a->length) +
arec_size > le32_to_cpu(ctx->mrec->bytes_allocated)) {
errno = ENOSPC;
ntfs_log_trace("Not enough space to make attribute resident\n");
return -1;
}
if (ntfs_attr_map_whole_runlist(na))
return -1;
if (a->name_length) {
memmove((u8*)a + name_ofs, (u8*)a + le16_to_cpu(a->name_offset),
a->name_length * sizeof(ntfschar));
}
a->name_offset = cpu_to_le16(name_ofs);
if (ntfs_attr_record_resize(ctx->mrec, a, arec_size) < 0) {
ntfs_log_error("BUG! Failed to resize attribute record. "
"Please report to the %s. Aborting...\n",
NTFS_DEV_LIST);
errno = EIO;
return -1;
}
a->non_resident = 0;
a->flags = const_cpu_to_le16(0);
a->value_length = cpu_to_le32(na->data_size);
a->value_offset = cpu_to_le16(val_ofs);
if (!na->data_size
&& (na->type == AT_DATA)
&& (na->ni->vol->major_ver >= 3)
&& NVolCompression(na->ni->vol)
&& (na->ni->vol->cluster_size <= MAX_COMPRESSION_CLUSTER_SIZE)
&& (na->ni->flags & FILE_ATTR_COMPRESSED)) {
a->flags |= ATTR_IS_COMPRESSED;
na->data_flags = a->flags;
}
if (a->type == AT_FILE_NAME)
a->resident_flags = RESIDENT_ATTR_IS_INDEXED;
else
a->resident_flags = 0;
a->reservedR = 0;
if (na->initialized_size > na->data_size)
na->initialized_size = na->data_size;
bytes_read = ntfs_rl_pread(vol, na->rl, 0, na->initialized_size,
(u8*)a + val_ofs);
if (bytes_read != na->initialized_size) {
if (bytes_read < 0)
err = errno;
ntfs_log_trace("Eeek! Failed to read attribute data. Leaving "
"inconstant metadata. Run chkdsk. "
"Aborting...\n");
errno = err;
return -1;
}
if (na->initialized_size < na->data_size)
memset((u8*)a + val_ofs + na->initialized_size, 0,
na->data_size - na->initialized_size);
if (ntfs_cluster_free(vol, na, 0, -1) < 0) {
ntfs_log_perror("Eeek! Failed to release allocated clusters");
ntfs_log_trace("Ignoring error and leaving behind wasted "
"clusters.\n");
}
free(na->rl);
na->rl = NULL;
NAttrClearNonResident(na);
NAttrClearFullyMapped(na);
NAttrClearSparse(na);
NAttrClearEncrypted(na);
na->initialized_size = na->data_size;
na->allocated_size = na->compressed_size = (na->data_size + 7) & ~7;
na->compression_block_size = 0;
na->compression_block_size_bits = na->compression_block_clusters = 0;
return 0;
}
static int ntfs_attr_update_meta(ATTR_RECORD *a, ntfs_attr *na, MFT_RECORD *m,
hole_type holes, ntfs_attr_search_ctx *ctx)
{
int sparse, ret = 0;
ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x\n",
(unsigned long long)na->ni->mft_no, le32_to_cpu(na->type));
if (a->lowest_vcn)
goto out;
a->allocated_size = cpu_to_sle64(na->allocated_size);
if (holes == HOLES_DELAY)
sparse = (a->flags & ATTR_IS_SPARSE) != const_cpu_to_le16(0);
else {
sparse = ntfs_rl_sparse(na->rl);
if (sparse == -1) {
errno = EIO;
goto error;
}
}
if ((holes != HOLES_DELAY)
&& sparse
&& !(a->flags & (ATTR_IS_SPARSE | ATTR_IS_COMPRESSED))) {
if ((le32_to_cpu(a->length) -
le16_to_cpu(a->mapping_pairs_offset) == 8)
&& !(le32_to_cpu(m->bytes_allocated) -
le32_to_cpu(m->bytes_in_use))) {
if (!NInoAttrList(na->ni)) {
ntfs_attr_put_search_ctx(ctx);
if (ntfs_inode_add_attrlist(na->ni))
goto leave;
goto retry;
}
if (ntfs_attr_record_move_away(ctx, 8)) {
ntfs_log_perror("Failed to move attribute");
goto error;
}
ntfs_attr_put_search_ctx(ctx);
goto retry;
}
if (!(le32_to_cpu(a->length) - le16_to_cpu(
a->mapping_pairs_offset))) {
errno = EIO;
ntfs_log_perror("Mapping pairs space is 0");
goto error;
}
NAttrSetSparse(na);
a->flags |= ATTR_IS_SPARSE;
na->data_flags = a->flags;
a->compression_unit = STANDARD_COMPRESSION_UNIT;
memmove((u8*)a + le16_to_cpu(a->name_offset) + 8,
(u8*)a + le16_to_cpu(a->name_offset),
a->name_length * sizeof(ntfschar));
a->name_offset = cpu_to_le16(le16_to_cpu(a->name_offset) + 8);
a->mapping_pairs_offset =
cpu_to_le16(le16_to_cpu(a->mapping_pairs_offset) + 8);
}
if (!sparse && (a->flags & ATTR_IS_SPARSE) &&
!(a->flags & ATTR_IS_COMPRESSED)) {
NAttrClearSparse(na);
a->flags &= ~ATTR_IS_SPARSE;
na->data_flags = a->flags;
a->compression_unit = 0;
memmove((u8*)a + le16_to_cpu(a->name_offset) - 8,
(u8*)a + le16_to_cpu(a->name_offset),
a->name_length * sizeof(ntfschar));
if (le16_to_cpu(a->name_offset) >= 8)
a->name_offset = cpu_to_le16(le16_to_cpu(a->name_offset) - 8);
a->mapping_pairs_offset =
cpu_to_le16(le16_to_cpu(a->mapping_pairs_offset) - 8);
}
if (NAttrFullyMapped(na)
&& (sparse || (na->data_flags & ATTR_COMPRESSION_MASK))) {
s64 new_compr_size;
new_compr_size = ntfs_rl_get_compressed_size(na->ni->vol, na->rl);
if (new_compr_size == -1)
goto error;
na->compressed_size = new_compr_size;
a->compressed_size = cpu_to_sle64(new_compr_size);
}
if (na->type == AT_DATA && na->name == AT_UNNAMED) {
if (sparse || (na->data_flags & ATTR_COMPRESSION_MASK))
na->ni->allocated_size = na->compressed_size;
else
na->ni->allocated_size = na->allocated_size;
NInoFileNameSetDirty(na->ni);
}
out:
return ret;
leave: ret = -1; goto out;
retry: ret = -2; goto out;
error: ret = -3; goto out;
}
#define NTFS_VCN_DELETE_MARK -2
static int ntfs_attr_update_mapping_pairs_i(ntfs_attr *na, VCN from_vcn,
hole_type holes)
{
ntfs_attr_search_ctx *ctx;
ntfs_inode *ni, *base_ni;
MFT_RECORD *m;
ATTR_RECORD *a;
VCN stop_vcn;
const runlist_element *stop_rl;
int err, mp_size, cur_max_mp_size, exp_max_mp_size, ret = -1;
BOOL finished_build;
BOOL first_updated = FALSE;
retry:
if (!na || !na->rl) {
errno = EINVAL;
ntfs_log_perror("%s: na=%p", __FUNCTION__, na);
return -1;
}
ntfs_log_trace("Entering for inode %llu, attr 0x%x\n",
(unsigned long long)na->ni->mft_no, le32_to_cpu(na->type));
if (!NAttrNonResident(na)) {
errno = EINVAL;
ntfs_log_perror("%s: resident attribute", __FUNCTION__);
return -1;
}
#if PARTIAL_RUNLIST_UPDATING
if ((holes != HOLES_DELAY)
&& (!NAttrFullyMapped(na) || from_vcn)
&& !(na->data_flags & ATTR_IS_COMPRESSED)) {
BOOL changed;
if (!(na->data_flags & ATTR_IS_SPARSE)) {
int sparse = 0;
runlist_element *xrl;
for (xrl = na->rl; xrl->length; xrl++) {
if (xrl->lcn < 0) {
if (xrl->lcn == LCN_HOLE) {
sparse = 1;
break;
}
if (xrl->lcn != LCN_RL_NOT_MAPPED) {
sparse = -1;
break;
}
}
}
if (sparse < 0) {
ntfs_log_error("Could not check whether sparse\n");
errno = EIO;
return (-1);
}
changed = sparse > 0;
} else {
changed = (((na->data_size - 1)
| (na->ni->vol->cluster_size - 1)) + 1)
== na->compressed_size;
}
if (changed) {
if (ntfs_attr_map_whole_runlist(na)) {
ntfs_log_error("Could not map whole for sparse change\n");
errno = EIO;
return (-1);
}
from_vcn = 0;
}
}
#endif
if (na->ni->nr_extents == -1)
base_ni = na->ni->base_ni;
else
base_ni = na->ni;
ctx = ntfs_attr_get_search_ctx(base_ni, NULL);
if (!ctx)
return -1;
stop_vcn = 0;
stop_rl = na->rl;
finished_build = FALSE;
while (!ntfs_attr_lookup(na->type, na->name, na->name_len,
CASE_SENSITIVE, from_vcn, NULL, 0, ctx)) {
a = ctx->attr;
m = ctx->mrec;
if (!a->lowest_vcn)
first_updated = TRUE;
if (from_vcn) {
LCN first_lcn;
stop_vcn = sle64_to_cpu(a->lowest_vcn);
from_vcn = 0;
first_lcn = ntfs_rl_vcn_to_lcn(na->rl, stop_vcn);
if (first_lcn == LCN_EINVAL) {
errno = EIO;
ntfs_log_perror("Bad runlist");
goto put_err_out;
}
if (first_lcn == LCN_ENOENT ||
first_lcn == LCN_RL_NOT_MAPPED)
finished_build = TRUE;
}
if (finished_build) {
ntfs_log_trace("Mark attr 0x%x for delete in inode "
"%lld.\n", (unsigned)le32_to_cpu(a->type),
(long long)ctx->ntfs_ino->mft_no);
a->highest_vcn = cpu_to_sle64(NTFS_VCN_DELETE_MARK);
ntfs_inode_mark_dirty(ctx->ntfs_ino);
continue;
}
switch (ntfs_attr_update_meta(a, na, m, holes, ctx)) {
case -1: return -1;
case -2: goto retry;
case -3: goto put_err_out;
}
cur_max_mp_size = le32_to_cpu(a->length) -
le16_to_cpu(a->mapping_pairs_offset);
exp_max_mp_size = le32_to_cpu(m->bytes_allocated) -
le32_to_cpu(m->bytes_in_use) + cur_max_mp_size;
mp_size = ntfs_get_size_for_mapping_pairs(na->ni->vol, stop_rl,
stop_vcn, exp_max_mp_size);
if (mp_size <= 0) {
ntfs_log_perror("%s: get MP size failed", __FUNCTION__);
goto put_err_out;
}
if (mp_size > exp_max_mp_size) {
if (na->type == AT_ATTRIBUTE_LIST) {
ntfs_attr_put_search_ctx(ctx);
if (ntfs_inode_free_space(na->ni, mp_size -
cur_max_mp_size)) {
ntfs_log_perror("Attribute list is too "
"big. Defragment the "
"volume\n");
return -1;
}
goto retry;
}
if (!NInoAttrList(base_ni)) {
ntfs_attr_put_search_ctx(ctx);
if (ntfs_inode_add_attrlist(base_ni)) {
ntfs_log_perror("Can not add attrlist");
return -1;
}
goto retry;
}
mp_size = exp_max_mp_size;
}
if (((mp_size + 7) & ~7) != cur_max_mp_size) {
if (ntfs_attr_record_resize(m, a,
le16_to_cpu(a->mapping_pairs_offset) +
mp_size)) {
errno = EIO;
ntfs_log_perror("Failed to resize attribute");
goto put_err_out;
}
}
a->lowest_vcn = cpu_to_sle64(stop_vcn);
ntfs_inode_mark_dirty(ctx->ntfs_ino);
if ((ctx->ntfs_ino->nr_extents == -1 ||
NInoAttrList(ctx->ntfs_ino)) &&
ctx->attr->type != AT_ATTRIBUTE_LIST) {
ctx->al_entry->lowest_vcn = cpu_to_sle64(stop_vcn);
ntfs_attrlist_mark_dirty(ctx->ntfs_ino);
}
if (!ntfs_mapping_pairs_build(na->ni->vol, (u8*)a + le16_to_cpu(
a->mapping_pairs_offset), mp_size, na->rl,
stop_vcn, &stop_rl))
finished_build = TRUE;
if (stop_rl)
stop_vcn = stop_rl->vcn;
else
stop_vcn = 0;
if (!finished_build && errno != ENOSPC) {
ntfs_log_perror("Failed to build mapping pairs");
goto put_err_out;
}
a->highest_vcn = cpu_to_sle64(stop_vcn - 1);
}
if (errno != ENOENT) {
ntfs_log_perror("%s: Attribute lookup failed", __FUNCTION__);
goto put_err_out;
}
if (!first_updated) {
le16 spcomp;
ntfs_attr_reinit_search_ctx(ctx);
if (!ntfs_attr_lookup(na->type, na->name, na->name_len,
CASE_SENSITIVE, 0, NULL, 0, ctx)) {
a = ctx->attr;
a->allocated_size = cpu_to_sle64(na->allocated_size);
spcomp = na->data_flags
& (ATTR_IS_COMPRESSED | ATTR_IS_SPARSE);
if (spcomp)
a->compressed_size = cpu_to_sle64(na->compressed_size);
if (ctx->ntfs_ino)
NInoSetDirty(ctx->ntfs_ino);
if ((na->type == AT_DATA) && (na->name == AT_UNNAMED)) {
na->ni->allocated_size
= (spcomp
? na->compressed_size
: na->allocated_size);
NInoFileNameSetDirty(na->ni);
}
} else {
ntfs_log_error("Failed to update sizes in base extent\n");
goto put_err_out;
}
}
if (finished_build) {
ntfs_attr_reinit_search_ctx(ctx);
ntfs_log_trace("Deallocate marked extents.\n");
while (!ntfs_attr_lookup(na->type, na->name, na->name_len,
CASE_SENSITIVE, 0, NULL, 0, ctx)) {
if (sle64_to_cpu(ctx->attr->highest_vcn) !=
NTFS_VCN_DELETE_MARK)
continue;
if (ntfs_attr_record_rm(ctx)) {
ntfs_log_perror("Could not remove unused attr");
goto put_err_out;
}
ntfs_attr_reinit_search_ctx(ctx);
}
if (errno != ENOENT) {
ntfs_log_perror("%s: Attr lookup failed", __FUNCTION__);
goto put_err_out;
}
ntfs_log_trace("Deallocate done.\n");
ntfs_attr_put_search_ctx(ctx);
goto ok;
}
ntfs_attr_put_search_ctx(ctx);
ctx = NULL;
while (1) {
mp_size = ntfs_get_size_for_mapping_pairs(na->ni->vol,
na->rl, stop_vcn, INT_MAX);
if (mp_size <= 0) {
ntfs_log_perror("%s: get mp size failed", __FUNCTION__);
goto put_err_out;
}
if (!na->ni->mft_no)
ni = ntfs_mft_rec_alloc(na->ni->vol,
na->type == AT_DATA);
else
ni = ntfs_mft_record_alloc(na->ni->vol, base_ni);
if (!ni) {
ntfs_log_perror("Could not allocate new MFT record");
goto put_err_out;
}
m = ni->mrec;
cur_max_mp_size = le32_to_cpu(m->bytes_allocated) -
le32_to_cpu(m->bytes_in_use) -
(offsetof(ATTR_RECORD, compressed_size) +
(((na->data_flags & ATTR_COMPRESSION_MASK)
|| NAttrSparse(na)) ?
sizeof(a->compressed_size) : 0)) -
((sizeof(ntfschar) * na->name_len + 7) & ~7);
if (mp_size > cur_max_mp_size)
mp_size = cur_max_mp_size;
err = ntfs_non_resident_attr_record_add(ni, na->type,
na->name, na->name_len, stop_vcn, mp_size,
na->data_flags);
if (err == -1) {
err = errno;
ntfs_log_perror("Could not add attribute extent");
if (ntfs_mft_record_free(na->ni->vol, ni))
ntfs_log_perror("Could not free MFT record");
errno = err;
goto put_err_out;
}
a = (ATTR_RECORD*)((u8*)m + err);
err = ntfs_mapping_pairs_build(na->ni->vol, (u8*)a +
le16_to_cpu(a->mapping_pairs_offset), mp_size, na->rl,
stop_vcn, &stop_rl);
if (stop_rl)
stop_vcn = stop_rl->vcn;
else
stop_vcn = 0;
if (err < 0 && errno != ENOSPC) {
err = errno;
ntfs_log_perror("Failed to build MP");
if (ntfs_mft_record_free(na->ni->vol, ni))
ntfs_log_perror("Couldn't free MFT record");
errno = err;
goto put_err_out;
}
a->highest_vcn = cpu_to_sle64(stop_vcn - 1);
ntfs_inode_mark_dirty(ni);
if (!err)
break;
}
ok:
NAttrClearRunlistDirty(na);
ret = 0;
out:
return ret;
put_err_out:
if (ctx)
ntfs_attr_put_search_ctx(ctx);
goto out;
}
#undef NTFS_VCN_DELETE_MARK
int ntfs_attr_update_mapping_pairs(ntfs_attr *na, VCN from_vcn)
{
int ret;
ntfs_log_enter("Entering\n");
ret = ntfs_attr_update_mapping_pairs_i(na, from_vcn, HOLES_OK);
ntfs_log_leave("\n");
return ret;
}
static int ntfs_non_resident_attr_shrink(ntfs_attr *na, const s64 newsize)
{
ntfs_volume *vol;
ntfs_attr_search_ctx *ctx;
VCN first_free_vcn;
s64 nr_freed_clusters;
int err;
ntfs_log_trace("Inode 0x%llx attr 0x%x new size %lld\n", (unsigned long long)
na->ni->mft_no, le32_to_cpu(na->type), (long long)newsize);
vol = na->ni->vol;
if (ntfs_attr_size_bounds_check(vol, na->type, newsize) < 0) {
if (errno == ERANGE) {
ntfs_log_trace("Eeek! Size bounds check failed. "
"Aborting...\n");
} else if (errno == ENOENT)
errno = EIO;
return -1;
}
if (na->data_flags & ATTR_COMPRESSION_MASK)
first_free_vcn = (((newsize - 1)
| (na->compression_block_size - 1)) + 1)
>> vol->cluster_size_bits;
else
first_free_vcn = (newsize + vol->cluster_size - 1) >>
vol->cluster_size_bits;
if ((na->allocated_size >> vol->cluster_size_bits) != first_free_vcn) {
if (ntfs_attr_map_whole_runlist(na)) {
ntfs_log_trace("Eeek! ntfs_attr_map_whole_runlist "
"failed.\n");
return -1;
}
nr_freed_clusters = ntfs_cluster_free(vol, na, first_free_vcn,
-1);
if (nr_freed_clusters < 0) {
ntfs_log_trace("Eeek! Freeing of clusters failed. "
"Aborting...\n");
return -1;
}
if (ntfs_rl_truncate(&na->rl, first_free_vcn)) {
free(na->rl);
na->rl = NULL;
ntfs_log_trace("Eeek! Run list truncation failed.\n");
return -1;
}
NAttrSetRunlistDirty(na);
na->allocated_size = first_free_vcn << vol->cluster_size_bits;
if (ntfs_attr_update_mapping_pairs(na, 0 )) {
ntfs_log_trace("Eeek! Mapping pairs update failed. "
"Leaving inconstant metadata. "
"Run chkdsk.\n");
return -1;
}
}
ctx = ntfs_attr_get_search_ctx(na->ni, NULL);
if (!ctx)
return -1;
if (ntfs_attr_lookup(na->type, na->name, na->name_len, CASE_SENSITIVE,
0, NULL, 0, ctx)) {
err = errno;
if (err == ENOENT)
err = EIO;
ntfs_log_trace("Eeek! Lookup of first attribute extent failed. "
"Leaving inconstant metadata.\n");
goto put_err_out;
}
na->data_size = newsize;
ctx->attr->data_size = cpu_to_sle64(newsize);
if (newsize < na->initialized_size) {
na->initialized_size = newsize;
ctx->attr->initialized_size = cpu_to_sle64(newsize);
}
if (na->ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) {
if (na->type == AT_INDEX_ROOT && na->name == NTFS_INDEX_I30) {
na->ni->data_size = na->data_size;
na->ni->allocated_size = na->allocated_size;
set_nino_flag(na->ni,KnownSize);
}
} else {
if (na->type == AT_DATA && na->name == AT_UNNAMED) {
na->ni->data_size = na->data_size;
NInoFileNameSetDirty(na->ni);
}
}
if (!newsize) {
if (!(na->data_flags & ATTR_IS_ENCRYPTED)
&& ntfs_attr_make_resident(na, ctx)) {
if (errno != EPERM)
ntfs_log_error("Failed to make attribute "
"resident. Leaving as is...\n");
}
}
ntfs_inode_mark_dirty(ctx->ntfs_ino);
ntfs_attr_put_search_ctx(ctx);
return 0;
put_err_out:
ntfs_attr_put_search_ctx(ctx);
errno = err;
return -1;
}
static int ntfs_non_resident_attr_expand_i(ntfs_attr *na, const s64 newsize,
hole_type holes)
{
LCN lcn_seek_from;
VCN first_free_vcn;
ntfs_volume *vol;
ntfs_attr_search_ctx *ctx;
runlist *rl, *rln;
s64 org_alloc_size;
int err;
ntfs_log_trace("Inode %lld, attr 0x%x, new size %lld old size %lld\n",
(unsigned long long)na->ni->mft_no, le32_to_cpu(na->type),
(long long)newsize, (long long)na->data_size);
vol = na->ni->vol;
if (ntfs_attr_size_bounds_check(vol, na->type, newsize) < 0) {
if (errno == ENOENT)
errno = EIO;
ntfs_log_perror("%s: bounds check failed", __FUNCTION__);
return -1;
}
if (na->type == AT_DATA)
NAttrSetDataAppending(na);
org_alloc_size = na->allocated_size;
first_free_vcn = (newsize + vol->cluster_size - 1) >>
vol->cluster_size_bits;
if ((na->allocated_size >> vol->cluster_size_bits) < first_free_vcn) {
#if PARTIAL_RUNLIST_UPDATING
s64 start_update;
start_update = na->allocated_size >> vol->cluster_size_bits;
if (start_update)
start_update--;
if (ntfs_attr_map_partial_runlist(na, start_update)) {
ntfs_log_perror("failed to map partial runlist");
return -1;
}
#else
if (ntfs_attr_map_whole_runlist(na)) {
ntfs_log_perror("ntfs_attr_map_whole_runlist failed");
return -1;
}
#endif
if ((na->type == AT_DATA) && (vol->major_ver >= 3)
&& (holes != HOLES_NO)) {
rl = ntfs_malloc(0x1000);
if (!rl)
return -1;
rl[0].vcn = (na->allocated_size >>
vol->cluster_size_bits);
rl[0].lcn = LCN_HOLE;
rl[0].length = first_free_vcn -
(na->allocated_size >> vol->cluster_size_bits);
rl[1].vcn = first_free_vcn;
rl[1].lcn = LCN_ENOENT;
rl[1].length = 0;
} else {
lcn_seek_from = -1;
if (na->rl->length) {
for (rl = na->rl; (rl + 1)->length; rl++)
;
while (rl->lcn < 0 && rl != na->rl)
rl--;
if (rl->lcn >= 0)
lcn_seek_from = rl->lcn + rl->length;
}
rl = ntfs_cluster_alloc(vol, na->allocated_size >>
vol->cluster_size_bits, first_free_vcn -
(na->allocated_size >>
vol->cluster_size_bits), lcn_seek_from,
DATA_ZONE);
if (!rl) {
ntfs_log_perror("Cluster allocation failed "
"(%lld)",
(long long)first_free_vcn -
((long long)na->allocated_size >>
vol->cluster_size_bits));
return -1;
}
}
rln = ntfs_runlists_merge(na->rl, rl);
if (!rln) {
err = errno;
ntfs_log_perror("Run list merge failed");
ntfs_cluster_free_from_rl(vol, rl);
free(rl);
errno = err;
return -1;
}
na->rl = rln;
NAttrSetRunlistDirty(na);
na->allocated_size = first_free_vcn << vol->cluster_size_bits;
#if PARTIAL_RUNLIST_UPDATING
if ((holes != HOLES_DELAY)
&& ntfs_attr_update_mapping_pairs_i(na, start_update,
holes)) {
#else
if (ntfs_attr_update_mapping_pairs(na, 0)) {
#endif
err = errno;
ntfs_log_perror("Mapping pairs update failed");
goto rollback;
}
}
ctx = ntfs_attr_get_search_ctx(na->ni, NULL);
if (!ctx) {
err = errno;
if (na->allocated_size == org_alloc_size) {
errno = err;
return -1;
} else
goto rollback;
}
if (ntfs_attr_lookup(na->type, na->name, na->name_len, CASE_SENSITIVE,
0, NULL, 0, ctx)) {
err = errno;
ntfs_log_perror("Lookup of first attribute extent failed");
if (err == ENOENT)
err = EIO;
if (na->allocated_size != org_alloc_size) {
ntfs_attr_put_search_ctx(ctx);
goto rollback;
} else
goto put_err_out;
}
na->data_size = newsize;
ctx->attr->data_size = cpu_to_sle64(newsize);
if (na->ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) {
if (na->type == AT_INDEX_ROOT && na->name == NTFS_INDEX_I30) {
na->ni->data_size = na->data_size;
na->ni->allocated_size = na->allocated_size;
set_nino_flag(na->ni,KnownSize);
}
} else {
if (na->type == AT_DATA && na->name == AT_UNNAMED) {
na->ni->data_size = na->data_size;
NInoFileNameSetDirty(na->ni);
}
}
ntfs_inode_mark_dirty(ctx->ntfs_ino);
ntfs_attr_put_search_ctx(ctx);
return 0;
rollback:
if (ntfs_cluster_free(vol, na, org_alloc_size >>
vol->cluster_size_bits, -1) < 0) {
err = EIO;
ntfs_log_perror("Leaking clusters");
}
if (ntfs_rl_truncate(&na->rl, org_alloc_size >>
vol->cluster_size_bits)) {
free(na->rl);
na->rl = NULL;
ntfs_log_perror("Couldn't truncate runlist. Rollback failed");
} else {
NAttrSetRunlistDirty(na);
na->allocated_size = org_alloc_size;
if (ntfs_attr_update_mapping_pairs(na, 0
)) {
ntfs_log_perror("Failed to restore old mapping pairs");
}
}
errno = err;
return -1;
put_err_out:
ntfs_attr_put_search_ctx(ctx);
errno = err;
return -1;
}
static int ntfs_non_resident_attr_expand(ntfs_attr *na, const s64 newsize,
hole_type holes)
{
int ret;
ntfs_log_enter("Entering\n");
ret = ntfs_non_resident_attr_expand_i(na, newsize, holes);
ntfs_log_leave("\n");
return ret;
}
static int ntfs_attr_truncate_i(ntfs_attr *na, const s64 newsize,
hole_type holes)
{
int ret = STATUS_ERROR;
s64 fullsize;
BOOL compressed;
if (!na || newsize < 0 ||
(na->ni->mft_no == FILE_MFT && na->type == AT_DATA)) {
ntfs_log_trace("Invalid arguments passed.\n");
errno = EINVAL;
return STATUS_ERROR;
}
ntfs_log_enter("Entering for inode %lld, attr 0x%x, size %lld\n",
(unsigned long long)na->ni->mft_no, le32_to_cpu(na->type),
(long long)newsize);
if (na->data_size == newsize) {
ntfs_log_trace("Size is already ok\n");
ret = STATUS_OK;
goto out;
}
if ((na->data_flags & ATTR_IS_ENCRYPTED) && !na->ni->vol->efs_raw) {
errno = EACCES;
ntfs_log_trace("Cannot truncate encrypted attribute\n");
goto out;
}
compressed = (na->data_flags & ATTR_COMPRESSION_MASK)
!= const_cpu_to_le16(0);
if (compressed
&& NAttrNonResident(na)
&& ((na->data_flags & ATTR_COMPRESSION_MASK) != ATTR_IS_COMPRESSED)) {
errno = EOPNOTSUPP;
ntfs_log_perror("Failed to truncate compressed attribute");
goto out;
}
if (NAttrNonResident(na)) {
if (compressed && newsize && (newsize > na->data_size))
fullsize = (na->initialized_size
| (na->compression_block_size - 1)) + 1;
else
fullsize = newsize;
if (fullsize > na->data_size)
ret = ntfs_non_resident_attr_expand(na, fullsize,
holes);
else
ret = ntfs_non_resident_attr_shrink(na, fullsize);
} else
ret = ntfs_resident_attr_resize_i(na, newsize, holes);
out:
ntfs_log_leave("Return status %d\n", ret);
return ret;
}
int ntfs_attr_truncate(ntfs_attr *na, const s64 newsize)
{
int r;
r = ntfs_attr_truncate_i(na, newsize, HOLES_OK);
NAttrClearDataAppending(na);
NAttrClearBeingNonResident(na);
return (r);
}
int ntfs_attr_truncate_solid(ntfs_attr *na, const s64 newsize)
{
return (ntfs_attr_truncate_i(na, newsize, HOLES_NO));
}
static int stuff_hole(ntfs_attr *na, const s64 pos)
{
s64 size;
s64 begin_size;
s64 end_size;
char *buf;
int ret;
ret = 0;
if (!NAttrNonResident(na)) {
ret = ntfs_resident_attr_resize(na, pos);
if (!ret && !NAttrNonResident(na))
na->initialized_size = na->data_size = pos;
}
if (!ret && NAttrNonResident(na)) {
if ((pos ^ na->initialized_size)
& ~(na->compression_block_size - 1)) {
begin_size = ((na->initialized_size - 1)
| (na->compression_block_size - 1))
+ 1 - na->initialized_size;
end_size = pos & (na->compression_block_size - 1);
size = (begin_size > end_size ? begin_size : end_size);
} else {
begin_size = size = pos - na->initialized_size;
end_size = 0;
}
if (size)
buf = (char*)ntfs_malloc(size);
else
buf = (char*)NULL;
if (buf || !size) {
memset(buf,0,size);
if (begin_size
&& (ntfs_attr_pwrite(na,
na->initialized_size, begin_size, buf)
!= begin_size))
ret = -1;
if (!ret
&& ((na->initialized_size + end_size) < pos)
&& ntfs_non_resident_attr_expand(na,
pos - end_size, HOLES_OK))
ret = -1;
else
na->initialized_size
= na->data_size = pos - end_size;
if (!ret && end_size
&& (ntfs_attr_pwrite(na,
na->initialized_size, end_size, buf)
!= end_size))
ret = -1;
if (buf)
free(buf);
} else
ret = -1;
}
if (!ret && (na->initialized_size != pos)) {
ntfs_log_error("Failed to stuff a compressed file"
"target %lld reached %lld\n",
(long long)pos, (long long)na->initialized_size);
errno = EIO;
ret = -1;
}
return (ret);
}
void *ntfs_attr_readall(ntfs_inode *ni, const ATTR_TYPES type,
ntfschar *name, u32 name_len, s64 *data_size)
{
ntfs_attr *na;
void *data, *ret = NULL;
s64 size;
ntfs_log_enter("Entering\n");
na = ntfs_attr_open(ni, type, name, name_len);
if (!na) {
ntfs_log_perror("ntfs_attr_open failed, inode %lld attr 0x%lx",
(long long)ni->mft_no,(long)le32_to_cpu(type));
goto err_exit;
}
if (((u64)na->data_size > 65536)
&& ((type != AT_BITMAP)
|| ((u64)na->data_size >
(u64)((ni->vol->nr_clusters + 7) >> 3)))) {
ntfs_log_error("Corrupt attribute 0x%lx in inode %lld\n",
(long)le32_to_cpu(type),(long long)ni->mft_no);
errno = EOVERFLOW;
goto out;
}
data = ntfs_malloc(na->data_size);
if (!data)
goto out;
size = ntfs_attr_pread(na, 0, na->data_size, data);
if (size != na->data_size) {
ntfs_log_perror("ntfs_attr_pread failed");
free(data);
goto out;
}
ret = data;
if (data_size)
*data_size = size;
out:
ntfs_attr_close(na);
err_exit:
ntfs_log_leave("\n");
return ret;
}
int ntfs_attr_data_read(ntfs_inode *ni,
ntfschar *stream_name, int stream_name_len,
char *buf, size_t size, off_t offset)
{
ntfs_attr *na = NULL;
int res, total = 0;
na = ntfs_attr_open(ni, AT_DATA, stream_name, stream_name_len);
if (!na) {
res = -errno;
goto exit;
}
if ((size_t)offset < (size_t)na->data_size) {
if (offset + size > (size_t)na->data_size)
size = na->data_size - offset;
while (size) {
res = ntfs_attr_pread(na, offset, size, buf + total);
if ((off_t)res < (off_t)size)
ntfs_log_perror("ntfs_attr_pread partial read "
"(%lld : %lld <> %d)",
(long long)offset,
(long long)size, res);
if (res <= 0) {
res = -errno;
goto exit;
}
size -= res;
offset += res;
total += res;
}
}
res = total;
exit:
if (na)
ntfs_attr_close(na);
return res;
}
int ntfs_attr_data_write(ntfs_inode *ni, ntfschar *stream_name,
int stream_name_len, const char *buf, size_t size, off_t offset)
{
ntfs_attr *na = NULL;
int res, total = 0;
na = ntfs_attr_open(ni, AT_DATA, stream_name, stream_name_len);
if (!na) {
res = -errno;
goto exit;
}
while (size) {
res = ntfs_attr_pwrite(na, offset, size, buf + total);
if (res < (s64)size)
ntfs_log_perror("ntfs_attr_pwrite partial write (%lld: "
"%lld <> %d)", (long long)offset,
(long long)size, res);
if (res <= 0) {
res = -errno;
goto exit;
}
size -= res;
offset += res;
total += res;
}
res = total;
exit:
if (na)
ntfs_attr_close(na);
return res;
}
int ntfs_attr_shrink_size(ntfs_inode *ni, ntfschar *stream_name,
int stream_name_len, off_t offset)
{
ntfs_attr_search_ctx *ctx;
ATTR_RECORD *a;
int res;
res = -1;
ctx = ntfs_attr_get_search_ctx(ni, NULL);
if (ctx) {
if (!ntfs_attr_lookup(AT_DATA, stream_name, stream_name_len,
CASE_SENSITIVE, 0, NULL, 0, ctx)) {
a = ctx->attr;
if (a->non_resident
&& (sle64_to_cpu(a->initialized_size) > offset)) {
a->initialized_size = cpu_to_sle64(offset);
a->data_size = a->initialized_size;
}
res = 0;
}
ntfs_attr_put_search_ctx(ctx);
}
return (res);
}
int ntfs_attr_exist(ntfs_inode *ni, const ATTR_TYPES type, const ntfschar *name,
u32 name_len)
{
ntfs_attr_search_ctx *ctx;
int ret;
ntfs_log_trace("Entering\n");
ctx = ntfs_attr_get_search_ctx(ni, NULL);
if (!ctx)
return 0;
ret = ntfs_attr_lookup(type, name, name_len, CASE_SENSITIVE, 0, NULL, 0,
ctx);
ntfs_attr_put_search_ctx(ctx);
return !ret;
}
int ntfs_attr_remove(ntfs_inode *ni, const ATTR_TYPES type, ntfschar *name,
u32 name_len)
{
ntfs_attr *na;
int ret;
ntfs_log_trace("Entering\n");
if (!ni) {
ntfs_log_error("%s: NULL inode pointer", __FUNCTION__);
errno = EINVAL;
return -1;
}
na = ntfs_attr_open(ni, type, name, name_len);
if (!na) {
if (type != AT_DATA) {
ntfs_log_perror("Failed to open attribute 0x%02x of inode "
"0x%llx", le32_to_cpu(type), (unsigned long long)ni->mft_no);
}
return -1;
}
ret = ntfs_attr_rm(na);
if (ret)
ntfs_log_perror("Failed to remove attribute 0x%02x of inode "
"0x%llx", le32_to_cpu(type), (unsigned long long)ni->mft_no);
ntfs_attr_close(na);
return ret;
}
#define BCX(x) ((x) - (((x) >> 1) & 0x77777777) - \
(((x) >> 2) & 0x33333333) - \
(((x) >> 3) & 0x11111111))
#define BITCOUNT(x) (((BCX(x) + (BCX(x) >> 4)) & 0x0F0F0F0F) % 255)
static u8 *ntfs_init_lut256(void)
{
int i;
u8 *lut;
lut = ntfs_malloc(256);
if (lut)
for(i = 0; i < 256; i++)
*(lut + i) = 8 - BITCOUNT(i);
return lut;
}
s64 ntfs_attr_get_free_bits(ntfs_attr *na)
{
u8 *buf, *lut;
s64 br = 0;
s64 total = 0;
s64 nr_free = 0;
lut = ntfs_init_lut256();
if (!lut)
return -1;
buf = ntfs_malloc(65536);
if (!buf)
goto out;
while (1) {
u32 *p;
br = ntfs_attr_pread(na, total, 65536, buf);
if (br <= 0)
break;
total += br;
p = (u32 *)buf + br / 4 - 1;
for (; (u8 *)p >= buf; p--) {
nr_free += lut[ *p & 255] +
lut[(*p >> 8) & 255] +
lut[(*p >> 16) & 255] +
lut[(*p >> 24) ];
}
switch (br % 4) {
case 3: nr_free += lut[*(buf + br - 3)];
case 2: nr_free += lut[*(buf + br - 2)];
case 1: nr_free += lut[*(buf + br - 1)];
}
}
free(buf);
out:
free(lut);
if (!total || br < 0)
return -1;
return nr_free;
}