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)edge.strict_endians
parent
94ac119fb4
commit
fd8f109ed2
925
libntfs/dir.c
925
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 <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue