From fd8f109ed2c2b30fdc5f578f13423960b2e3aa2d Mon Sep 17 00:00:00 2001 From: !flatcap Date: Thu, 22 Aug 2002 18:09:47 +0000 Subject: [PATCH] AT_NONAME -> AT_UNNAMED 2002/07/12 08:18:15-00:00 !antona sorry flatcap. - remove a lot of reorganization which rips the code into incomprehensible order. - all functions were and are again now placed together by logicnot by sheer randomness. - functions which call each other are placed together for example... 2002/07/11 23:44:13-00:00 !flatcap new function headers and a few function moves 2002/07/11 16:20:33-00:00 !flatcap whitespace and include guards 2002/07/08 23:27:16-00:00 !flatcap added AT_NONAME so we can search for a (un)named attribute or just iterate through all attributes 2002/07/08 06:23:22-00:00 !antona Don't use string concatenation with __FUNCTION__ as gcc-3.x don't like it. 2002/07/02 23:47:10-00:00 !antona Global replacement of __[su]{8,16,32,64} with [su]{8,16,32,64} and layout.h define it. 2002/06/08 14:12:01-00:00 !antona ntfs_readdir() has arrived. 2002/06/06 20:47:33-00:00 !antona The beginning of the directory operations! Introduce dir.[hc] and ntfs_lookup_inode_by_name(). 2002/06/06 15:41:30-00:00 !antona Add template files for directory handling code. (Logical change 1.5) --- libntfs/dir.c | 925 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 925 insertions(+) diff --git a/libntfs/dir.c b/libntfs/dir.c index e69de29b..dd156fba 100644 --- a/libntfs/dir.c +++ b/libntfs/dir.c @@ -0,0 +1,925 @@ +/* + * $Id$ + * + * dir.c - Directory handling code. Part of the Linux-NTFS project. + * + * Copyright (c) 2002 Anton Altaparmakov. + * + * This program/include file is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program/include file is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program (in the main directory of the Linux-NTFS + * distribution in the file COPYING); if not, write to the Free Software + * Foundation,Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include + +#include "types.h" +#include "debug.h" +#include "attrib.h" +#include "inode.h" +#include "dir.h" +#include "volume.h" + +/* + * The little endian Unicode string "$I30" as a global constant. + */ +uchar_t I30[5] = { const_cpu_to_le16('$'), const_cpu_to_le16('I'), + const_cpu_to_le16('3'), const_cpu_to_le16('0'), + const_cpu_to_le16('\0') }; + +/** + * ntfs_lookup_inode_by_name - find an inode in a directory given its name + * @dir_ni: ntfs inode of the directory in which to search for the name + * @uname: Unicode name for which to search in the directory + * @uname_len: length of the name @uname in Unicode characters + * + * Look for an inode with name @uname in the directory with inode @dir_ni. + * ntfs_lookup_inode_by_name() walks the contents of the directory looking for + * the Unicode name. If the name is found in the directory, the corresponding + * inode number (>= 0) is returned as a mft reference in cpu format, i.e. it + * is a 64-bit number containing the sequence number. + * + * On error, return -1 with errno set to the error code. If the inode is is not + * found errno is ENOENT. + * + * Note, @uname_len does not include the (optional) terminating NULL character. + * + * Note, we look for a case sensitive match first but we also look for a case + * insensitive match at the same time. If we find a case insensitive match, we + * save that for the case that we don't find an exact match, where we return + * the mft reference of the case insensitive match. + * + * If the volume is mounted with the case sensitive flag set, then we only + * allow exact matches. + */ +u64 ntfs_lookup_inode_by_name(ntfs_inode *dir_ni, const uchar_t *uname, + const int uname_len) +{ + VCN vcn; + u64 mref = 0; + s64 br; + ntfs_volume *vol = dir_ni->vol; + ntfs_attr_search_ctx *ctx; + INDEX_ROOT *ir; + INDEX_ENTRY *ie; + INDEX_ALLOCATION *ia; + u8 *index_end; + ntfs_attr *ia_na; + int eo, rc; + u32 index_block_size, index_vcn_size; + u8 index_vcn_size_bits; + + if (!dir_ni || !dir_ni->mrec || !uname || uname_len <= 0) { + errno = EINVAL; + return -1; + } + + ctx = ntfs_get_attr_search_ctx(dir_ni, NULL); + if (!ctx) + return -1; + + /* Find the index root attribute in the mft record. */ + if (!ntfs_lookup_attr(AT_INDEX_ROOT, I30, 4, CASE_SENSITIVE, 0, NULL, + 0, ctx)) { + Dprintf("Index root attribute missing in directory inode " + "0x%Lx: %s\n", + (unsigned long long)dir_ni->mft_no, + strerror(errno)); + goto put_err_out; + } + /* Get to the index root value. */ + ir = (INDEX_ROOT*)((u8*)ctx->attr + + le16_to_cpu(ctx->attr->value_offset)); + index_block_size = le32_to_cpu(ir->index_block_size); + if (index_block_size < NTFS_SECTOR_SIZE || + index_block_size & (index_block_size - 1)) { + Dprintf("Index block size %u is invalid.\n", index_block_size); + goto put_err_out; + } + index_end = (u8*)&ir->index + le32_to_cpu(ir->index.index_length); + /* 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. + */ + for (;; ie = (INDEX_ENTRY*)((u8*)ie + le16_to_cpu(ie->length))) { + /* Bounds checks. */ + if ((u8*)ie < (u8*)ctx->mrec || (u8*)ie + + sizeof(INDEX_ENTRY_HEADER) > index_end || + (u8*)ie + le16_to_cpu(ie->key_length) > + index_end) + goto put_err_out; + /* + * The last entry cannot contain a name. 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; + /* + * We perform a case sensitive comparison and if that matches + * we are done and return the mft reference of the inode (i.e. + * the inode number together with the sequence number for + * consistency checking). We convert it to cpu format before + * returning. + */ + if (ntfs_are_names_equal(uname, uname_len, + (uchar_t*)&ie->key.file_name.file_name, + ie->key.file_name.file_name_length, + CASE_SENSITIVE, vol->upcase, vol->upcase_len)) { +found_it: + /* + * We have a perfect match, so we don't need to care + * about having matched imperfectly before. + */ + mref = le64_to_cpu(ie->indexed_file); + ntfs_put_attr_search_ctx(ctx); + return mref; + } + /* + * For a case insensitive mount, we also perform a case + * insensitive comparison (provided the file name is not in the + * POSIX namespace). If the comparison matches, we cache the + * mft reference in mref. + */ + if (!NVolCaseSensitive(vol) && + ie->key.file_name.file_name_type && + ntfs_are_names_equal(uname, uname_len, + (uchar_t*)&ie->key.file_name.file_name, + ie->key.file_name.file_name_length, + IGNORE_CASE, vol->upcase, vol->upcase_len)) { + /* Only one case insensitive matching name allowed. */ + if (mref) { + Dputs("Found already cached mft reference in " + "phase 1. Please run chkdsk " + "and if that doesn't find any " + "errors please report you saw " + "this message to " + "linux-ntfs-dev@lists.sf.net."); + goto put_err_out; + } + mref = le64_to_cpu(ie->indexed_file); + } + /* + * 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_names(uname, uname_len, + (uchar_t*)&ie->key.file_name.file_name, + ie->key.file_name.file_name_length, 1, + IGNORE_CASE, vol->upcase, vol->upcase_len); + /* + * If uname collates before the name of the current entry, there + * is definitely no such name 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; + /* The names are not equal, continue the search. */ + if (rc) + continue; + /* + * Names match with case insensitive comparison, now try the + * case sensitive comparison, which is required for proper + * collation. + */ + rc = ntfs_collate_names(uname, uname_len, + (uchar_t*)&ie->key.file_name.file_name, + ie->key.file_name.file_name_length, 1, + CASE_SENSITIVE, vol->upcase, vol->upcase_len); + if (rc == -1) + break; + if (rc) + continue; + /* + * Perfect match, this will never happen as the + * ntfs_are_names_equal() call will have gotten a match but we + * still treat it correctly. + */ + goto found_it; + } + /* + * We have finished with this index without success. Check for the + * presence of a child node and if not present return error code + * ENOENT, unless we have got the mft reference of a matching name + * cached in mref in which case return mref. + */ + if (!(ie->flags & INDEX_ENTRY_NODE)) { + ntfs_put_attr_search_ctx(ctx); + if (mref) + return mref; + Dputs("Entry not found."); + errno = ENOENT; + return -1; + } /* Child node present, descend into it. */ + + /* Open the index allocation attribute. */ + ia_na = ntfs_attr_open(dir_ni, AT_INDEX_ALLOCATION, I30, 4); + if (!ia_na) { + Dprintf("Failed to open index allocation attribute. Directory " + "inode 0x%Lx is corrupt or driver bug: %s\n", + (unsigned long long)dir_ni->mft_no, + strerror(errno)); + goto put_err_out; + } + + /* Allocate a buffer for the current index block. */ + ia = (INDEX_ALLOCATION*)malloc(index_block_size); + if (!ia) { + Dperror("Failed to allocate buffer for index block"); + goto put_err_out; + } + + /* Determine the size of a vcn in the directory index. */ + if (vol->cluster_size <= index_block_size) { + index_vcn_size = vol->cluster_size; + index_vcn_size_bits = vol->cluster_size_bits; + } else { + index_vcn_size = vol->sector_size; + index_vcn_size_bits = vol->sector_size_bits; + } + + /* Get the starting vcn of the index_block holding the child node. */ + vcn = sle64_to_cpup((u8*)ie + le16_to_cpu(ie->length) - 8); + +descend_into_child_node: + + /* Read the index block starting at vcn. */ + br = ntfs_attr_mst_pread(ia_na, vcn << index_vcn_size_bits, 1, + index_block_size, ia); + if (br != 1) { + if (br != -1) + errno = EIO; + Dprintf("Failed to read vcn 0x%Lx: %s\n", vcn, strerror(errno)); + goto close_err_out; + } + + if (sle64_to_cpu(ia->index_block_vcn) != vcn) { + Dprintf("Actual VCN (0x%Lx) of index buffer is different from " + "expected VCN (0x%Lx).\n", + (long long)sle64_to_cpu(ia->index_block_vcn), + (long long)vcn); + errno = EIO; + goto close_err_out; + } + if (le32_to_cpu(ia->index.allocated_size) + 0x18 != index_block_size) { + Dprintf("Index buffer (VCN 0x%Lx) of directory inode 0x%Lx " + "has a size (%u) differing from the directory " + "specified size (%u).\n", (long long)vcn, + (unsigned long long)dir_ni->mft_no, + le32_to_cpu(ia->index.allocated_size) + 0x18, + index_block_size); + errno = EIO; + goto close_err_out; + } + index_end = (u8*)&ia->index + le32_to_cpu(ia->index.index_length); + if (index_end > (u8*)ia + index_block_size) { + Dprintf("Size of index buffer (VCN 0x%Lx) of directory inode " + "0x%Lx exceeds maximum size.\n", (long long)vcn, + (unsigned long long)dir_ni->mft_no); + errno = EIO; + goto close_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 check. */ + if ((u8*)ie < (u8*)ia || (u8*)ie + + sizeof(INDEX_ENTRY_HEADER) > index_end || + (u8*)ie + le16_to_cpu(ie->key_length) > + index_end) { + Dprintf("Index entry out of bounds in directory inode " + "0x%Lx.\n", + (unsigned long long)dir_ni->mft_no); + errno = EIO; + goto close_err_out; + } + /* + * The last entry cannot contain a name. 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; + /* + * We perform a case sensitive comparison and if that matches + * we are done and return the mft reference of the inode (i.e. + * the inode number together with the sequence number for + * consistency checking). We convert it to cpu format before + * returning. + */ + if (ntfs_are_names_equal(uname, uname_len, + (uchar_t*)&ie->key.file_name.file_name, + ie->key.file_name.file_name_length, + CASE_SENSITIVE, vol->upcase, vol->upcase_len)) { +found_it2: + /* + * We have a perfect match, so we don't need to care + * about having matched imperfectly before. + */ + mref = le64_to_cpu(ie->indexed_file); + ntfs_attr_close(ia_na); + ntfs_put_attr_search_ctx(ctx); + return mref; + } + /* + * For a case insensitive mount, we also perform a case + * insensitive comparison (provided the file name is not in the + * POSIX namespace). If the comparison matches, we cache the + * mft reference in mref. + */ + if (!NVolCaseSensitive(vol) && + ie->key.file_name.file_name_type && + ntfs_are_names_equal(uname, uname_len, + (uchar_t*)&ie->key.file_name.file_name, + ie->key.file_name.file_name_length, + IGNORE_CASE, vol->upcase, vol->upcase_len)) { + /* Only one case insensitive matching name allowed. */ + if (mref) { + Dputs("Found already cached mft reference in " + "phase 2. Please run chkdsk " + "and if that doesn't find any " + "errors please report you saw " + "this message to " + "linux-ntfs-dev@lists.sf.net."); + goto close_err_out; + } + mref = le64_to_cpu(ie->indexed_file); + } + /* + * 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_names(uname, uname_len, + (uchar_t*)&ie->key.file_name.file_name, + ie->key.file_name.file_name_length, 1, + IGNORE_CASE, vol->upcase, vol->upcase_len); + /* + * If uname collates before the name of the current entry, there + * is definitely no such name 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; + /* The names are not equal, continue the search. */ + if (rc) + continue; + /* + * Names match with case insensitive comparison, now try the + * case sensitive comparison, which is required for proper + * collation. + */ + rc = ntfs_collate_names(uname, uname_len, + (uchar_t*)&ie->key.file_name.file_name, + ie->key.file_name.file_name_length, 1, + CASE_SENSITIVE, vol->upcase, vol->upcase_len); + if (rc == -1) + break; + if (rc) + continue; + /* + * Perfect match, this will never happen as the + * ntfs_are_names_equal() call will have gotten a match but we + * still treat it correctly. + */ + goto found_it2; + } + /* + * We have finished with this index buffer without success. Check for + * the presence of a child node. + */ + if (ie->flags & INDEX_ENTRY_NODE) { + if ((ia->index.flags & NODE_MASK) == LEAF_NODE) { + Dprintf("Index entry with child node found in a leaf " + "node in directory inode 0x%Lx.\n", + (unsigned long long)dir_ni->mft_no); + errno = EIO; + goto close_err_out; + } + /* Child node present, descend into it. */ + vcn = sle64_to_cpup((u8*)ie + le16_to_cpu(ie->length) - 8); + if (vcn >= 0) + goto descend_into_child_node; + Dprintf("Negative child node vcn in directory inode 0x%Lx.\n", + (unsigned long long)dir_ni->mft_no); + errno = EIO; + goto close_err_out; + } + ntfs_attr_close(ia_na); + ntfs_put_attr_search_ctx(ctx); + /* + * No child node present, return error code ENOENT, unless we have got + * the mft reference of a matching name cached in mref in which case + * return mref. + */ + if (mref) + return mref; + Dputs("Entry not found."); + errno = ENOENT; + return -1; +put_err_out: + eo = EIO; + Dputs("Corrupt directory. Aborting lookup."); +eo_put_err_out: + ntfs_put_attr_search_ctx(ctx); + errno = eo; + return -1; +close_err_out: + eo = errno; + free(ia); + ntfs_attr_close(ia_na); + goto eo_put_err_out; +} + +/* + * The little endian Unicode string ".." for ntfs_readdir(). + */ +static const uchar_t dotdot[3] = { const_cpu_to_le16('.'), + const_cpu_to_le16('.'), + const_cpu_to_le16('\0') }; + +/* + * More helpers for ntfs_readdir(). + */ +typedef union { + INDEX_ROOT *ir; + INDEX_ALLOCATION *ia; +} index_union __attribute__ ((__transparent_union__)); + +typedef enum { + INDEX_TYPE_ROOT, /* index root */ + INDEX_TYPE_ALLOCATION, /* index allocation */ +} INDEX_TYPE; + +/** + * Internal: + * + * ntfs_filldir - ntfs specific filldir method + * @dir_ni: ntfs inode of current directory + * @pos: current position in directory + * @ivcn_bits: log(2) of index vcn size + * @index_type: specifies whether @iu is an index root or an index allocation + * @iu: index root or index block to which @ie belongs + * @ie: current index entry + * @dirent: context for filldir callback supplied by the caller + * @filldir: filldir callback supplied by the caller + * + * Pass information specifying the current directory entry @ie to the @filldir + * callback. + */ +static inline int ntfs_filldir(ntfs_inode *dir_ni, s64 *pos, u8 ivcn_bits, + const INDEX_TYPE index_type, index_union iu, INDEX_ENTRY *ie, + void *dirent, ntfs_filldir_t filldir) +{ + FILE_NAME_ATTR *fn = &ie->key.file_name; + unsigned dt_type; + + /* Advance the position even if going to skip the entry. */ + if (index_type == INDEX_TYPE_ALLOCATION) + *pos = (u8*)ie - (u8*)iu.ia + (sle64_to_cpu( + iu.ia->index_block_vcn) << ivcn_bits) + + dir_ni->vol->mft_record_size; + else /* if (index_type == INDEX_TYPE_ROOT) */ + *pos = (u8*)ie - (u8*)iu.ir; + /* Skip root directory self reference entry. */ + if (MREF_LE(ie->indexed_file) == FILE_root) + return 0; + if (ie->key.file_name.file_attributes & + FILE_ATTR_DUP_FILE_NAME_INDEX_PRESENT) + dt_type = NTFS_DT_DIR; + else + dt_type = NTFS_DT_REG; + return filldir(dirent, fn->file_name, fn->file_name_length, + fn->file_name_type, *pos, + le64_to_cpu(ie->indexed_file), dt_type); +} + +/** + * Internal: + * + * ntfs_get_parent_mft_ref - find mft reference of parent directory of an inode + * @ni: ntfs inode whose parent directory to find + * + * Find the parent directory of the ntfs inode @ni. To do this, find the first + * file name attribute in the mft record of @ni and return the parent mft + * reference from that. + * + * Note this only makes sense for directories, since files can be hard linked + * from multiple directories and there is no way for us to tell which one is + * being looked for. + * + * Technically directories can have hard links, too, but we consider that as + * illegal as Linux/UNIX do not support directory hard links. + * + * Return the mft reference of the parent directory on success or -1 on error + * with errno set to the error code. + */ +static MFT_REF ntfs_get_parent_mft_ref(ntfs_inode *ni) +{ + MFT_REF mref; + ntfs_attr_search_ctx *ctx; + FILE_NAME_ATTR *fn; + int eo; + + if (!ni) { + errno = EINVAL; + return -1; + } + + ctx = ntfs_get_attr_search_ctx(ni, NULL); + if (!ctx) + return -1; + if (ntfs_lookup_attr(AT_FILE_NAME, AT_UNNAMED, 0, 0, 0, NULL, 0, ctx)) { + Dprintf("No file name found in inode 0x%Lx. Corrupt inode.\n", + (unsigned long long)ni->mft_no); + goto err_out; + } + if (ctx->attr->non_resident) { + Dprintf("File name attribute must be resident. Corrupt inode " + "0x%Lx.\n", (unsigned long long)ni->mft_no); + goto io_err_out; + } + fn = (FILE_NAME_ATTR*)((u8*)ctx->attr + + le16_to_cpu(ctx->attr->value_offset)); + if ((u8*)fn + le32_to_cpu(ctx->attr->value_length) > + (u8*)ctx->attr + le32_to_cpu(ctx->attr->length)) { + Dprintf("Corrupt file name attribute in inode 0x%Lx.\n", + (unsigned long long)ni->mft_no); + goto io_err_out; + } + mref = le64_to_cpu(fn->parent_directory); + ntfs_put_attr_search_ctx(ctx); + return mref; +io_err_out: + errno = EIO; +err_out: + eo = errno; + ntfs_put_attr_search_ctx(ctx); + errno = eo; + return -1; +} + +/** + * ntfs_readdir - read the contents of an ntfs directory + * @dir_ni: ntfs inode of current directory + * @pos: current position in directory + * @dirent: context for filldir callback supplied by the caller + * @filldir: filldir callback supplied by the caller + * + * Parse the index root and the index blocks that are marked in use in the + * index bitmap and hand each found directory entry to the @filldir callback + * supplied by the caller. + * + * Return 0 on success or -1 on error with errno set to the error code. + * + * Note: Index blocks are parsed in ascending vcn order, from which follows + * that the directory entries are not returned sorted. + */ +int ntfs_readdir(ntfs_inode *dir_ni, s64 *pos, + void *dirent, ntfs_filldir_t filldir) +{ + s64 i_size, br, ia_pos, bmp_pos, ia_start; + ntfs_volume *vol; + ntfs_attr *ia_na, *bmp_na = NULL; + ntfs_attr_search_ctx *ctx = NULL; + u8 *index_end, *bmp; + INDEX_ROOT *ir; + INDEX_ENTRY *ie; + INDEX_ALLOCATION *ia; + int rc, ir_pos, bmp_buf_size, bmp_buf_pos, eo; + u32 index_block_size, index_vcn_size; + u8 index_block_size_bits, index_vcn_size_bits; + + if (!dir_ni || !pos || !filldir) { + errno = EINVAL; + return -1; + } + + vol = dir_ni->vol; + + Dprintf("Entering for inode 0x%Lx, *pos 0x%Lx.\n", + (unsigned long long)dir_ni->mft_no, (long long)*pos); + + /* Open the index allocation attribute. */ + ia_na = ntfs_attr_open(dir_ni, AT_INDEX_ALLOCATION, I30, 4); + if (!ia_na) { + if (errno != ENOENT) { + Dprintf("Failed to open index allocation attribute. " + "Directory inode 0x%Lx is corrupt or " + "bug: %s\n", + (unsigned long long)dir_ni->mft_no, + strerror(errno)); + return -1; + } + i_size = 0; + } else + i_size = ia_na->data_size; + + rc = 0; + + /* Are we at end of dir yet? */ + if (*pos >= i_size + vol->mft_record_size) + goto done; + + /* Emulate . and .. for all directories. */ + if (!*pos) { + rc = filldir(dirent, dotdot, 1, FILE_NAME_POSIX, *pos, + MK_MREF(dir_ni->mft_no, + le16_to_cpu(dir_ni->mrec->sequence_number)), + NTFS_DT_DIR); + if (rc) + goto done; + ++*pos; + } + if (*pos == 1) { + MFT_REF parent_mref; + + parent_mref = ntfs_get_parent_mft_ref(dir_ni); + if (parent_mref == -1) { + Dprintf("Parent directory not found: %s\n", errno); + goto dir_err_out; + } + + rc = filldir(dirent, dotdot, 2, FILE_NAME_POSIX, *pos, + parent_mref, NTFS_DT_DIR); + if (rc) + goto done; + ++*pos; + } + + ctx = ntfs_get_attr_search_ctx(dir_ni, NULL); + if (!ctx) + goto err_out; + + /* Get the offset into the index root attribute. */ + ir_pos = (int)*pos; + /* Find the index root attribute in the mft record. */ + if (!ntfs_lookup_attr(AT_INDEX_ROOT, I30, 4, CASE_SENSITIVE, 0, NULL, + 0, ctx)) { + Dprintf("Index root attribute missing in directory inode " + "0x%Lx.\n", (unsigned long long)dir_ni->mft_no); + goto dir_err_out; + } + /* Get to the index root value. */ + ir = (INDEX_ROOT*)((u8*)ctx->attr + + le16_to_cpu(ctx->attr->value_offset)); + + /* Determine the size of a vcn in the directory index. */ + index_block_size = le32_to_cpu(ir->index_block_size); + if (index_block_size < NTFS_SECTOR_SIZE || + index_block_size & (index_block_size - 1)) { + Dprintf("Index block size %u is invalid.\n", index_block_size); + goto dir_err_out; + } + index_block_size_bits = ffs(index_block_size) - 1; + if (vol->cluster_size <= index_block_size) { + index_vcn_size = vol->cluster_size; + index_vcn_size_bits = vol->cluster_size_bits; + } else { + index_vcn_size = vol->sector_size; + index_vcn_size_bits = vol->sector_size_bits; + } + + /* Are we jumping straight into the index allocation attribute? */ + if (*pos >= vol->mft_record_size) { + ntfs_put_attr_search_ctx(ctx); + ctx = NULL; + goto skip_index_root; + } + + index_end = (u8*)&ir->index + le32_to_cpu(ir->index.index_length); + /* 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 or until filldir tells us it has had enough + * or signals an error (both covered by the rc test). + */ + for (;; ie = (INDEX_ENTRY*)((u8*)ie + le16_to_cpu(ie->length))) { + Dprintf("In index root, offset 0x%x.\n", (u8*)ie - (u8*)ir); + /* Bounds checks. */ + if ((u8*)ie < (u8*)ctx->mrec || (u8*)ie + + sizeof(INDEX_ENTRY_HEADER) > index_end || + (u8*)ie + le16_to_cpu(ie->key_length) > + index_end) + goto dir_err_out; + /* The last entry cannot contain a name. */ + if (ie->flags & INDEX_ENTRY_END) + break; + /* Skip index root entry if continuing previous readdir. */ + if (ir_pos > (u8*)ie - (u8*)ir) + continue; + /* + * Submit the directory entry to ntfs_filldir(), which will + * invoke the filldir() callback as appropriate. + */ + rc = ntfs_filldir(dir_ni, pos, index_vcn_size_bits, + INDEX_TYPE_ROOT, ir, ie, dirent, filldir); + if (rc) { + ntfs_put_attr_search_ctx(ctx); + ctx = NULL; + goto done; + } + } + ntfs_put_attr_search_ctx(ctx); + ctx = NULL; + + /* If there is no index allocation attribute we are finished. */ + if (!ia_na) + goto EOD; + + /* Advance *pos to the beginning of the index allocation. */ + *pos = vol->mft_record_size; + +skip_index_root: + + if (!ia_na) + goto done; + + /* Allocate a buffer for the current index block. */ + ia = (INDEX_ALLOCATION*)malloc(index_block_size); + if (!ia) { + Dperror("Failed to allocate buffer for index block"); + goto err_out; + } + + bmp_na = ntfs_attr_open(dir_ni, AT_BITMAP, I30, 4); + if (!bmp_na) { + Dperror("Failed to open index bitmap attribute"); + goto dir_err_out; + } + + /* Get the offset into the index allocation attribute. */ + ia_pos = *pos - vol->mft_record_size; + + bmp_pos = ia_pos >> index_block_size_bits; + if (bmp_pos >> 3 >= bmp_na->data_size) { + Dputs("Current index position exceeds index bitmap size."); + goto dir_err_out; + } + + bmp_buf_size = min(bmp_na->data_size - (bmp_pos >> 3), 4096); + bmp = (u8*)malloc(bmp_buf_size); + if (!bmp) { + Dperror("Failed to allocate bitmap buffer"); + goto err_out; + } + + br = ntfs_attr_pread(bmp_na, bmp_pos >> 3, bmp_buf_size, bmp); + if (br != bmp_buf_size) { + if (br != -1) + errno = EIO; + Dperror("Failed to read from inde bitmap attribute"); + goto err_out; + } + + bmp_buf_pos = 0; + /* If the index block is not in use find the next one that is. */ + while (!(bmp[bmp_buf_pos >> 3] & (1 << (bmp_buf_pos & 7)))) { +find_next_index_buffer: + bmp_pos++; + bmp_buf_pos++; + /* If we have reached the end of the bitmap, we are done. */ + if (bmp_pos >> 3 >= bmp_na->data_size) + goto EOD; + ia_pos = bmp_pos << index_block_size_bits; + if (bmp_buf_pos >> 3 < bmp_buf_size) + continue; + /* Read next chunk from the index bitmap. */ + if ((bmp_pos >> 3) + bmp_buf_size > bmp_na->data_size) + bmp_buf_size = bmp_na->data_size - (bmp_pos >> 3); + br = ntfs_attr_pread(bmp_na, bmp_pos >> 3, bmp_buf_size, bmp); + if (br != bmp_buf_size) { + if (br != -1) + errno = EIO; + Dperror("Failed to read from inde bitmap attribute"); + goto err_out; + } + } + + Dprintf("Handling index block 0x%Lx.", (long long)bmp_pos); + + /* Read the index block starting at bmp_pos. */ + br = ntfs_attr_mst_pread(ia_na, bmp_pos << index_block_size_bits, 1, + index_block_size, ia); + if (br != 1) { + if (br != -1) + errno = EIO; + Dperror("Failed to read index block"); + goto err_out; + } + + ia_start = ia_pos & ~(s64)(index_block_size - 1); + if (sle64_to_cpu(ia->index_block_vcn) != ia_start >> + index_vcn_size_bits) { + Dprintf("Actual VCN (0x%Lx) of index buffer is different from " + "expected VCN (0x%Lx) in inode 0x%Lx.\n", + (long long)sle64_to_cpu(ia->index_block_vcn), + (long long)ia_start >> index_vcn_size_bits, + (unsigned long long)dir_ni->mft_no); + goto dir_err_out; + } + if (le32_to_cpu(ia->index.allocated_size) + 0x18 != index_block_size) { + Dprintf("Index buffer (VCN 0x%Lx) of directory inode 0x%Lx " + "has a size (%u) differing from the directory " + "specified size (%u).\n", + (long long)ia_start >> index_vcn_size_bits, + (unsigned long long)dir_ni->mft_no, + le32_to_cpu(ia->index.allocated_size) + 0x18, + index_block_size); + goto dir_err_out; + } + index_end = (u8*)&ia->index + le32_to_cpu(ia->index.index_length); + if (index_end > (u8*)ia + index_block_size) { + Dprintf("Size of index buffer (VCN 0x%Lx) of directory inode " + "0x%Lx exceeds maximum size.\n", + (long long)ia_start >> index_vcn_size_bits, + (unsigned long long)dir_ni->mft_no); + goto dir_err_out; + } + /* The first index entry. */ + ie = (INDEX_ENTRY*)((u8*)&ia->index + + le32_to_cpu(ia->index.entries_offset)); + /* + * Loop until we exceed valid memory (corruption case) or until we + * reach the last entry or until ntfs_filldir tells us it has had + * enough or signals an error (both covered by the rc test). + */ + for (;; ie = (INDEX_ENTRY*)((u8*)ie + le16_to_cpu(ie->length))) { + Dprintf("In index allocation, offset 0x%Lx.\n", + (long long)ia_start + ((u8*)ie - (u8*)ia)); + /* Bounds checks. */ + if ((u8*)ie < (u8*)ia || (u8*)ie + + sizeof(INDEX_ENTRY_HEADER) > index_end || + (u8*)ie + le16_to_cpu(ie->key_length) > + index_end) { + Dprintf("Index entry out of bounds in directory inode " + "0x%Lx.\n", + (unsigned long long)dir_ni->mft_no); + goto dir_err_out; + } + /* The last entry cannot contain a name. */ + if (ie->flags & INDEX_ENTRY_END) + break; + /* Skip index entry if continuing previous readdir. */ + if (ia_pos - ia_start > (u8*)ie - (u8*)ia) + continue; + /* + * Submit the directory entry to ntfs_filldir(), which will + * invoke the filldir() callback as appropriate. + */ + rc = ntfs_filldir(dir_ni, pos, index_vcn_size_bits, + INDEX_TYPE_ALLOCATION, ia, ie, dirent, filldir); + if (rc) + goto done; + } + goto find_next_index_buffer; +EOD: + /* We are finished, set *pos to EOD. */ + *pos = i_size + vol->mft_record_size; +done: + if (bmp_na) + ntfs_attr_close(bmp_na); + ntfs_attr_close(ia_na); +#ifdef DEBUG + if (!rc) + Dprintf("EOD, *pos 0x%Lx, returning 0.\n", (long long)*pos); + else + Dprintf("filldir returned %i, *pos 0x%Lx, returning 0.\n", + rc, (long long)*pos); +#endif + return 0; +dir_err_out: + errno = EIO; +err_out: + eo = errno; + Dprintf("%s() failed.\n", __FUNCTION__); + if (ctx) + ntfs_put_attr_search_ctx(ctx); + if (bmp_na) + ntfs_attr_close(bmp_na); + ntfs_attr_close(ia_na); + errno = eo; + return -1; +} +