From 08689c171e1c1fa65e3e855927a22399b0ae6d63 Mon Sep 17 00:00:00 2001 From: yura Date: Wed, 2 Aug 2006 03:55:41 +0000 Subject: [PATCH] Merge index code from ntfs-3g. Clean it a bit and fix adding index allocation when index root in the base mft record. --- ChangeLog | 2 + configure.ac | 2 +- include/ntfs/index.h | 59 +- include/ntfs/types.h | 7 + libntfs/index.c | 2059 ++++++++++++++++++++++++++++++++---------- 5 files changed, 1621 insertions(+), 508 deletions(-) diff --git a/ChangeLog b/ChangeLog index ee87f32f..b20f151a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -8,6 +8,8 @@ xx/xx/2006 - x.xx.x - . ntfs kernel driver to the same function. (Anton) - Add new API ntfs_attr_exist() that checks whether attribute with selected type and name already present in inode. (Szaka) + - Extend ntfs_index_{add_filename,rm} to support almost all cases of + index operations. (Szaka, Yura) 21/06/2006 - 1.13.1 - Various fixes. diff --git a/configure.ac b/configure.ac index a1975eee..d7056501 100644 --- a/configure.ac +++ b/configure.ac @@ -22,7 +22,7 @@ # AC_PREREQ(2.59) -AC_INIT([ntfsprogs],[1.13.1],[linux-ntfs-dev@lists.sourceforge.net]) +AC_INIT([ntfsprogs],[3.0.0-WIP],[linux-ntfs-dev@lists.sourceforge.net]) # # Before making a release, the LTVERSION string should be modified. diff --git a/include/ntfs/index.h b/include/ntfs/index.h index 10465dd8..ecd53cfc 100644 --- a/include/ntfs/index.h +++ b/include/ntfs/index.h @@ -1,9 +1,10 @@ /* * index.h - Defines for NTFS index handling. Part of the Linux-NTFS project. * - * Copyright (c) 2004 Anton Altaparmakov - * Copyright (c) 2005 Yura Pakhuchiy + * Copyright (c) 2004 Anton Altaparmakov * Copyright (c) 2004-2005 Richard Russon + * Copyright (c) 2005-2006 Yura Pakhuchiy + * Copyright (c) 2006 Szabolcs Szakacsits * * This program/include file is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as published @@ -30,24 +31,32 @@ #include "inode.h" #include "mft.h" +#define VCN_INDEX_ROOT_PARENT ((VCN)-2) + +#define MAX_PARENT_VCN 32 + /** * struct ntfs_index_context - * @ni: inode containing the @entry described by this context * @name: name of the index described by this context * @name_len: length of the index name - * @entry: index entry (points into @ir or @ia) + * @entry: index entry (points into @ir or @ib) * @data: index entry data (points into @entry) * @data_len: length in bytes of @data - * @is_in_root: TRUE if @entry is in @ir or FALSE if it is in @ia + * @cr: + * @is_in_root: TRUE if @entry is in @ir or FALSE if it is in @ib * @ir: index root if @is_in_root or NULL otherwise * @actx: attribute search context if in root or NULL otherwise - * @ia: index block if @is_in_root is FALSE or NULL otherwise * @ia_na: opened INDEX_ALLOCATION attribute - * @ia_vcn: VCN from which @ia where read from - * @ia_dirty: TRUE if index block was changed + * @ib: index block if @is_in_root is FALSE or NULL otherwise + * @ib_vcn: VCN from which @ib where read from + * @ib_dirty: TRUE if index block was changed + * @parent_pos: parent entries' positions in the index block + * @parent_vcn: entry's parent nodes or VCN_INDEX_ROOT_PARENT for root + * @max_depth: number of the parent nodes + * @pindex: maximum it's the number of the parent nodes * @block_size: index block size - * @vcn_size: VCN size for this index block - * @vcn_size_bits: use instead of @vcn_size to speedup multiplication + * @vcn_size_bits: VCN size bits for this index block * * @ni is the inode this context belongs to. * @@ -56,13 +65,13 @@ * simply points into @entry. This is probably what the user is interested in. * * If @is_in_root is TRUE, @entry is in the index root attribute @ir described - * by the attribute search context @actx and inode @ni. @ia, @ia_vcn and - * @ia_dirty are undefined in this case. + * by the attribute search context @actx and inode @ni. @ib, @ib_vcn and + * @ib_dirty are undefined in this case. * - * If @is_in_root is FALSE, @entry is in the index allocation attribute and @ia - * and @ia_vcn point to the index allocation block and VCN where it's placed, + * If @is_in_root is FALSE, @entry is in the index allocation attribute and @ib + * and @ib_vcn point to the index allocation block and VCN where it's placed, * respectively. @ir and @actx are NULL in this case. @ia_na is opened - * INDEX_ALLOCATION attribute. @ia_dirty is TRUE if index block was changed and + * INDEX_ALLOCATION attribute. @ib_dirty is TRUE if index block was changed and * FALSE otherwise. * * To obtain a context call ntfs_index_ctx_get(). @@ -81,15 +90,19 @@ typedef struct { INDEX_ENTRY *entry; void *data; u16 data_len; + COLLATION_RULES cr; BOOL is_in_root; INDEX_ROOT *ir; ntfs_attr_search_ctx *actx; - INDEX_ALLOCATION *ia; ntfs_attr *ia_na; - VCN ia_vcn; - BOOL ia_dirty; + INDEX_BLOCK *ib; + VCN ib_vcn; + BOOL ib_dirty; + int parent_pos[MAX_PARENT_VCN]; + VCN parent_vcn[MAX_PARENT_VCN]; + int max_depth; + int pindex; u32 block_size; - u32 vcn_size; u8 vcn_size_bits; } ntfs_index_context; @@ -107,6 +120,12 @@ extern int ntfs_index_rm(ntfs_index_context *ictx); extern INDEX_ROOT *ntfs_index_root_get(ntfs_inode *ni, ATTR_RECORD *attr); +extern VCN ntfs_ie_get_vcn(INDEX_ENTRY *ie); + +char *ntfs_ie_filename_get(INDEX_ENTRY *ie); +void ntfs_ie_filename_dump(INDEX_ENTRY *ie); +void ntfs_ih_filename_dump(INDEX_HEADER *ih); + /** * ntfs_index_entry_mark_dirty - mark an index entry dirty * @ictx: ntfs index context describing the index entry @@ -118,7 +137,7 @@ extern INDEX_ROOT *ntfs_index_root_get(ntfs_inode *ni, ATTR_RECORD *attr); * hence the index root attribute, will be written out to disk later. * * If the index entry is in an index block belonging to the index allocation - * attribute, set ia_dirty to TRUE, thus index block will be updated during + * attribute, set ib_dirty to TRUE, thus index block will be updated during * ntfs_index_ctx_put. */ static inline void ntfs_index_entry_mark_dirty(ntfs_index_context *ictx) @@ -126,7 +145,7 @@ static inline void ntfs_index_entry_mark_dirty(ntfs_index_context *ictx) if (ictx->is_in_root) ntfs_inode_mark_dirty(ictx->actx->ntfs_ino); else - ictx->ia_dirty = TRUE; + ictx->ib_dirty = TRUE; } #endif /* _NTFS_INDEX_H */ diff --git a/include/ntfs/types.h b/include/ntfs/types.h index 4987d785..f4d7c771 100644 --- a/include/ntfs/types.h +++ b/include/ntfs/types.h @@ -3,6 +3,7 @@ * the Linux-NTFS project. * * Copyright (c) 2000-2004 Anton Altaparmakov + * Copyright (c) 2006 Szabolcs Szakacsits * * This program/include file is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as published @@ -114,5 +115,11 @@ typedef enum { IGNORE_CASE = 1, } IGNORE_CASE_BOOL; +#define STATUS_OK (0) +#define STATUS_ERROR (-1) +#define STATUS_RESIDENT_ATTRIBUTE_FILLED_MFT (-2) +#define STATUS_KEEP_SEARCHING (-3) +#define STATUS_NOT_FOUND (-4) + #endif /* defined _NTFS_TYPES_H */ diff --git a/libntfs/index.c b/libntfs/index.c index 8fd78b47..126af72e 100644 --- a/libntfs/index.c +++ b/libntfs/index.c @@ -2,8 +2,9 @@ * index.c - NTFS index handling. Part of the Linux-NTFS project. * * Copyright (c) 2004-2005 Anton Altaparmakov - * Copyright (c) 2005 Yura Pakhuchiy * Copyright (c) 2004-2005 Richard Russon + * Copyright (c) 2005-2006 Yura Pakhuchiy + * Copyright (c) 2005-2006 Szabolcs Szakacsits * * This program/include file is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as published @@ -42,6 +43,44 @@ #include "mst.h" #include "dir.h" #include "logging.h" +#include "bitmap.h" +#include "support.h" + +static s64 ntfs_ib_vcn_to_pos(ntfs_index_context *icx, VCN vcn) +{ + return vcn << icx->vcn_size_bits; +} + +static VCN ntfs_ib_pos_to_vcn(ntfs_index_context *icx, s64 pos) +{ + return pos >> icx->vcn_size_bits; +} + +static int ntfs_ib_write(ntfs_index_context *icx, VCN vcn, void *buf) +{ + s64 ret; + + ntfs_log_trace("vcn: %lld\n", vcn); + + ret = ntfs_attr_mst_pwrite(icx->ia_na, ntfs_ib_vcn_to_pos(icx, vcn), + 1, icx->block_size, buf); + if (ret != 1) { + ntfs_log_perror("Failed to write index block %lld of inode " + "%llu", vcn, icx->ni->mft_no); + return STATUS_ERROR; + } + return STATUS_OK; +} + +static int ntfs_icx_ib_write(ntfs_index_context *icx) +{ + if (ntfs_ib_write(icx, icx->ib_vcn, icx->ib)) + return STATUS_ERROR; + + icx->ib_dirty = FALSE; + + return STATUS_OK; +} /** * ntfs_index_ctx_get - allocate and initialize a new index context @@ -53,115 +92,528 @@ * Return NULL if allocation failed. */ ntfs_index_context *ntfs_index_ctx_get(ntfs_inode *ni, - ntfschar *name, u32 name_len) + ntfschar *name, u32 name_len) { - ntfs_index_context *ictx; + ntfs_index_context *icx; + ntfs_log_trace("Entering.\n"); + if (!ni) { errno = EINVAL; return NULL; } if (ni->nr_extents == -1) ni = ni->base_ni; - ictx = malloc(sizeof(ntfs_index_context)); - if (ictx) - *ictx = (ntfs_index_context) { + icx = ntfs_calloc(sizeof(ntfs_index_context)); + if (icx) + *icx = (ntfs_index_context) { .ni = ni, .name = name, .name_len = name_len, }; - return ictx; + return icx; +} + +static void ntfs_index_ctx_free(ntfs_index_context *icx) +{ + ntfs_log_trace("Entering.\n"); + + if (!icx->entry) + return; + + if (icx->actx) + ntfs_attr_put_search_ctx(icx->actx); + + if (icx->is_in_root) { + if (icx->ia_na) + ntfs_attr_close(icx->ia_na); + return; + } + + if (icx->ib_dirty) { + /* FIXME: Error handling!!! */ + ntfs_ib_write(icx, icx->ib_vcn, icx->ib); + } + + free(icx->ib); + ntfs_attr_close(icx->ia_na); } /** * ntfs_index_ctx_put - release an index context - * @ictx: index context to free + * @icx: index context to free * - * Release the index context @ictx, releasing all associated resources. + * Release the index context @icx, releasing all associated resources. */ -void ntfs_index_ctx_put(ntfs_index_context *ictx) +void ntfs_index_ctx_put(ntfs_index_context *icx) { - if (ictx->entry) { - if (ictx->is_in_root) { - if (ictx->actx) - ntfs_attr_put_search_ctx(ictx->actx); - } else { - /* Write out index block it it's dirty. */ - if (ictx->ia_dirty) { - if (ntfs_attr_mst_pwrite(ictx->ia_na, - ictx->ia_vcn << - ictx->vcn_size_bits, - 1, ictx->block_size, - ictx->ia) != 1) - ntfs_log_error("Failed to write out " - "index block.\n"); - } - /* Free resources. */ - free(ictx->ia); - ntfs_attr_close(ictx->ia_na); - } - } - free(ictx); + ntfs_index_ctx_free(icx); + free(icx); } /** * ntfs_index_ctx_reinit - reinitialize an index context - * @ictx: index context to reinitialize + * @icx: index context to reinitialize * - * Reinitialize the index context @ictx so it can be used for ntfs_index_lookup. + * Reinitialize the index context @icx so it can be used for ntfs_index_lookup. */ -void ntfs_index_ctx_reinit(ntfs_index_context *ictx) +void ntfs_index_ctx_reinit(ntfs_index_context *icx) { - if (ictx->entry) { - if (ictx->is_in_root) { - if (ictx->actx) - ntfs_attr_put_search_ctx(ictx->actx); - } else { - /* Write out index block it it's dirty. */ - if (ictx->ia_dirty) { - if (ntfs_attr_mst_pwrite(ictx->ia_na, - ictx->ia_vcn << - ictx->vcn_size_bits, - 1, ictx->block_size, - ictx->ia) != 1) - ntfs_log_error("Failed to write out " - "index block.\n"); - } - /* Free resources. */ - free(ictx->ia); - ntfs_attr_close(ictx->ia_na); - } - } - *ictx = (ntfs_index_context) { - .ni = ictx->ni, - .name = ictx->name, - .name_len = ictx->name_len, + ntfs_log_trace("Entering.\n"); + + ntfs_index_ctx_free(icx); + + *icx = (ntfs_index_context) { + .ni = icx->ni, + .name = icx->name, + .name_len = icx->name_len, }; } +static VCN *ntfs_ie_get_vcn_addr(INDEX_ENTRY *ie) +{ + return (VCN *)((u8 *)ie + le16_to_cpu(ie->length) - sizeof(VCN)); +} + +/** + * Get the subnode vcn to which the index entry refers. + */ +VCN ntfs_ie_get_vcn(INDEX_ENTRY *ie) +{ + return sle64_to_cpup(ntfs_ie_get_vcn_addr(ie)); +} + +static INDEX_ENTRY *ntfs_ie_get_first(INDEX_HEADER *ih) +{ + return (INDEX_ENTRY *)((u8 *)ih + le32_to_cpu(ih->entries_offset)); +} + +static INDEX_ENTRY *ntfs_ie_get_next(INDEX_ENTRY *ie) +{ + return (INDEX_ENTRY *)((char *)ie + le16_to_cpu(ie->length)); +} + +static u8 *ntfs_ie_get_end(INDEX_HEADER *ih) +{ + /* FIXME: check if it isn't overflowing the index block size */ + return (u8 *)ih + le32_to_cpu(ih->index_length); +} + +static int ntfs_ie_end(INDEX_ENTRY *ie) +{ + return ie->flags & INDEX_ENTRY_END; +} + +/** + * Find the last entry in the index block + */ +static INDEX_ENTRY *ntfs_ie_get_last(INDEX_ENTRY *ie, char *ies_end) +{ + ntfs_log_trace("Entering.\n"); + + while ((char *)ie < ies_end && !ntfs_ie_end(ie)) + ie = ntfs_ie_get_next(ie); + return ie; +} + +static INDEX_ENTRY *ntfs_ie_get_by_pos(INDEX_HEADER *ih, int pos) +{ + ntfs_log_trace("pos: %d\n", pos); + + INDEX_ENTRY *ie = ntfs_ie_get_first(ih); + + while (pos-- > 0) + ie = ntfs_ie_get_next(ie); + return ie; +} + +static INDEX_ENTRY *ntfs_ie_prev(INDEX_HEADER *ih, INDEX_ENTRY *ie) +{ + ntfs_log_trace("Entering.\n"); + + INDEX_ENTRY *ie_prev = NULL; + INDEX_ENTRY *tmp = ntfs_ie_get_first(ih); + + while (tmp != ie) { + ie_prev = tmp; + tmp = ntfs_ie_get_next(tmp); + } + return ie_prev; +} + +char *ntfs_ie_filename_get(INDEX_ENTRY *ie) +{ + FILE_NAME_ATTR *fn; + char *name = NULL; + int name_len; + + fn = (FILE_NAME_ATTR *)&ie->key; + name_len = ntfs_ucstombs(fn->file_name, fn->file_name_length, &name, 0); + if (name_len < 0) { + ntfs_log_perror("ntfs_ucstombs"); + return NULL; + } else if (name_len > 0) + return name; + free(name); + return NULL; +} + +void ntfs_ie_filename_dump(INDEX_ENTRY *ie) +{ + char *s; + + s = ntfs_ie_filename_get(ie); + ntfs_log_debug("'%s' ", s); + free(s); +} + +void ntfs_ih_filename_dump(INDEX_HEADER *ih) +{ + INDEX_ENTRY *ie; + + ntfs_log_trace("Entering.\n"); + + ie = ntfs_ie_get_first(ih); + while (!ntfs_ie_end(ie)) { + ntfs_ie_filename_dump(ie); + ie = ntfs_ie_get_next(ie); + } +} + +static int ntfs_ih_numof_entries(INDEX_HEADER *ih) +{ + int n; + INDEX_ENTRY *ie; + + ntfs_log_trace("Entering.\n"); + + ie = ntfs_ie_get_first(ih); + for (n = 0; !ntfs_ie_end(ie); n++) + ie = ntfs_ie_get_next(ie); + return n; +} + +static int ntfs_ih_one_entry(INDEX_HEADER *ih) +{ + return (ntfs_ih_numof_entries(ih) == 1); +} + +static int ntfs_ih_zero_entry(INDEX_HEADER *ih) +{ + return (ntfs_ih_numof_entries(ih) == 0); +} + +static void ntfs_ie_delete(INDEX_HEADER *ih, INDEX_ENTRY *ie) +{ + u32 new_size; + + ntfs_log_trace("Entering.\n"); + + new_size = le32_to_cpu(ih->index_length) - le16_to_cpu(ie->length); + ih->index_length = cpu_to_le32(new_size); + memmove(ie, (u8 *)ie + le16_to_cpu(ie->length), + new_size - ((u8 *)ie - (u8 *)ih)); +} + +static void ntfs_ie_set_vcn(INDEX_ENTRY *ie, VCN vcn) +{ + *ntfs_ie_get_vcn_addr(ie) = cpu_to_le64(vcn); +} + +/** + * Insert @ie index entry at @pos entry. Used @ih values should be ok already. + */ +static void ntfs_ie_insert(INDEX_HEADER *ih, INDEX_ENTRY *ie, INDEX_ENTRY *pos) +{ + int ie_size = le16_to_cpu(ie->length); + + ntfs_log_trace("Entering.\n"); + + ih->index_length = cpu_to_le32(le32_to_cpu(ih->index_length) + ie_size); + memmove((u8 *)pos + ie_size, pos, + le32_to_cpu(ih->index_length) - ((u8 *)pos - (u8 *)ih) - + ie_size); + memcpy(pos, ie, ie_size); +} + +static INDEX_ENTRY *ntfs_ie_dup(INDEX_ENTRY *ie) +{ + INDEX_ENTRY *dup; + + ntfs_log_trace("Entering.\n"); + + dup = ntfs_malloc(ie->length); + if (dup) + memcpy(dup, ie, ie->length); + return dup; +} + +static INDEX_ENTRY *ntfs_ie_dup_novcn(INDEX_ENTRY *ie) +{ + INDEX_ENTRY *dup; + int size = ie->length; + + ntfs_log_trace("Entering.\n"); + + if (ie->flags & INDEX_ENTRY_NODE) + size -= sizeof(VCN); + + dup = ntfs_malloc(size); + if (dup) + memcpy(dup, ie, size); + + dup->flags &= ~INDEX_ENTRY_NODE; + dup->length = size; + return dup; +} + +static int ntfs_ia_check(ntfs_index_context *icx, INDEX_BLOCK *ib, VCN vcn) +{ + u32 ib_size = (unsigned)le32_to_cpu(ib->index.allocated_size) + 0x18; + + ntfs_log_trace("Entering.\n"); + + if (!ntfs_is_indx_record(ib->magic)) { + + ntfs_log_error("Corrupt index block signature: vcn %lld inode " + "%llu\n", (long long)vcn, icx->ni->mft_no); + return -1; + } + + if (sle64_to_cpu(ib->index_block_vcn) != vcn) { + + ntfs_log_error("Corrupt index block: VCN (%lld) is different " + "from expected VCN (%lld) in inode %llu\n", + (long long) sle64_to_cpu(ib->index_block_vcn), + (long long)vcn, icx->ni->mft_no); + return -1; + } + + if (ib_size != icx->block_size) { + + ntfs_log_error("Corrupt index block : VCN (%lld) of inode %llu " + "has a size (%u) differing from the index " + "specified size (%u)\n", (long long)vcn, + icx->ni->mft_no, ib_size, icx->block_size); + return -1; + } + return 0; +} + +static INDEX_ROOT *ntfs_ir_lookup(ntfs_inode *ni, ntfschar *name, + u32 name_len, ntfs_attr_search_ctx **ctx) +{ + ATTR_RECORD *a; + INDEX_ROOT *ir = NULL; + + ntfs_log_trace("Entering.\n"); + + *ctx = ntfs_attr_get_search_ctx(ni, NULL); + if (!*ctx) { + ntfs_log_perror("Failed to get $INDEX_ROOT search context"); + return NULL; + } + + if (ntfs_attr_lookup(AT_INDEX_ROOT, name, name_len, CASE_SENSITIVE, + 0, NULL, 0, *ctx)) { + ntfs_log_perror("Failed to lookup $INDEX_ROOT"); + goto err_out; + } + + a = (*ctx)->attr; + if (a->non_resident) { + errno = EINVAL; + ntfs_log_perror("Non-resident $INDEX_ROOT detected"); + goto err_out; + } + + ir = (INDEX_ROOT *)((char *)a + le16_to_cpu(a->value_offset)); +err_out: + if (!ir) + ntfs_attr_put_search_ctx(*ctx); + return ir; +} + +/** + * Find a key in the index block. + * + * Return values: + * STATUS_OK with errno set to ESUCCESS if we know for sure that the + * entry exists and @ie_out points to this entry. + * STATUS_NOT_FOUND with errno set to ENOENT if we know for sure the + * entry doesn't exist and @ie_out is the insertion point. + * STATUS_KEEP_SEARCHING if we can't answer the above question and + * @vcn will contain the node index block. + * STATUS_ERROR with errno set if on unexpected error during lookup. + */ +static int ntfs_ie_lookup(const void *key, const int key_len, + ntfs_index_context *icx, INDEX_HEADER *ih, + VCN *vcn, INDEX_ENTRY **ie_out) +{ + INDEX_ENTRY *ie; + u8 *index_end; + int rc, item = 0; + + ntfs_log_trace("Entering.\n"); + + index_end = ntfs_ie_get_end(ih); + + /* + * Loop until we exceed valid memory (corruption case) or until we + * reach the last entry. + */ + for (ie = ntfs_ie_get_first(ih); ; ie = ntfs_ie_get_next(ie)) { + /* Bounds checks. */ + if ((u8 *)ie + sizeof(INDEX_ENTRY_HEADER) > index_end || + (u8 *)ie + le16_to_cpu(ie->length) > index_end) { + errno = ERANGE; + ntfs_log_error("Index entry out of bounds in inode " + "%llu.\n", icx->ni->mft_no); + return STATUS_ERROR; + } + /* + * The last entry cannot contain a key. It can however contain + * a pointer to a child node in the B+tree so we just break out. + */ + if (ntfs_ie_end(ie)) + break; + /* + * Not a perfect match, need to do full blown collation so we + * know which way in the B+tree we have to go. + */ + rc = ntfs_collate(icx->ni->vol, icx->cr, key, key_len, &ie->key, + le16_to_cpu(ie->key_length)); + if (rc == NTFS_COLLATION_ERROR) { + ntfs_log_error("Collation error. Perhaps a filename " + "contains invalid characters?\n"); + errno = ERANGE; + return STATUS_ERROR; + } + /* + * If @key collates before the key of the current entry, there + * is definitely no such key in this index but we might need to + * descend into the B+tree so we just break out of the loop. + */ + if (rc == -1) + break; + + if (!rc) { + *ie_out = ie; + errno = 0; + icx->parent_pos[icx->pindex] = item; + return STATUS_OK; + } + + item++; + } + /* + * We have finished with this index block without success. Check for the + * presence of a child node and if not present return with errno ENOENT, + * otherwise we will keep searching in another index block. + */ + if (!(ie->flags & INDEX_ENTRY_NODE)) { + ntfs_log_debug("Index entry wasn't found.\n"); + *ie_out = ie; + errno = ENOENT; + return STATUS_NOT_FOUND; + } + + /* Get the starting vcn of the index_block holding the child node. */ + *vcn = ntfs_ie_get_vcn(ie); + if (*vcn < 0) { + errno = EINVAL; + ntfs_log_perror("Negative vcn in inode %llu\n", + icx->ni->mft_no); + return STATUS_ERROR; + } + + ntfs_log_trace("Parent entry number %d\n", item); + icx->parent_pos[icx->pindex] = item; + return STATUS_KEEP_SEARCHING; +} + +static ntfs_attr *ntfs_ia_open(ntfs_index_context *icx, ntfs_inode *ni) +{ + ntfs_attr *na; + + na = ntfs_attr_open(ni, AT_INDEX_ALLOCATION, icx->name, icx->name_len); + if (!na) { + ntfs_log_perror("Failed to open index allocation of inode " + "%llu", ni->mft_no); + return NULL; + } + return na; +} + +static int ntfs_ib_read(ntfs_index_context *icx, VCN vcn, INDEX_BLOCK *dst) +{ + s64 pos, ret; + + ntfs_log_trace("vcn: %lld\n", vcn); + + pos = ntfs_ib_vcn_to_pos(icx, vcn); + + ret = ntfs_attr_mst_pread(icx->ia_na, pos, 1, icx->block_size, + (u8 *)dst); + if (ret != 1) { + if (ret == -1) + ntfs_log_perror("Failed to read index block"); + else + ntfs_log_error("Failed to read full index block at " + "%lld\n", pos); + return -1; + } + + if (ntfs_ia_check(icx, dst, vcn)) + return -1; + return 0; +} + +static int ntfs_icx_parent_inc(ntfs_index_context *icx) +{ + icx->pindex++; + if (icx->pindex >= MAX_PARENT_VCN) { + errno = EOPNOTSUPP; + ntfs_log_perror("Index is over %d level deep", MAX_PARENT_VCN); + return STATUS_ERROR; + } + return STATUS_OK; +} + +static int ntfs_icx_parent_dec(ntfs_index_context *icx) +{ + icx->pindex--; + if (icx->pindex < 0) { + errno = EINVAL; + ntfs_log_perror("Corrupt index pointer (%d)", icx->pindex); + return STATUS_ERROR; + } + return STATUS_OK; +} + /** * ntfs_index_lookup - find a key in an index and return its index entry * @key: [IN] key for which to search in the index * @key_len: [IN] length of @key in bytes - * @ictx: [IN/OUT] context describing the index and the returned entry + * @icx: [IN/OUT] context describing the index and the returned entry * - * Before calling ntfs_index_lookup(), @ictx must have been obtained from a + * Before calling ntfs_index_lookup(), @icx must have been obtained from a * call to ntfs_index_ctx_get(). * - * Look for the @key in the index specified by the index lookup context @ictx. + * Look for the @key in the index specified by the index lookup context @icx. * ntfs_index_lookup() walks the contents of the index looking for the @key. * - * If the @key is found in the index, 0 is returned and @ictx is setup to - * describe the index entry containing the matching @key. @ictx->entry is the - * index entry and @ictx->data and @ictx->data_len are the index entry data and + * If the @key is found in the index, 0 is returned and @icx is setup to + * describe the index entry containing the matching @key. @icx->entry is the + * index entry and @icx->data and @icx->data_len are the index entry data and * its length in bytes, respectively. * * If the @key is not found in the index, -1 is returned, errno = ENOENT and - * @ictx is setup to describe the index entry whose key collates immediately + * @icx is setup to describe the index entry whose key collates immediately * after the search @key, i.e. this is the position in the index at which * an index entry with a key of @key would need to be inserted. * - * If an error occurs return -1, set errno to error code and @ictx is left + * If an error occurs return -1, set errno to error code and @icx is left * untouched. * * When finished with the entry and its data, call ntfs_index_ctx_put() to free @@ -172,297 +624,794 @@ void ntfs_index_ctx_reinit(ntfs_index_context *ictx) * to disk. */ int ntfs_index_lookup(const void *key, const int key_len, - ntfs_index_context *ictx) + ntfs_index_context *icx) { - COLLATION_RULES cr; - VCN vcn; - ntfs_inode *ni = ictx->ni; - ntfs_volume *vol = ni->vol; + VCN old_vcn, vcn; + ntfs_inode *ni = icx->ni; INDEX_ROOT *ir; INDEX_ENTRY *ie; - INDEX_ALLOCATION *ia = NULL; - u8 *index_end; + INDEX_BLOCK *ib = NULL; ntfs_attr_search_ctx *actx; - ntfs_attr *na = NULL; - int rc, err = 0; + int ret, err = 0; ntfs_log_trace("Entering.\n"); + if (!key || key_len <= 0) { errno = EINVAL; + ntfs_log_perror("key: %p key_len: %d", key, key_len); return -1; } - actx = ntfs_attr_get_search_ctx(ni, NULL); - if (!actx) { - err = ENOMEM; - goto err_out; + ir = ntfs_ir_lookup(ni, icx->name, icx->name_len, &actx); + if (!ir) { + if (errno == ENOENT) + errno = EIO; + return -1; + } + + icx->block_size = le32_to_cpu(ir->index_block_size); + if (icx->block_size < NTFS_BLOCK_SIZE) { + errno = EINVAL; + ntfs_log_perror("Index block size (%d) is smaller than the " + "sector size (%d)", icx->block_size, + NTFS_BLOCK_SIZE); + return -1; } - /* Find the index root attribute in the mft record. */ - err = ntfs_attr_lookup(AT_INDEX_ROOT, ictx->name, ictx->name_len, - CASE_SENSITIVE, 0, NULL, 0, actx); - if (err) { - if (errno == ENOENT) { - ntfs_log_error("Index root attribute missing in inode " - "0x%llx.\n", ni->mft_no); - err = EIO; - } else - err = errno; + if (ni->vol->cluster_size <= icx->block_size) + icx->vcn_size_bits = ni->vol->cluster_size_bits; + else + icx->vcn_size_bits = ni->vol->sector_size_bits; + + icx->cr = ir->collation_rule; + if (!ntfs_is_collation_rule_supported(icx->cr)) { + err = errno = EOPNOTSUPP; + ntfs_log_perror("Unknown collation rule 0x%x", + (unsigned)le32_to_cpu(icx->cr)); goto err_out; } - /* Get to the index root value (it has been verified in read_inode). */ - ir = (INDEX_ROOT*)((u8*)actx->attr + - le16_to_cpu(actx->attr->value_offset)); - index_end = (u8*)&ir->index + le32_to_cpu(ir->index.index_length); - /* Save index block size for future use. */ - ictx->block_size = le32_to_cpu(ir->index_block_size); - /* Determine the size of a vcn in the directory index. */ - if (vol->cluster_size <= ictx->block_size) { - ictx->vcn_size = vol->cluster_size; - ictx->vcn_size_bits = vol->cluster_size_bits; - } else { - ictx->vcn_size = vol->sector_size; - ictx->vcn_size_bits = vol->sector_size_bits; - } - /* Get collation rule type and validate it. */ - cr = ir->collation_rule; - if (!ntfs_is_collation_rule_supported(cr)) { - ntfs_log_error("Index uses unsupported collation rule 0x%x. " - "Aborting lookup.\n", (unsigned)le32_to_cpu(cr)); - err = EOPNOTSUPP; - goto err_out; - } - /* The first index entry. */ - ie = (INDEX_ENTRY*)((u8*)&ir->index + - le32_to_cpu(ir->index.entries_offset)); - /* - * Loop until we exceed valid memory (corruption case) or until we - * reach the last entry. + + old_vcn = VCN_INDEX_ROOT_PARENT; + /* + * FIXME: check for both ir and ib that the first index entry is + * within the index block. */ - for (;; ie = (INDEX_ENTRY*)((u8*)ie + le16_to_cpu(ie->length))) { - /* Bounds checks. */ - if ((u8*)ie < (u8*)actx->mrec || (u8*)ie + - sizeof(INDEX_ENTRY_HEADER) > index_end || - (u8*)ie + le16_to_cpu(ie->length) > index_end) - goto idx_err_out; - /* - * The last entry cannot contain a key. It can however contain - * a pointer to a child node in the B+tree so we just break out. - */ - if (ie->flags & INDEX_ENTRY_END) - break; - /* If the keys match perfectly, we setup @ictx and return 0. */ - if ((key_len == le16_to_cpu(ie->key_length)) && !memcmp(key, - &ie->key, key_len)) { -ir_done: - ictx->is_in_root = TRUE; - ictx->actx = actx; - ictx->ia = NULL; - ictx->ir = ir; -done: - ictx->entry = ie; - ictx->data = (u8*)ie + offsetof(INDEX_ENTRY, key); - ictx->data_len = le16_to_cpu(ie->key_length); - ntfs_log_trace("Done.\n"); - if (err) { - errno = err; - return -1; - } - return 0; - } - /* - * Not a perfect match, need to do full blown collation so we - * know which way in the B+tree we have to go. - */ - rc = ntfs_collate(vol, cr, key, key_len, &ie->key, - le16_to_cpu(ie->key_length)); - if (rc == NTFS_COLLATION_ERROR) { - ntfs_log_error("Collation error. Probably filename " - "contain invalid characters.\n"); - err = ERANGE; - goto err_out; - } - /* - * If @key collates before the key of the current entry, there - * is definitely no such key in this index but we might need to - * descend into the B+tree so we just break out of the loop. - */ - if (rc == -1) - break; - /* - * A match should never happen as the memcmp() call should have - * caught it, but we still treat it correctly. - */ - if (!rc) - goto ir_done; - /* The keys are not equal, continue the search. */ - } - /* - * We have finished with this index without success. Check for the - * presence of a child node and if not present setup @ictx and return - * -1 with errno ENOENT. - */ - if (!(ie->flags & INDEX_ENTRY_NODE)) { - ntfs_log_debug("Entry not found.\n"); - err = ENOENT; - goto ir_done; - } /* Child node present, descend into it. */ - /* Get the starting vcn of the index_block holding the child node. */ - vcn = sle64_to_cpup((sle64*)((u8*)ie + le16_to_cpu(ie->length) - 8)); - /* We are done with the index root. Release attribute search ctx. */ - ntfs_attr_put_search_ctx(actx); - actx = NULL; - /* Open INDEX_ALLOCATION. */ - na = ntfs_attr_open(ni, AT_INDEX_ALLOCATION, - ictx->name, ictx->name_len); - if (!na) { - ntfs_log_error("No index allocation attribute but index entry " - "requires one. Inode 0x%llx is corrupt or " - "library bug.\n", ni->mft_no); + ret = ntfs_ie_lookup(key, key_len, icx, &ir->index, &vcn, &ie); + if (ret == STATUS_ERROR) { + err = errno; goto err_out; } - /* Allocate memory to store index block. */ - ia = malloc(ictx->block_size); - if (!ia) { - ntfs_log_error("Not enough memory to allocate buffer for index" - " allocation.\n"); - err = ENOMEM; - goto err_out; - } -descend_into_child_node: - ntfs_log_debug("Descend into node with VCN %lld.\n", vcn); - /* Read index allocation block. */ - if (ntfs_attr_mst_pread(na, vcn << ictx->vcn_size_bits, 1, - ictx->block_size, ia) != 1) { - ntfs_log_error("Failed to read index allocation.\n"); - goto err_out; - } - /* Catch multi sector transfer fixup errors. */ - if (!ntfs_is_indx_record(ia->magic)) { - ntfs_log_error("Index record with vcn 0x%llx is corrupt. " - "Corrupt inode 0x%llx. Run chkdsk.\n", - (long long)vcn, ni->mft_no); - goto err_out; - } - if (sle64_to_cpu(ia->index_block_vcn) != vcn) { - ntfs_log_error("Actual VCN (0x%llx) of index buffer is " - "different from expected VCN (0x%llx). Inode " - "0x%llx is corrupt or driver bug.\n", - (unsigned long long) - sle64_to_cpu(ia->index_block_vcn), - (unsigned long long)vcn, ni->mft_no); - goto err_out; - } - if (le32_to_cpu(ia->index.allocated_size) + 0x18 != ictx->block_size) { - ntfs_log_error("Index buffer (VCN 0x%llx) of inode 0x%llx has " - "a size (%u) differing from the index " - "specified size (%u). Inode is corrupt or " - "driver bug.\n", (unsigned long long)vcn, - ni->mft_no, (unsigned) - le32_to_cpu(ia->index.allocated_size) + 0x18, - (unsigned)ictx->block_size); - goto err_out; - } - index_end = (u8*)&ia->index + le32_to_cpu(ia->index.index_length); - if (index_end > (u8*)ia + ictx->block_size) { - ntfs_log_error("Size of index buffer (VCN 0x%llx) of inode " - "0x%llx exceeds maximum size.\n", - (unsigned long long)vcn, ni->mft_no); - goto err_out; - } - /* The first index entry. */ - ie = (INDEX_ENTRY*)((u8*)&ia->index + - le32_to_cpu(ia->index.entries_offset)); - /* - * Iterate similar to above big loop but applied to index buffer, thus - * loop until we exceed valid memory (corruption case) or until we - * reach the last entry. - */ - for (;; ie = (INDEX_ENTRY*)((u8*)ie + le16_to_cpu(ie->length))) { - /* Bounds checks. */ - if ((u8*)ie < (u8*)ia || (u8*)ie + - sizeof(INDEX_ENTRY_HEADER) > index_end || - (u8*)ie + le16_to_cpu(ie->length) > index_end) { - ntfs_log_error("Index entry out of bounds in inode " - "0x%llx.\n", ni->mft_no); - goto err_out; - } - /* - * The last entry cannot contain a key. It can however contain - * a pointer to a child node in the B+tree so we just break out. - */ - if (ie->flags & INDEX_ENTRY_END) - break; - /* If the keys match perfectly, we setup @ictx and return 0. */ - if ((key_len == le16_to_cpu(ie->key_length)) && !memcmp(key, - &ie->key, key_len)) { -ia_done: - ictx->is_in_root = FALSE; - ictx->actx = NULL; - ictx->ia = ia; - ictx->ia_vcn = vcn; - ictx->ia_na = na; - goto done; - } - /* - * Not a perfect match, need to do full blown collation so we - * know which way in the B+tree we have to go. - */ - rc = ntfs_collate(vol, cr, key, key_len, &ie->key, - le16_to_cpu(ie->key_length)); - if (rc == NTFS_COLLATION_ERROR) { - ntfs_log_error("Collation error. Probably filename " - "contain invalid characters.\n"); - err = ERANGE; - goto err_out; - } - /* - * If @key collates before the key of the current entry, there - * is definitely no such key in this index but we might need to - * descend into the B+tree so we just break out of the loop. - */ - if (rc == -1) - break; - /* - * A match should never happen as the memcmp() call should have - * caught it, but we still treat it correctly. - */ - if (!rc) - goto ia_done; - /* The keys are not equal, continue the search. */ - } - /* - * We have finished with this index buffer without success. Check for - * the presence of a child node and if not present return ENOENT. - */ - if (!(ie->flags & INDEX_ENTRY_NODE)) { - ntfs_log_debug("Entry not found.\n"); - err = ENOENT; - goto ia_done; - } - if ((ia->index.flags & NODE_MASK) == LEAF_NODE) { - ntfs_log_error("Index entry with child node found in a leaf " - "node in inode 0x%llx.\n", ni->mft_no); - goto err_out; + + icx->actx = actx; + icx->ir = ir; + + if (ret != STATUS_KEEP_SEARCHING) { + /* STATUS_OK or STATUS_NOT_FOUND */ + err = errno; + icx->is_in_root = TRUE; + icx->parent_vcn[icx->pindex] = old_vcn; + goto done; } + /* Child node present, descend into it. */ - vcn = sle64_to_cpup((sle64*)((u8*)ie + le16_to_cpu(ie->length) - 8)); - if (vcn >= 0) - goto descend_into_child_node; - ntfs_log_error("Negative child node vcn in inode 0x%llx.\n", ni->mft_no); + icx->ia_na = ntfs_ia_open(icx, ni); + if (!icx->ia_na) + goto err_out; + + ib = ntfs_malloc(icx->block_size); + if (!ib) { + err = errno; + goto err_out; + } + +descend_into_child_node: + icx->parent_vcn[icx->pindex] = old_vcn; + if (ntfs_icx_parent_inc(icx)) { + err = errno; + goto err_out; + } + old_vcn = vcn; + + ntfs_log_debug("Descend into node with VCN %lld.\n", vcn); + + if (ntfs_ib_read(icx, vcn, ib)) + goto err_out; + + ret = ntfs_ie_lookup(key, key_len, icx, &ib->index, &vcn, &ie); + if (ret != STATUS_KEEP_SEARCHING) { + err = errno; + if (ret == STATUS_ERROR) + goto err_out; + + /* STATUS_OK or STATUS_NOT_FOUND */ + icx->is_in_root = FALSE; + icx->ib = ib; + icx->parent_vcn[icx->pindex] = icx->ib_vcn = vcn; + goto done; + } + + if ((ib->index.flags & NODE_MASK) == LEAF_NODE) { + ntfs_log_error("Index entry with child node found in a leaf " + "node in inode 0x%llx.\n", ni->mft_no); + goto err_out; + } + + goto descend_into_child_node; err_out: - if (na) - ntfs_attr_close(na); - free(ia); + if (icx->ia_na) { + ntfs_attr_close(icx->ia_na); + icx->ia_na = NULL; + } + free(ib); if (!err) err = EIO; if (actx) ntfs_attr_put_search_ctx(actx); errno = err; return -1; -idx_err_out: - ntfs_log_error("Corrupt index. Aborting lookup.\n"); - goto err_out; +done: + icx->entry = ie; + icx->data = (u8 *)ie + offsetof(INDEX_ENTRY, key); + icx->data_len = le16_to_cpu(ie->key_length); + icx->max_depth = icx->pindex; + ntfs_log_trace("Done.\n"); + if (err) { + errno = err; + return -1; + } + return 0; +} + +static INDEX_BLOCK *ntfs_ib_alloc(VCN ib_vcn, int ib_size, + INDEX_HEADER_FLAGS node_type) +{ + INDEX_BLOCK *ib; + int ih_size = sizeof(INDEX_HEADER); + + ntfs_log_trace("Entering ib_vcn = %lld ib_size = %d\n", ib_vcn, + ib_size); + + ib = ntfs_calloc(ib_size); + if (!ib) + return NULL; + + ib->magic = magic_INDX; + ib->usa_ofs = cpu_to_le16(sizeof(INDEX_BLOCK)); + ib->usa_count = cpu_to_le16(ib_size / NTFS_BLOCK_SIZE + 1); + /* Set USN to 1 */ + *(u16 *)((char *)ib + le16_to_cpu(ib->usa_ofs)) = cpu_to_le16(1); + ib->lsn = cpu_to_le64(0); + + ib->index_block_vcn = cpu_to_sle64(ib_vcn); + + ib->index.entries_offset = cpu_to_le32((ih_size + + le16_to_cpu(ib->usa_count) * 2 + 7) & ~7); + ib->index.index_length = 0; + ib->index.allocated_size = cpu_to_le32(ib_size - + (sizeof(INDEX_BLOCK) - ih_size)); + ib->index.flags = node_type; + return ib; +} + +/** + * Find the median by going through all the entries + */ +static INDEX_ENTRY *ntfs_ie_get_median(INDEX_HEADER *ih) +{ + INDEX_ENTRY *ie, *ie_start; + u8 *ie_end; + int i = 0, median; + + ntfs_log_trace("Entering.\n"); + + ie = ie_start = ntfs_ie_get_first(ih); + ie_end = (u8 *)ntfs_ie_get_end(ih); + + while ((u8 *)ie < ie_end && !ntfs_ie_end(ie)) { + ie = ntfs_ie_get_next(ie); + i++; + } + /* + * NOTE: this could be also the entry at the half of the index block. + */ + median = i / 2 - 1; + + ntfs_log_trace("Entries: %d median: %d\n", i, median); + + for (i = 0, ie = ie_start; i <= median; i++) + ie = ntfs_ie_get_next(ie); + + return ie; +} + +static s64 ntfs_ibm_vcn_to_pos(ntfs_index_context *icx, VCN vcn) +{ + return ntfs_ib_vcn_to_pos(icx, vcn) / icx->block_size; +} + +static s64 ntfs_ibm_pos_to_vcn(ntfs_index_context *icx, s64 pos) +{ + return ntfs_ib_pos_to_vcn(icx, pos * icx->block_size); +} + +static int ntfs_ibm_add(ntfs_index_context *icx) +{ + u8 bmp[8]; + + ntfs_log_trace("Entering.\n"); + + if (ntfs_attr_exist(icx->ni, AT_BITMAP, icx->name, icx->name_len)) + return STATUS_OK; + /* + * AT_BITMAP must be at least 8 bytes. + */ + memset(bmp, 0, sizeof(bmp)); + if (ntfs_attr_add(icx->ni, AT_BITMAP, icx->name, icx->name_len, + bmp, sizeof(bmp))) { + ntfs_log_perror("Failed to add AT_BITMAP"); + return STATUS_ERROR; + } + return STATUS_OK; +} + +static int ntfs_ibm_modify(ntfs_index_context *icx, VCN vcn, int set) +{ + u8 byte; + s64 pos = ntfs_ibm_vcn_to_pos(icx, vcn); + u32 bpos = pos / 8; + u32 bit = 1 << (pos % 8); + ntfs_attr *na; + int ret = STATUS_ERROR; + + ntfs_log_trace("%s vcn: %lld\n", set ? "set" : "clear", vcn); + + na = ntfs_attr_open(icx->ni, AT_BITMAP, icx->name, icx->name_len); + if (!na) { + ntfs_log_perror("Failed to open $BITMAP attribute"); + return -1; + } + + if (set) { + if (na->data_size < bpos + 1) { + if (ntfs_attr_truncate(na, (na->data_size + 8) & ~7)) { + ntfs_log_perror("Failed to truncate AT_BITMAP"); + goto err_na; + } + } + } + + if (ntfs_attr_pread(na, bpos, 1, &byte) != 1) { + ntfs_log_perror("Failed to read $BITMAP"); + goto err_na; + } + + if (set) + byte |= bit; + else + byte &= ~bit; + + if (ntfs_attr_pwrite(na, bpos, 1, &byte) != 1) { + ntfs_log_perror("Failed to write $Bitmap"); + goto err_na; + } + + ret = STATUS_OK; +err_na: + ntfs_attr_close(na); + return ret; +} + + +static int ntfs_ibm_set(ntfs_index_context *icx, VCN vcn) +{ + return ntfs_ibm_modify(icx, vcn, 1); +} + +static int ntfs_ibm_clear(ntfs_index_context *icx, VCN vcn) +{ + return ntfs_ibm_modify(icx, vcn, 0); +} + +static VCN ntfs_ibm_get_free(ntfs_index_context *icx) +{ + u8 *bm; + int bit; + s64 vcn, byte, size; + + ntfs_log_trace("Entering.\n"); + + bm = ntfs_attr_readall(icx->ni, AT_BITMAP, icx->name, icx->name_len, + &size); + if (!bm) + return (VCN)-1; + + for (byte = 0; byte < size; byte++) { + + if (bm[byte] == 255) + continue; + + for (bit = 0; bit < 8; bit++) { + if (!(bm[byte] & (1 << bit))) { + vcn = ntfs_ibm_pos_to_vcn(icx, byte * 8 + bit); + goto out; + } + } + } + + vcn = ntfs_ibm_pos_to_vcn(icx, size * 8); +out: + ntfs_log_trace("allocated vcn: %lld\n", vcn); + + if (ntfs_ibm_set(icx, vcn)) + vcn = (VCN)-1; + + free(bm); + return vcn; +} + +static INDEX_BLOCK *ntfs_ir_to_ia(INDEX_ROOT *ir, VCN ib_vcn) +{ + INDEX_BLOCK *ib; + INDEX_ENTRY *ie_last; + char *ies_start, *ies_end; + int i; + + ntfs_log_trace("Entering.\n"); + + ib = ntfs_ib_alloc(ib_vcn, ir->index_block_size, LEAF_NODE); + if (!ib) + return NULL; + + ies_start = (char *)ntfs_ie_get_first(&ir->index); + ies_end = (char *)ntfs_ie_get_end(&ir->index); + + ie_last = ntfs_ie_get_last((INDEX_ENTRY *)ies_start, ies_end); + /* + * Copy all entries, including the termination entry + * as well, which can never have any data. + */ + i = (char *)ie_last - ies_start + le16_to_cpu(ie_last->length); + memcpy(ntfs_ie_get_first(&ib->index), ies_start, i); + + ib->index.flags = ir->index.flags; + ib->index.index_length = cpu_to_le32(i + + le32_to_cpu(ib->index.entries_offset)); + /* + * Move the index root termination entry forward + */ + if ((char *)ie_last > ies_start) { + memmove(ies_start, (char *)ie_last, le16_to_cpu( + ie_last->length)); + ie_last = (INDEX_ENTRY *)ies_start; + } + return ib; +} + +static int ntfs_ib_copy_tail(ntfs_index_context *icx, INDEX_BLOCK *src, + INDEX_BLOCK *dst, INDEX_ENTRY *median, VCN new_vcn) +{ + u8 *ies_end; + INDEX_ENTRY *ie_head; /* first entry after the median */ + int tail_size; + + ntfs_log_trace("Entering.\n"); + + ie_head = ntfs_ie_get_next(median); + + ies_end = (u8 *)ntfs_ie_get_end(&src->index); + tail_size = ies_end - (u8 *)ie_head; + memcpy(ntfs_ie_get_first(&dst->index), ie_head, tail_size); + + dst->index.index_length = cpu_to_le32(tail_size + + le32_to_cpu(dst->index.entries_offset)); + + if (ntfs_ib_write(icx, new_vcn, dst)) + return STATUS_ERROR; + + return STATUS_OK; +} + +static int ntfs_ib_cut_tail(ntfs_index_context *icx, INDEX_BLOCK *src, + INDEX_ENTRY *ie) +{ + char *ies_start, *ies_end; + INDEX_ENTRY *ie_last; + + ntfs_log_trace("Entering.\n"); + + ies_start = (char *)ntfs_ie_get_first(&src->index); + ies_end = (char *)ntfs_ie_get_end(&src->index); + + ie_last = ntfs_ie_get_last((INDEX_ENTRY *)ies_start, ies_end); + if (ie_last->flags & INDEX_ENTRY_NODE) + ntfs_ie_set_vcn(ie_last, ntfs_ie_get_vcn(ie)); + + memcpy(ie, ie_last, ie_last->length); + + src->index.index_length = cpu_to_le32(((char *)ie - ies_start) + + ie->length + le32_to_cpu(src->index.entries_offset)); + + if (ntfs_ib_write(icx, icx->parent_vcn[icx->pindex + 1], src)) + return STATUS_ERROR; + + return STATUS_OK; +} + +static int ntfs_ia_add(ntfs_index_context *icx) +{ + ntfs_log_trace("Entering.\n"); + + if (ntfs_ibm_add(icx)) + return -1; + + if (!ntfs_attr_exist(icx->ni, AT_INDEX_ALLOCATION, icx->name, + icx->name_len)) { + if (ntfs_attr_add(icx->ni, AT_INDEX_ALLOCATION, icx->name, + icx->name_len, NULL, 0)) { + ntfs_log_perror("Failed to add AT_INDEX_ALLOCATION"); + return -1; + } + } + + icx->ia_na = ntfs_ia_open(icx, icx->ni); + if (!icx->ia_na) + return -1; + return 0; +} + +static int ntfs_ir_reparent(ntfs_index_context *icx) +{ + ntfs_attr_search_ctx *ctx; + INDEX_ROOT *ir; + INDEX_ENTRY *ie; + INDEX_BLOCK *ib = NULL; + VCN new_ib_vcn; + int ret = STATUS_ERROR; + + ntfs_log_trace("Entering.\n"); + + if (!(icx->ia_na)) + if (ntfs_ia_add(icx)) + return -1; + + ir = ntfs_ir_lookup(icx->ni, icx->name, icx->name_len, &ctx); + if (!ir) + return -1; + + new_ib_vcn = ntfs_ibm_get_free(icx); + if (new_ib_vcn == -1) + goto err_out; + + ib = ntfs_ir_to_ia(ir, new_ib_vcn); + if (ib == NULL) { + ntfs_log_perror("Failed to create AT_INDEX_ALLOCATION"); + goto err_out; + } + + ie = ntfs_ie_get_first(&ir->index); + ie->flags |= INDEX_ENTRY_NODE; + ie->length = cpu_to_le16(sizeof(INDEX_ENTRY_HEADER) + sizeof(VCN)); + ntfs_ie_set_vcn(ie, new_ib_vcn); + + ir->index.flags = LARGE_INDEX; + ir->index.index_length = cpu_to_le32(le32_to_cpu( + ir->index.entries_offset) + le16_to_cpu(ie->length)); + ir->index.allocated_size = ir->index.index_length; + + if (ntfs_resident_attr_value_resize(ctx->mrec, ctx->attr, + sizeof(INDEX_ROOT) - sizeof(INDEX_HEADER) + + le32_to_cpu(ir->index.allocated_size))) + /* FIXME: revert bitmap, index root */ + goto err_out; + ntfs_inode_mark_dirty(ctx->ntfs_ino); + + if (ntfs_ib_write(icx, new_ib_vcn, ib)) + goto err_out; + + ret = STATUS_OK; +err_out: + ntfs_attr_put_search_ctx(ctx); + free(ib); + return ret; +} + +/** + * ntfs_ir_truncate - Truncate index root attribute + * + * Returns STATUS_OK, STATUS_RESIDENT_ATTRIBUTE_FILLED_MFT or STATUS_ERROR. + */ +static int ntfs_ir_truncate(ntfs_index_context *icx, int data_size) +{ + ntfs_attr *na; + int ret; + + ntfs_log_trace("Entering.\n"); + + na = ntfs_attr_open(icx->ni, AT_INDEX_ROOT, icx->name, icx->name_len); + if (!na) { + ntfs_log_perror("Failed to open INDEX_ROOT"); + return STATUS_ERROR; + } + /* + * INDEX_ROOT must be resident and its entries can be moved to + * INDEX_BLOCK, so ENOSPC isn't a real error. + */ + ret = ntfs_attr_truncate(na, data_size + offsetof(INDEX_ROOT, index)); + if (ret == STATUS_OK) { + ntfs_attr_search_ctx *ctx; + + icx->ir = ntfs_ir_lookup(icx->ni, icx->name, icx->name_len, + &ctx); + if (!icx->ir) + return STATUS_ERROR; + + icx->ir->index.allocated_size = cpu_to_le16(data_size); + + ntfs_attr_put_search_ctx(ctx); + } else { + if (errno != ENOSPC && errno != EOVERFLOW) + ntfs_log_trace("Failed to truncate INDEX_ROOT"); + if (errno == EOVERFLOW) + ret = STATUS_RESIDENT_ATTRIBUTE_FILLED_MFT; + } + + ntfs_attr_close(na); + return ret; +} + +/** + * ntfs_ir_make_space - Make more space for the index root attribute + * + * On success return STATUS_OK or STATUS_KEEP_SEARCHING. + * On error return STATUS_ERROR. + */ +static int ntfs_ir_make_space(ntfs_index_context *icx, int data_size) +{ + int ret; + + ntfs_log_trace("Entering.\n"); + + ret = ntfs_ir_truncate(icx, data_size); + if (ret == STATUS_RESIDENT_ATTRIBUTE_FILLED_MFT) { + ret = ntfs_ir_reparent(icx); + if (ret == STATUS_OK) + ret = STATUS_KEEP_SEARCHING; + else + ntfs_log_perror("Failed to nodify INDEX_ROOT"); + } + return ret; +} + +/* + * NOTE: 'ie' must be a copy of a real index entry. + */ +static int ntfs_ie_add_vcn(INDEX_ENTRY **ie) +{ + INDEX_ENTRY *p, *old = *ie; + + old->length += sizeof(VCN); + p = realloc(old, old->length); + if (!p) + return STATUS_ERROR; + + p->flags |= INDEX_ENTRY_NODE; + *ie = p; + return STATUS_OK; +} + +static int ntfs_ih_insert(INDEX_HEADER *ih, INDEX_ENTRY *orig_ie, VCN new_vcn, + int pos) +{ + INDEX_ENTRY *ie_node, *ie; + int ret = STATUS_ERROR; + VCN old_vcn; + + ntfs_log_trace("Entering.\n"); + + ie = ntfs_ie_dup(orig_ie); + if (!ie) + return STATUS_ERROR; + + if (!(ie->flags & INDEX_ENTRY_NODE)) + if (ntfs_ie_add_vcn(&ie)) + goto out; + + ie_node = ntfs_ie_get_by_pos(ih, pos); + old_vcn = ntfs_ie_get_vcn(ie_node); + ntfs_ie_set_vcn(ie_node, new_vcn); + + ntfs_ie_insert(ih, ie, ie_node); + ntfs_ie_set_vcn(ie_node, old_vcn); + ret = STATUS_OK; +out: + free(ie); + return ret; +} + +static VCN ntfs_icx_parent_vcn(ntfs_index_context *icx) +{ + return icx->parent_vcn[icx->pindex]; +} + +static VCN ntfs_icx_parent_pos(ntfs_index_context *icx) +{ + return icx->parent_pos[icx->pindex]; +} + +static int ntfs_ir_insert_median(ntfs_index_context *icx, INDEX_ENTRY *median, + VCN new_vcn) +{ + u32 new_size; + int ret; + + ntfs_log_trace("Entering.\n"); + + new_size = le16_to_cpu(icx->ir->index.index_length) + median->length; + if (!(median->flags & INDEX_ENTRY_NODE)) + new_size += sizeof(VCN); + + ret = ntfs_ir_make_space(icx, new_size); + if (ret != STATUS_OK) + return ret; + + return ntfs_ih_insert(&icx->ir->index, median, new_vcn, + ntfs_icx_parent_pos(icx)); +} + +static int ntfs_ib_split(ntfs_index_context *icx, INDEX_BLOCK *ib); + +/** + * On success return STATUS_OK or STATUS_KEEP_SEARCHING. + * On error return STATUS_ERROR. + */ +static int ntfs_ib_insert(ntfs_index_context *icx, INDEX_ENTRY *ie, VCN new_vcn) +{ + INDEX_BLOCK *ib; + u32 idx_size, allocated_size; + int err = STATUS_ERROR; + VCN old_vcn; + + ntfs_log_trace("Entering.\n"); + + ib = ntfs_malloc(icx->block_size); + if (!ib) + return -1; + + old_vcn = ntfs_icx_parent_vcn(icx); + + if (ntfs_ib_read(icx, old_vcn, ib)) + goto err_out; + + idx_size = le16_to_cpu(ib->index.index_length); + allocated_size = le16_to_cpu(ib->index.allocated_size); + /* FIXME: sizeof(VCN) should be included only if ie has no VCN */ + if (idx_size + ie->length + sizeof(VCN) > allocated_size) { + err = ntfs_ib_split(icx, ib); + if (err == STATUS_OK) + err = STATUS_KEEP_SEARCHING; + goto err_out; + } + + if (ntfs_ih_insert(&ib->index, ie, new_vcn, ntfs_icx_parent_pos(icx))) + goto err_out; + + if (ntfs_ib_write(icx, old_vcn, ib)) + goto err_out; + + err = STATUS_OK; +err_out: + free(ib); + return err; +} + +/** + * ntfs_ib_split - Split index allocation attribute + * + * On success return STATUS_OK or STATUS_KEEP_SEARCHING. + * On error return is STATUS_ERROR. + */ +static int ntfs_ib_split(ntfs_index_context *icx, INDEX_BLOCK *ib) +{ + INDEX_BLOCK *ib_new; + INDEX_ENTRY *median; + VCN new_vcn; + int ret, err = STATUS_ERROR; + + ntfs_log_trace("Entering.\n"); + + if (ntfs_icx_parent_dec(icx)) + return STATUS_ERROR; + + median = ntfs_ie_get_median(&ib->index); + new_vcn = ntfs_ibm_get_free(icx); + if (new_vcn == -1) + return STATUS_ERROR; + + if (ntfs_icx_parent_vcn(icx) == VCN_INDEX_ROOT_PARENT) + ret = ntfs_ir_insert_median(icx, median, new_vcn); + else + ret = ntfs_ib_insert(icx, median, new_vcn); + + ntfs_inode_mark_dirty(icx->actx->ntfs_ino); + + if (ret != STATUS_OK) { + ntfs_ibm_clear(icx, new_vcn); + return ret; + } + + ib_new = ntfs_ib_alloc(new_vcn, icx->block_size, + ib->index.flags & NODE_MASK); + if (!ib_new) + return STATUS_ERROR; + + if (ntfs_ib_copy_tail(icx, ib, ib_new, median, new_vcn)) + goto free_ib_new; + + if (ntfs_ib_cut_tail(icx, ib, median)) + goto free_ib_new; + + err = STATUS_OK; +free_ib_new: + free(ib_new); + return err; +} + +static int ntfs_ie_add(ntfs_index_context *icx, INDEX_ENTRY *ie) +{ + char *fn; + INDEX_HEADER *ih; + int allocated_size, new_size; + int ret = STATUS_ERROR; + + fn = ntfs_ie_filename_get(ie); + ntfs_log_trace("file: '%s'\n", fn); + + while (1) { + if (!ntfs_index_lookup(&ie->key, le16_to_cpu(ie->key_length), + icx)) { + errno = EEXIST; + ntfs_log_error("Index already have such entry.\n"); + goto err_out; + } + if (errno != ENOENT) { + ntfs_log_perror("Failed to find place for new entry"); + goto err_out; + } + + if (icx->is_in_root) + ih = &icx->ir->index; + else + ih = &icx->ib->index; + + allocated_size = le16_to_cpu(ih->allocated_size); + new_size = le16_to_cpu(ih->index_length) + + le16_to_cpu(ie->length); + + if (new_size <= allocated_size) + break; + + ntfs_log_trace("index block sizes: allocated: %d needed: %d\n", + allocated_size, new_size); + + if (icx->is_in_root) { + if (ntfs_ir_make_space(icx, new_size) == STATUS_ERROR) + goto err_out; + } else { + if (ntfs_ib_split(icx, icx->ib) == STATUS_ERROR) + goto err_out; + } + ntfs_inode_mark_dirty(icx->actx->ntfs_ino); + ntfs_index_ctx_reinit(icx); + } + + ntfs_ie_insert(ih, ie, icx->entry); + ntfs_index_entry_mark_dirty(icx); + + ret = STATUS_OK; +err_out: + free(fn); + ntfs_log_trace("%s\n", ret ? "Failed" : "Done"); + return ret; } /** @@ -471,192 +1420,339 @@ idx_err_out: * @fn: FILE_NAME attribute to add * @mref: reference of the inode which @fn describes * - * NOTE: This function does not support all cases, so it can fail with - * EOPNOTSUPP error code. - * * Return 0 on success or -1 on error with errno set to the error code. */ int ntfs_index_add_filename(ntfs_inode *ni, FILE_NAME_ATTR *fn, MFT_REF mref) { - ntfs_index_context *ictx; INDEX_ENTRY *ie; - INDEX_HEADER *ih; - int err, fn_size, ie_size, allocated_size = 0; + ntfs_index_context *icx; + int fn_size, ie_size, ret = -1; ntfs_log_trace("Entering.\n"); + if (!ni || !fn) { ntfs_log_error("Invalid arguments.\n"); errno = EINVAL; return -1; } - ictx = ntfs_index_ctx_get(ni, NTFS_INDEX_I30, 4); - if (!ictx) - return -1; + fn_size = (fn->file_name_length * sizeof(ntfschar)) + sizeof(FILE_NAME_ATTR); ie_size = (sizeof(INDEX_ENTRY_HEADER) + fn_size + 7) & ~7; -retry: - /* Find place where insert new entry. */ - if (!ntfs_index_lookup(fn, fn_size, ictx)) { - err = EEXIST; - ntfs_log_error("Index already have such entry.\n"); - goto err_out; - } - if (errno != ENOENT) { - err = errno; - ntfs_log_error("Failed to find place where to insert new entry.\n"); - goto err_out; - } - /* Some setup. */ - if (ictx->is_in_root) - ih = &ictx->ir->index; - else - ih = &ictx->ia->index; - if (!allocated_size) - allocated_size = le16_to_cpu(ih->allocated_size); - /* Check whether we have enough space in the index. */ - if (le16_to_cpu(ih->index_length) + ie_size > allocated_size) { - /* If we in the index root try to resize it. */ - if (ictx->is_in_root) { - ntfs_attr *na; + + ie = ntfs_calloc(ie_size); + if (!ie) + return -1; - allocated_size = le16_to_cpu(ih->index_length) + - ie_size; - na = ntfs_attr_open(ictx->ni, AT_INDEX_ROOT, ictx->name, - ictx->name_len); - if (!na) { - err = errno; - ntfs_log_error("Failed to open INDEX_ROOT.\n"); - goto err_out; - } - if (ntfs_attr_truncate(na, allocated_size + offsetof( - INDEX_ROOT, index))) { - err = EOPNOTSUPP; - ntfs_attr_close(na); - ntfs_log_debug("Failed to truncate " - "INDEX_ROOT.\n"); - goto err_out; - } - ntfs_attr_close(na); - ntfs_index_ctx_reinit(ictx); - goto retry; - } - ntfs_log_debug("Not implemented case.\n"); - err = EOPNOTSUPP; - goto err_out; - } - /* Update allocated size if we in INDEX_ROOT. */ - if (ictx->is_in_root) - ih->allocated_size = cpu_to_le16(allocated_size); - /* Create entry. */ - ie = calloc(1, ie_size); - if (!ie) { - err = errno; - goto err_out; - } ie->indexed_file = cpu_to_le64(mref); - ie->length = cpu_to_le16(ie_size); - ie->key_length = cpu_to_le16(fn_size); + ie->length = cpu_to_le16(ie_size); + ie->key_length = cpu_to_le16(fn_size); memcpy(&ie->key, fn, fn_size); - /* Update index length, move following entries forard and copy entry. */ - ih->index_length = cpu_to_le16(le16_to_cpu(ih->index_length) + ie_size); - memmove((u8*)ictx->entry + ie_size, ictx->entry, - le16_to_cpu(ih->index_length) - - ((u8*)ictx->entry - (u8*)ih) - ie_size); - memcpy(ictx->entry, ie, ie_size); - /* Done! */ - ntfs_index_entry_mark_dirty(ictx); - ntfs_index_ctx_put(ictx); + + icx = ntfs_index_ctx_get(ni, NTFS_INDEX_I30, 4); + if (!icx) + goto out; + + ret = ntfs_ie_add(icx, ie); + + ntfs_index_ctx_put(icx); +out: free(ie); - ntfs_log_trace("Done.\n"); - return 0; -err_out: - ntfs_log_trace("Failed.\n"); - ntfs_index_ctx_put(ictx); - errno = err; - return -1; + return ret; +} + +static int ntfs_ih_takeout(ntfs_index_context *icx, INDEX_HEADER *ih, + INDEX_ENTRY *ie, INDEX_BLOCK *ib) +{ + INDEX_ENTRY *ie_roam; + int ret = STATUS_ERROR; + + ntfs_log_trace("Entering.\n"); + + ie_roam = ntfs_ie_dup_novcn(ie); + if (!ie_roam) + return STATUS_ERROR; + + ntfs_ie_delete(ih, ie); + + if (ntfs_icx_parent_vcn(icx) == VCN_INDEX_ROOT_PARENT) + ntfs_inode_mark_dirty(icx->actx->ntfs_ino); + else + if (ntfs_ib_write(icx, ntfs_icx_parent_vcn(icx), ib)) + goto out; + + ntfs_index_ctx_reinit(icx); + + ret = ntfs_ie_add(icx, ie_roam); +out: + free(ie_roam); + return ret; +} + +/** + * Used if an empty index block to be deleted has END entry as the parent + * in the INDEX_ROOT which is the only one there. + */ +static void ntfs_ir_leafify(ntfs_index_context *icx, INDEX_HEADER *ih) +{ + INDEX_ENTRY *ie; + + ntfs_log_trace("Entering.\n"); + + ie = ntfs_ie_get_first(ih); + ie->flags &= ~INDEX_ENTRY_NODE; + ie->length -= sizeof(VCN); + + ih->index_length -= sizeof(VCN); + ih->flags &= ~LARGE_INDEX; + + /* Not fatal error */ + ntfs_ir_truncate(icx, le32_to_cpu(ih->index_length)); + + ntfs_inode_mark_dirty(icx->actx->ntfs_ino); + ntfs_index_ctx_reinit(icx); +} + +/** + * Used if an empty index block to be deleted has END entry as the parent + * in the INDEX_ROOT which is not the only one there. + */ +static int ntfs_ih_reparent_end(ntfs_index_context *icx, INDEX_HEADER *ih, + INDEX_BLOCK *ib) +{ + INDEX_ENTRY *ie, *ie_prev; + + ntfs_log_trace("Entering.\n"); + + ie = ntfs_ie_get_by_pos(ih, ntfs_icx_parent_pos(icx)); + ie_prev = ntfs_ie_prev(ih, ie); + + ntfs_ie_set_vcn(ie, ntfs_ie_get_vcn(ie_prev)); + return ntfs_ih_takeout(icx, ih, ie_prev, ib); +} + +static int ntfs_index_rm_leaf(ntfs_index_context *icx) +{ + INDEX_BLOCK *ib = NULL; + INDEX_HEADER *parent_ih; + INDEX_ENTRY *ie; + int ret = STATUS_ERROR; + + ntfs_log_trace("pindex: %d\n", icx->pindex); + + if (ntfs_icx_parent_dec(icx)) + return STATUS_ERROR; + + if (ntfs_ibm_clear(icx, icx->parent_vcn[icx->pindex + 1])) + return STATUS_ERROR; + + if (ntfs_icx_parent_vcn(icx) == VCN_INDEX_ROOT_PARENT) + parent_ih = &icx->ir->index; + else { + ib = ntfs_malloc(icx->block_size); + if (!ib) + return STATUS_ERROR; + + if (ntfs_ib_read(icx, ntfs_icx_parent_vcn(icx), ib)) + goto out; + + parent_ih = &ib->index; + } + + ie = ntfs_ie_get_by_pos(parent_ih, ntfs_icx_parent_pos(icx)); + if (!ntfs_ie_end(ie)) { + ret = ntfs_ih_takeout(icx, parent_ih, ie, ib); + goto out; + } + + if (ntfs_ih_zero_entry(parent_ih)) { + + if (ntfs_icx_parent_vcn(icx) == VCN_INDEX_ROOT_PARENT) { + ntfs_ir_leafify(icx, parent_ih); + goto ok; + } + + ret = ntfs_index_rm_leaf(icx); + goto out; + } + + if (ntfs_ih_reparent_end(icx, parent_ih, ib)) + goto out; +ok: + ret = STATUS_OK; +out: + free(ib); + return ret; +} + +static int ntfs_index_rm_node(ntfs_index_context *icx) +{ + int entry_pos; + VCN vcn; + INDEX_BLOCK *ib = NULL; + INDEX_ENTRY *ie_succ, *ie, *entry = icx->entry; + INDEX_HEADER *ih; + u32 new_size; + int delta, ret = STATUS_ERROR; + + ntfs_log_trace("Entering.\n"); + + if (!icx->ia_na) { + icx->ia_na = ntfs_ia_open(icx, icx->ni); + if (!icx->ia_na) + return STATUS_ERROR; + } + + ib = ntfs_malloc(icx->block_size); + if (!ib) + return STATUS_ERROR; + + ie_succ = ntfs_ie_get_next(icx->entry); + entry_pos = icx->parent_pos[icx->pindex]++; +descend: + vcn = ntfs_ie_get_vcn(ie_succ); + if (ntfs_ib_read(icx, vcn, ib)) + goto out; + + ie_succ = ntfs_ie_get_first(&ib->index); + + if (ntfs_icx_parent_inc(icx)) + goto out; + + icx->parent_vcn[icx->pindex] = vcn; + icx->parent_pos[icx->pindex] = 0; + + if ((ib->index.flags & NODE_MASK) == INDEX_NODE) + goto descend; + + if (ntfs_ih_zero_entry(&ib->index)) { + errno = EOPNOTSUPP; + ntfs_log_perror("Failed to find any entry in an index block. " + "Please run chkdsk."); + goto out; + } + + ie = ntfs_ie_dup(ie_succ); + if (!ie) + goto out; + + if (ntfs_ie_add_vcn(&ie)) + goto out2; + + ntfs_ie_set_vcn(ie, ntfs_ie_get_vcn(icx->entry)); + + if (icx->is_in_root) + ih = &icx->ir->index; + else + ih = &icx->ib->index; + + delta = ie->length - icx->entry->length; + new_size = le32_to_cpu(ih->index_length) + delta; + if (delta > 0) { + if (icx->is_in_root) { + if (ntfs_ir_truncate(icx, new_size)) { + errno = EOPNOTSUPP; + ntfs_log_perror("Denied to truncate INDEX_ROOT" + " during entry removal"); + goto out2; + } + + ih = &icx->ir->index; + entry = ntfs_ie_get_by_pos(ih, entry_pos); + + } else if (new_size > ih->allocated_size) { + errno = EOPNOTSUPP; + ntfs_log_perror("Denied to split INDEX_BLOCK during " + "entry removal"); + goto out2; + } + } + + ntfs_ie_delete(ih, entry); + ntfs_ie_insert(ih, ie, entry); + + if (icx->is_in_root) { + if (ntfs_ir_truncate(icx, new_size)) + goto out2; + ntfs_inode_mark_dirty(icx->actx->ntfs_ino); + } else + if (ntfs_icx_ib_write(icx)) + goto out2; + + ntfs_ie_delete(&ib->index, ie_succ); + + if (ntfs_ih_zero_entry(&ib->index)) { + if (ntfs_index_rm_leaf(icx)) + goto out2; + } else + if (ntfs_ib_write(icx, vcn, ib)) + goto out2; + + ret = STATUS_OK; +out2: + free(ie); +out: + free(ib); + return ret; } /** * ntfs_index_rm - remove entry from the index - * @ictx: index context describing entry to delete + * @icx: index context describing entry to delete * - * Delete entry described by @ictx from the index. NOTE: This function does not - * support all cases, so it can fail with EOPNOTSUPP error code. In any case - * index context is always reinitialized after use of this function, so it can - * be used for index lookup once again. + * Delete entry described by @icx from the index. Index context is always + * reinitialized after use of this function, so it can be used for index + * lookup once again. * * Return 0 on success or -1 on error with errno set to the error code. */ -int ntfs_index_rm(ntfs_index_context *ictx) +int ntfs_index_rm(ntfs_index_context *icx) { INDEX_HEADER *ih; - u32 new_index_length; int err; ntfs_log_trace("Entering.\n"); - if (!ictx || (!ictx->ia && !ictx->ir) || - ictx->entry->flags & INDEX_ENTRY_END) { + + if (!icx || (!icx->ib && !icx->ir) || ntfs_ie_end(icx->entry)) { ntfs_log_error("Invalid arguments.\n"); - err = EINVAL; + errno = EINVAL; goto err_out; } - if (ictx->is_in_root) - ih = &ictx->ir->index; + if (icx->is_in_root) + ih = &icx->ir->index; else - ih = &ictx->ia->index; - /* Don't support deletion of entries with subnodes yet. */ - if (ictx->entry->flags & INDEX_ENTRY_NODE) { - err = EOPNOTSUPP; - goto err_out; - } - /* Calculate new length of the index. */ - new_index_length = le32_to_cpu(ih->index_length) - - le16_to_cpu(ictx->entry->length); - /* Don't support deletion of the last entry in the allocation block. */ - if (!ictx->is_in_root && (new_index_length <= - le32_to_cpu(ih->entries_offset) + - sizeof(INDEX_ENTRY_HEADER) + sizeof(VCN))) { - err = EOPNOTSUPP; - goto err_out; - } - /* Update index length and remove index entry. */ - ih->index_length = cpu_to_le32(new_index_length); - if (ictx->is_in_root) - ih->allocated_size = ih->index_length; - memmove(ictx->entry, (u8*)ictx->entry + le16_to_cpu( - ictx->entry->length), new_index_length - - ((u8*)ictx->entry - (u8*)ih)); - ntfs_index_entry_mark_dirty(ictx); - /* Resize INDEX_ROOT attribute. */ - if (ictx->is_in_root) { - ntfs_attr *na; + ih = &icx->ib->index; + + if (icx->entry->flags & INDEX_ENTRY_NODE) { + + if (ntfs_index_rm_node(icx)) + goto err_out; - na = ntfs_attr_open(ictx->ni, AT_INDEX_ROOT, ictx->name, - ictx->name_len); - if (!na) { - err = errno; - ntfs_log_error("Failed to open INDEX_ROOT attribute. " - "Leaving inconsistent metadata.\n"); + } else if (icx->is_in_root || !ntfs_ih_one_entry(ih)) { + + ntfs_ie_delete(ih, icx->entry); + + if (icx->is_in_root) { + err = ntfs_ir_truncate(icx, + le32_to_cpu(ih->index_length)); + if (err != STATUS_OK) + goto err_out; + } else + if (ntfs_icx_ib_write(icx)) + goto err_out; + } else { + if (ntfs_index_rm_leaf(icx)) goto err_out; - } - if (ntfs_attr_truncate(na, new_index_length + offsetof( - INDEX_ROOT, index))) { - err = errno; - ntfs_log_error("Failed to truncate INDEX_ROOT " - "attribute. Leaving inconsistent " - "metadata.\n"); - goto err_out; - } - ntfs_attr_close(na); } - ntfs_index_ctx_reinit(ictx); + + ntfs_index_ctx_reinit(icx); ntfs_log_trace("Done.\n"); return 0; err_out: - ntfs_index_ctx_reinit(ictx); - ntfs_log_trace("Failed.\n"); + err = errno; + ntfs_index_ctx_reinit(icx); errno = err; + ntfs_log_trace("Failed.\n"); return -1; } @@ -680,23 +1776,12 @@ INDEX_ROOT *ntfs_index_root_get(ntfs_inode *ni, ATTR_RECORD *attr) name = (ntfschar *)((u8 *)attr + le16_to_cpu(attr->name_offset)); - ctx = ntfs_attr_get_search_ctx(ni, NULL); - if (!ctx) { - ntfs_log_perror("ntfs_get_search_ctx failed"); + if (!ntfs_ir_lookup(ni, name, attr->name_length, &ctx)) return NULL; - } - if (ntfs_attr_lookup(AT_INDEX_ROOT, name, attr->name_length, 0, 0, NULL, - 0, ctx)) { - ntfs_log_perror("ntfs_attr_lookup failed"); + root = ntfs_malloc(sizeof(INDEX_ROOT)); + if (!root) goto out; - } - - root = malloc(sizeof(INDEX_ROOT)); - if (!root) { - ntfs_log_perror("malloc failed"); - goto out; - } *root = *((INDEX_ROOT *)((u8 *)ctx->attr + le16_to_cpu(ctx->attr->value_offset)));