#include <sys/buf.h>
#include <sys/param.h>
#include <sys/dirent.h>
#include <sys/errno.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/ucred.h>
#include <sys/uio.h>
#include <sys/vnode.h>
#include <string.h>
#include <libkern/OSAtomic.h>
#include <libkern/OSMalloc.h>
#include <kern/debug.h>
#include <kern/locks.h>
#include "ntfs.h"
#include "ntfs_attr.h"
#include "ntfs_debug.h"
#include "ntfs_dir.h"
#include "ntfs_endian.h"
#include "ntfs_index.h"
#include "ntfs_inode.h"
#include "ntfs_layout.h"
#include "ntfs_mft.h"
#include "ntfs_page.h"
#include "ntfs_time.h"
#include "ntfs_types.h"
#include "ntfs_unistr.h"
#include "ntfs_volume.h"
ntfschar I30[5] = { const_cpu_to_le16('$'), const_cpu_to_le16('I'),
const_cpu_to_le16('3'), const_cpu_to_le16('0'), 0 };
errno_t ntfs_lookup_inode_by_name(ntfs_inode *dir_ni, const ntfschar *uname,
const signed uname_len, MFT_REF *res_mref,
ntfs_dir_lookup_name **res_name)
{
VCN vcn, old_vcn;
ntfs_volume *vol = dir_ni->vol;
mount_t mp = vol->mp;
ntfs_inode *ia_ni;
vnode_t ia_vn = NULL;
MFT_RECORD *m;
INDEX_ROOT *ir;
INDEX_ENTRY *ie;
ntfs_dir_lookup_name *name = NULL;
upl_t upl;
upl_page_info_array_t pl;
u8 *kaddr;
INDEX_ALLOCATION *ia;
u8 *index_end;
ntfs_attr_search_ctx *ctx;
int rc;
errno_t err;
if (!S_ISDIR(dir_ni->mode))
panic("%s(): !S_ISDIR(dir_ni->mode\n", __FUNCTION__);
if (NInoAttr(dir_ni))
panic("%s(): NInoAttr(dir_ni)\n", __FUNCTION__);
err = ntfs_index_inode_get(dir_ni, I30, 4, FALSE, &ia_ni);
if (err) {
ntfs_error(mp, "Failed to get index vnode (error %d).", err);
return err;
}
ia_vn = ia_ni->vn;
lck_rw_lock_shared(&ia_ni->lock);
err = ntfs_mft_record_map(dir_ni, &m);
if (err) {
ntfs_error(mp, "Failed to map mft record for directory (error "
"%d).", err);
goto err;
}
ctx = ntfs_attr_search_ctx_get(dir_ni, m);
if (!ctx) {
ntfs_error(mp, "Failed to get attribute search context.");
err = ENOMEM;
goto unm_err;
}
err = ntfs_attr_lookup(AT_INDEX_ROOT, I30, 4, 0, NULL, 0, ctx);
if (err) {
if (err == ENOENT) {
ntfs_error(mp, "Index root attribute missing in "
"directory inode 0x%llx.",
(unsigned long long)dir_ni->mft_no);
err = EIO;
}
goto put_err;
}
ir = (INDEX_ROOT*)((u8*)ctx->a + le16_to_cpu(ctx->a->value_offset));
index_end = (u8*)&ir->index + le32_to_cpu(ir->index.index_length);
ie = (INDEX_ENTRY*)((u8*)&ir->index +
le32_to_cpu(ir->index.entries_offset));
for (;; ie = (INDEX_ENTRY*)((u8*)ie + le16_to_cpu(ie->length))) {
ntfs_debug("In index root, offset 0x%x.",
(unsigned)((u8*)ie - (u8*)ir));
if ((u8*)ie < (u8*)&ir->index || (u8*)ie +
sizeof(INDEX_ENTRY_HEADER) > index_end ||
(u8*)ie + le16_to_cpu(ie->key_length) >
index_end)
goto dir_err;
if (ie->flags & INDEX_ENTRY_END)
break;
if (ntfs_are_names_equal(uname, uname_len,
(ntfschar*)&ie->key.filename.filename,
ie->key.filename.filename_length, TRUE,
vol->upcase, vol->upcase_len)) {
found_it:
if (ie->key.filename.filename_type == FILENAME_DOS) {
u8 len;
if (!name) {
*res_name = name = OSMalloc(
sizeof(*name),
ntfs_malloc_tag);
if (!name) {
err = ENOMEM;
goto put_err;
}
}
name->mref = le64_to_cpu(ie->indexed_file);
name->type = FILENAME_DOS;
name->len = len = ie->key.filename.
filename_length;
memcpy(name->name, ie->key.filename.filename,
len * sizeof(ntfschar));
} else {
if (name)
OSFree(name, sizeof(*name),
ntfs_malloc_tag);
*res_name = NULL;
}
*res_mref = le64_to_cpu(ie->indexed_file);
ntfs_attr_search_ctx_put(ctx);
ntfs_mft_record_unmap(dir_ni);
lck_rw_unlock_shared(&ia_ni->lock);
(void)vnode_put(ia_vn);
return 0;
}
if (!NVolCaseSensitive(vol) &&
ntfs_are_names_equal(uname, uname_len,
(ntfschar*)&ie->key.filename.filename,
ie->key.filename.filename_length, FALSE,
vol->upcase, vol->upcase_len)) {
u8 type;
type = ie->key.filename.filename_type;
if (!name || type == FILENAME_WIN32 || type ==
FILENAME_WIN32_AND_DOS) {
u8 len;
if (!name) {
*res_name = name = OSMalloc(
sizeof(*name),
ntfs_malloc_tag);
if (!name) {
err = ENOMEM;
goto put_err;
}
}
name->mref = le64_to_cpu(ie->indexed_file);
name->type = type;
name->len = len = ie->key.filename.
filename_length;
memcpy(name->name, ie->key.filename.filename,
len * sizeof(ntfschar));
}
}
rc = ntfs_collate_names(uname, uname_len,
(ntfschar*)&ie->key.filename.filename,
ie->key.filename.filename_length, 1, FALSE,
vol->upcase, vol->upcase_len);
if (rc == -1)
break;
if (rc)
continue;
rc = ntfs_collate_names(uname, uname_len,
(ntfschar*)&ie->key.filename.filename,
ie->key.filename.filename_length, 1, TRUE,
vol->upcase, vol->upcase_len);
if (rc == -1)
break;
if (rc)
continue;
goto found_it;
}
if (!(ie->flags & INDEX_ENTRY_NODE)) {
ntfs_attr_search_ctx_put(ctx);
ntfs_mft_record_unmap(dir_ni);
goto not_found;
}
if (!NInoIndexAllocPresent(ia_ni)) {
ntfs_error(mp, "No index allocation attribute but index entry "
"requires one. Directory inode 0x%llx is "
"corrupt or driver bug.",
(unsigned long long)dir_ni->mft_no);
NVolSetErrors(vol);
goto put_err;
}
vcn = sle64_to_cpup((sle64*)((u8*)ie + le16_to_cpu(ie->length) - 8));
ntfs_attr_search_ctx_put(ctx);
ntfs_mft_record_unmap(dir_ni);
m = NULL;
ctx = NULL;
descend_into_child_node:
err = ntfs_page_map(ia_ni, (vcn << ia_ni->vcn_size_shift) &
~PAGE_MASK_64, &upl, &pl, &kaddr, FALSE);
if (err) {
ntfs_error(mp, "Failed to map directory index page (error "
"%d).", err);
goto err;
}
fast_descend_into_child_node:
ia = (INDEX_ALLOCATION*)(kaddr + ((vcn << ia_ni->vcn_size_shift) &
PAGE_MASK));
if ((u8*)ia < kaddr || (u8*)ia > kaddr + PAGE_SIZE) {
ntfs_error(mp, "Out of bounds check failed. Corrupt "
"directory inode 0x%llx or driver bug.",
(unsigned long long)dir_ni->mft_no);
goto page_err;
}
if (!ntfs_is_indx_record(ia->magic)) {
ntfs_error(mp, "Directory index record with VCN 0x%llx is "
"corrupt. Corrupt inode 0x%llx. Run chkdsk.",
(unsigned long long)vcn,
(unsigned long long)dir_ni->mft_no);
goto page_err;
}
if (sle64_to_cpu(ia->index_block_vcn) != vcn) {
ntfs_error(mp, "Actual VCN (0x%llx) of index buffer is "
"different from expected VCN (0x%llx). "
"Directory inode 0x%llx is corrupt or driver "
"bug.", (unsigned long long)
sle64_to_cpu(ia->index_block_vcn),
(unsigned long long)vcn,
(unsigned long long)dir_ni->mft_no);
goto page_err;
}
if (offsetof(INDEX_BLOCK, index) +
le32_to_cpu(ia->index.allocated_size) !=
ia_ni->block_size) {
ntfs_error(mp, "Index buffer (VCN 0x%llx) of directory inode "
"0x%llx has a size (%u) differing from the "
"directory specified size (%u). Directory "
"inode is corrupt or driver bug.",
(unsigned long long)vcn,
(unsigned long long)dir_ni->mft_no, (unsigned)
(offsetof(INDEX_BLOCK, index) +
le32_to_cpu(ia->index.allocated_size)),
(unsigned)ia_ni->block_size);
goto page_err;
}
index_end = (u8*)ia + ia_ni->block_size;
if (index_end > kaddr + PAGE_SIZE) {
ntfs_error(mp, "Index buffer (VCN 0x%llx) of directory inode "
"0x%llx crosses page boundary. Impossible! "
"Cannot access! This is probably a bug in "
"the driver.", (unsigned long long)vcn,
(unsigned long long)dir_ni->mft_no);
goto page_err;
}
index_end = (u8*)&ia->index + le32_to_cpu(ia->index.index_length);
if (index_end > (u8*)ia + ia_ni->block_size) {
ntfs_error(mp, "Size of index buffer (VCN 0x%llx) of directory "
"inode 0x%llx exceeds maximum size.",
(unsigned long long)vcn,
(unsigned long long)dir_ni->mft_no);
goto page_err;
}
ie = (INDEX_ENTRY*)((u8*)&ia->index +
le32_to_cpu(ia->index.entries_offset));
for (;; ie = (INDEX_ENTRY*)((u8*)ie + le16_to_cpu(ie->length))) {
if ((u8*)ie < (u8*)&ia->index || (u8*)ie +
sizeof(INDEX_ENTRY_HEADER) > index_end ||
(u8*)ie + le16_to_cpu(ie->key_length) >
index_end) {
ntfs_error(mp, "Index entry out of bounds in "
"directory inode 0x%llx.",
(unsigned long long)dir_ni->mft_no);
goto page_err;
}
if (ie->flags & INDEX_ENTRY_END)
break;
if (ntfs_are_names_equal(uname, uname_len,
(ntfschar*)&ie->key.filename.filename,
ie->key.filename.filename_length, TRUE,
vol->upcase, vol->upcase_len)) {
found_it2:
if (ie->key.filename.filename_type == FILENAME_DOS) {
u8 len;
if (!name) {
*res_name = name = OSMalloc(
sizeof(*name),
ntfs_malloc_tag);
if (!name) {
err = ENOMEM;
goto page_err;
}
}
name->mref = le64_to_cpu(ie->indexed_file);
name->type = FILENAME_DOS;
name->len = len = ie->key.filename.
filename_length;
memcpy(name->name, ie->key.filename.filename,
len * sizeof(ntfschar));
} else {
if (name)
OSFree(name, sizeof(*name),
ntfs_malloc_tag);
*res_name = NULL;
}
*res_mref = le64_to_cpu(ie->indexed_file);
ntfs_page_unmap(ia_ni, upl, pl, FALSE);
lck_rw_unlock_shared(&ia_ni->lock);
(void)vnode_put(ia_vn);
return 0;
}
if (!NVolCaseSensitive(vol) &&
ntfs_are_names_equal(uname, uname_len,
(ntfschar*)&ie->key.filename.filename,
ie->key.filename.filename_length, FALSE,
vol->upcase, vol->upcase_len)) {
u8 type;
type = ie->key.filename.filename_type;
if (!name || type == FILENAME_WIN32 || type ==
FILENAME_WIN32_AND_DOS) {
u8 len;
if (!name) {
*res_name = name = OSMalloc(
sizeof(*name),
ntfs_malloc_tag);
if (!name) {
err = ENOMEM;
goto page_err;
}
}
name->mref = le64_to_cpu(ie->indexed_file);
name->type = type;
name->len = len = ie->key.filename.
filename_length;
memcpy(name->name, ie->key.filename.filename,
len * sizeof(ntfschar));
}
}
rc = ntfs_collate_names(uname, uname_len,
(ntfschar*)&ie->key.filename.filename,
ie->key.filename.filename_length, 1, FALSE,
vol->upcase, vol->upcase_len);
if (rc == -1)
break;
if (rc)
continue;
rc = ntfs_collate_names(uname, uname_len,
(ntfschar*)&ie->key.filename.filename,
ie->key.filename.filename_length, 1, TRUE,
vol->upcase, vol->upcase_len);
if (rc == -1)
break;
if (rc)
continue;
goto found_it2;
}
if (ie->flags & INDEX_ENTRY_NODE) {
if ((ia->index.flags & NODE_MASK) == LEAF_NODE) {
ntfs_error(mp, "Index entry with child node found in "
"a leaf node in directory inode "
"0x%llx.",
(unsigned long long)dir_ni->mft_no);
goto page_err;
}
old_vcn = vcn;
vcn = sle64_to_cpup((sle64*)((u8*)ie +
le16_to_cpu(ie->length) - 8));
if (vcn >= 0) {
if (old_vcn << ia_ni->vcn_size_shift >> PAGE_SHIFT ==
vcn << ia_ni->vcn_size_shift >>
PAGE_SHIFT)
goto fast_descend_into_child_node;
ntfs_page_unmap(ia_ni, upl, pl, FALSE);
goto descend_into_child_node;
}
ntfs_error(mp, "Negative child node vcn in directory inode "
"0x%llx.", (unsigned long long)dir_ni->mft_no);
goto page_err;
}
ntfs_page_unmap(ia_ni, upl, pl, FALSE);
not_found:
lck_rw_unlock_shared(&ia_ni->lock);
(void)vnode_put(ia_vn);
if (name) {
*res_mref = name->mref;
return 0;
}
ntfs_debug("Entry not found.");
return ENOENT;
page_err:
ntfs_page_unmap(ia_ni, upl, pl, FALSE);
goto err;
dir_err:
ntfs_error(mp, "Corrupt directory inode 0x%llx. Run chkdsk.",
(unsigned long long)dir_ni->mft_no);
put_err:
ntfs_attr_search_ctx_put(ctx);
unm_err:
ntfs_mft_record_unmap(dir_ni);
err:
if (name)
OSFree(name, sizeof(*name), ntfs_malloc_tag);
lck_rw_unlock_shared(&ia_ni->lock);
(void)vnode_put(ia_vn);
if (!err)
err = EIO;
ntfs_debug("Failed (error %d).", err);
return err;
}
static inline int ntfs_do_dirent(ntfs_volume *vol, INDEX_ENTRY *ie,
struct dirent *de, uio_t uio, int *entries)
{
ino64_t mref;
u8 *utf8_name;
size_t utf8_size;
signed res_size, padding;
int err;
FILENAME_TYPE_FLAGS name_type;
#ifdef DEBUG
static const char *dts[15] = { "UNKNOWN", "FIFO", "CHR", "UNKNOWN",
"DIR", "UNKNOWN", "BLK", "UNKNOWN", "REG", "UNKNOWN",
"LNK", "UNKNOWN", "SOCK", "UNKNOWN", "WHT" };
#endif
name_type = ie->key.filename.filename_type;
if (name_type == FILENAME_DOS) {
ntfs_debug("Skipping DOS namespace entry.");
return 0;
}
mref = MREF_LE(ie->indexed_file);
if (mref < FILE_first_user) {
ntfs_debug("Removing core NTFS system file (mft_no 0x%x) from "
"name space.", (unsigned)mref);
return 0;
}
if (sizeof(de->d_ino) < 8 && mref & 0xffffffff00000000ULL) {
ntfs_warning(vol->mp, "Skipping dirent because its inode "
"number 0x%llx does not fit in 32-bits.",
(unsigned long long)mref);
return 0;
}
utf8_name = (u8*)de->d_name;
utf8_size = sizeof(de->d_name);
res_size = ntfs_to_utf8(vol, (ntfschar*)&ie->key.filename.filename,
ie->key.filename.filename_length << NTFSCHAR_SIZE_SHIFT,
&utf8_name, &utf8_size);
if (res_size <= 0) {
ntfs_warning(vol->mp, "Skipping unrepresentable inode 0x%llx "
"(error %d).", (unsigned long long)mref,
-res_size);
return 0;
}
de->d_ino = mref;
if (ie->key.filename.file_attributes &
FILE_ATTR_DUP_FILENAME_INDEX_PRESENT)
de->d_type = DT_DIR;
else {
if (ie->key.filename.data_size > MAXPATHLEN)
de->d_type = DT_REG;
else
de->d_type = DT_UNKNOWN;
}
if (res_size > 0xff)
panic("%s(): res_size (0x%x) does not fit in 8 bits. This is "
"a bug!", __FUNCTION__, res_size);
de->d_namlen = res_size;
res_size += offsetof(struct dirent, d_name) + 1;
de->d_reclen = (u16)(res_size + 3) & (u16)~3;
padding = de->d_reclen - res_size;
if (padding)
bzero((u8*)de + res_size, padding);
if (uio_resid(uio) < de->d_reclen)
return -1;
ntfs_debug("Returning dirent with d_ino 0x%llx, d_reclen 0x%x, d_type "
"DT_%s, d_namlen %d, d_name \"%s\".",
(unsigned long long)mref, (unsigned)de->d_reclen,
de->d_type < 15 ? dts[de->d_type] : dts[0],
(unsigned)de->d_namlen, de->d_name);
err = uiomove((caddr_t)de, de->d_reclen, uio);
if (!err) {
(*entries)++;
}
return err;
}
static ntfs_dirhint *ntfs_dirhint_get(ntfs_inode *ni, unsigned ofs)
{
ntfs_dirhint *dh;
BOOL need_init, need_remove;
struct timeval tv;
microuptime(&tv);
dh = NULL;
if (ofs & ~NTFS_DIR_POS_MASK) {
TAILQ_FOREACH(dh, &ni->dirhint_list, link) {
if (dh->ofs == ofs)
break;
}
}
need_init = FALSE;
need_remove = TRUE;
if (!dh) {
need_init = TRUE;
if (ni->nr_dirhints < NTFS_MAX_DIRHINTS) {
dh = OSMalloc(sizeof(*dh), ntfs_malloc_tag);
if (dh) {
ni->nr_dirhints++;
need_remove = FALSE;
}
}
if (!dh) {
dh = TAILQ_LAST(&ni->dirhint_list, ntfs_dirhint_head);
if (dh && dh->fn_size)
OSFree(dh->fn, dh->fn_size, ntfs_malloc_tag);
}
}
if (dh) {
if (need_remove)
TAILQ_REMOVE(&ni->dirhint_list, dh, link);
TAILQ_INSERT_HEAD(&ni->dirhint_list, dh, link);
if (need_init) {
dh->ofs = ofs;
dh->fn_size = 0;
}
dh->time = tv.tv_sec;
}
return dh;
}
static void ntfs_dirhint_put(ntfs_inode *ni, ntfs_dirhint *dh)
{
TAILQ_REMOVE(&ni->dirhint_list, dh, link);
ni->nr_dirhints--;
if (dh->fn_size)
OSFree(dh->fn, dh->fn_size, ntfs_malloc_tag);
OSFree(dh, sizeof(*dh), ntfs_malloc_tag);
}
void ntfs_dirhints_put(ntfs_inode *ni, BOOL stale_only)
{
ntfs_dirhint *dh, *tdh;
struct timeval tv;
if (stale_only)
microuptime(&tv);
TAILQ_FOREACH_REVERSE_SAFE(dh, &ni->dirhint_list, ntfs_dirhint_head,
link, tdh) {
if (stale_only) {
if (tv.tv_sec - dh->time < NTFS_DIRHINT_TTL)
break;
}
ntfs_dirhint_put(ni, dh);
}
}
errno_t ntfs_readdir(ntfs_inode *dir_ni, uio_t uio, int *eofflag,
int *numdirent)
{
off_t ofs;
ntfs_volume *vol;
struct dirent *de;
ntfs_inode *ia_ni;
ntfs_index_context *ictx;
ntfs_dirhint *dh;
int eof, entries, err;
unsigned tag;
u8 de_buf[sizeof(struct dirent) + 4];
ofs = uio_offset(uio);
vol = dir_ni->vol;
de = (struct dirent*)&de_buf;
ia_ni = NULL;
ictx = NULL;
dh = NULL;
err = entries = eof = tag = 0;
ntfs_debug("Entering for directory inode 0x%llx, offset 0x%llx, count "
"0x%llx.", (unsigned long long)dir_ni->mft_no,
(unsigned long long)ofs,
(unsigned long long)uio_resid(uio));
if ((unsigned)ofs == (unsigned)-1)
goto eof;
tag = (unsigned)ofs & NTFS_DIR_TAG_MASK;
ofs &= NTFS_DIR_POS_MASK;
if (uio_resid(uio) < (unsigned)offsetof(struct dirent, d_name) + 4) {
err = EINVAL;
goto err;
}
while (ofs < 2) {
if (!dir_ni->link_count) {
ofs = 2;
break;
}
*(u32*)de->d_name = 0;
de->d_name[0] = '.';
if (!ofs) {
if (dir_ni->mft_no == FILE_root)
de->d_ino = 2;
else {
if (sizeof(de->d_ino) < 8 && dir_ni->mft_no &
0xffffffff00000000ULL) {
ntfs_warning(vol->mp, "Skipping "
"emulated dirent for "
"\".\" because its "
"inode number 0x%llx "
"does not fit in "
"32-bits.",
(unsigned long long)
dir_ni->mft_no);
goto do_next;
}
de->d_ino = dir_ni->mft_no;
}
de->d_namlen = 1;
} else {
vnode_t parent_vn;
if (dir_ni->mft_no == FILE_root)
de->d_ino = 1;
else if ((parent_vn = vnode_getparent(dir_ni->vn))) {
if (sizeof(de->d_ino) < 8 &&
NTFS_I(parent_vn)->mft_no &
0xffffffff00000000ULL) {
ntfs_warning(vol->mp, "Skipping "
"emulated dirent for "
"\"..\" because its "
"inode number 0x%llx "
"does not fit in "
"32-bits.",
(unsigned long long)
NTFS_I(parent_vn)->
mft_no);
goto do_next;
}
de->d_ino = NTFS_I(parent_vn)->mft_no;
if (de->d_ino == FILE_root)
de->d_ino = 2;
(void)vnode_put(parent_vn);
} else {
MFT_REF mref;
err = ntfs_inode_get_name_and_parent_mref(
dir_ni, FALSE, &mref, NULL);
if (err) {
ntfs_warning(vol->mp, "Skipping "
"emulated dirent for "
"\"..\" because its "
"inode number could "
"not be determined "
"(error %d).", err);
goto do_next;
}
if (sizeof(de->d_ino) < 8 && MREF(mref) &
0xffffffff00000000ULL) {
ntfs_warning(vol->mp, "Skipping "
"emulated dirent for "
"\"..\" because its "
"inode number 0x%llx "
"does not fit in "
"32-bits.",
(unsigned long long)
MREF(mref));
goto do_next;
}
de->d_ino = MREF(mref);
if (de->d_ino == FILE_root)
de->d_ino = 2;
}
de->d_namlen = 2;
de->d_name[1] = '.';
}
de->d_reclen = offsetof(struct dirent, d_name) + 4;
de->d_type = DT_DIR;
ntfs_debug("Returning emulated \"%s\" dirent with d_ino "
"0x%llx, d_reclen 0x%x, d_type DT_DIR, "
"d_namlen %d.", de->d_name,
(unsigned long long)de->d_ino,
(unsigned)de->d_reclen,
(unsigned)de->d_namlen);
err = uiomove((caddr_t)de, de->d_reclen, uio);
if (err) {
ntfs_error(vol->mp, "uiomove() failed for emulated "
"entry (error %d).", err);
goto err;
}
entries++;
do_next:
ofs++;
if (uio_resid(uio) < (unsigned)offsetof(struct dirent, d_name)
+ 4) {
err = -1;
goto done;
}
}
err = ntfs_index_inode_get(dir_ni, I30, 4, FALSE, &ia_ni);
if (err) {
ntfs_error(vol->mp, "Failed to get index vnode (error %d).",
err);
ia_ni = NULL;
goto err;
}
lck_rw_lock_exclusive(&ia_ni->lock);
ictx = ntfs_index_ctx_get(ia_ni);
if (!ictx) {
ntfs_error(vol->mp, "Not enough memory to allocate index "
"context.");
err = ENOMEM;
goto err;
}
dh = ntfs_dirhint_get(ia_ni, ofs | tag);
if (!dh) {
tag = 0;
goto lookup_by_position;
}
if (!dh->fn_size)
goto lookup_by_position;
if (!dh->fn)
panic("%s(): !dh->fn\n", __FUNCTION__);
err = ntfs_index_lookup(dh->fn, dh->fn_size, &ictx);
if (!err)
goto do_dirent;
if (err != ENOENT) {
ntfs_warning(vol->mp, "Failed to look up filename from "
"directory hint (error %d), using position in "
"the B+tree to continue the lookup.", err);
ntfs_index_ctx_reinit(ictx, ia_ni);
goto lookup_by_position;
}
err = 0;
if (!(ictx->entry->flags & INDEX_ENTRY_END)) {
ictx->is_match = 1;
goto do_dirent;
}
if (ictx->entry->flags & INDEX_ENTRY_NODE)
panic("%s(): ictx->entry->flags & INDEX_ENTRY_NODE\n",
__FUNCTION__);
do {
ntfs_index_context *itmp;
if (ictx->is_root)
goto eof;
itmp = ictx;
ictx = ictx->up;
ntfs_index_ctx_put_single(itmp);
} while (ictx->entry_nr == ictx->nr_entries - 1);
err = ntfs_index_ctx_relock(ictx);
if (err)
goto err;
ictx->is_match = 1;
goto do_dirent;
lookup_by_position:
err = ntfs_index_lookup_by_position(ofs - 2, 0, &ictx);
while (!err) {
do_dirent:
err = ntfs_do_dirent(vol, ictx->entry, de, uio, &entries);
if (err) {
if (err < 0)
goto done;
ntfs_error(vol->mp, "uiomove() failed for index %s "
"entry (error %d).",
ictx->is_root ? "root" : "allocation",
err);
goto err;
}
ofs++;
err = ntfs_index_lookup_next(&ictx);
}
if (err != ENOENT) {
ntfs_error(vol->mp, "Failed to look up index entry with "
"position 0x%llx.",
(unsigned long long)(ofs - 2));
goto err;
}
eof:
eof = 1;
ofs = (unsigned)-1;
done:
if (err < 0 && !entries)
err = EINVAL;
else
err = 0;
err:
if (!eof && ofs & ~(off_t)NTFS_DIR_POS_MASK) {
ofs = NTFS_DIR_POS_MASK;
tag = (unsigned)(++ia_ni->dirhint_tag) << NTFS_DIR_TAG_SHIFT;
if (!tag || (tag | NTFS_DIR_POS_MASK) == (unsigned)-1) {
ia_ni->dirhint_tag = 1;
tag = (unsigned)1 << NTFS_DIR_TAG_SHIFT;
}
}
if (dh) {
unsigned size;
if (eof || err) {
ntfs_dirhint_put(ia_ni, dh);
goto dh_done;
}
size = le16_to_cpu(ictx->entry->key_length);
if (dh->fn_size != size) {
if (dh->fn_size)
OSFree(dh->fn, dh->fn_size, ntfs_malloc_tag);
dh->fn = OSMalloc(size, ntfs_malloc_tag);
if (!dh->fn) {
dh->fn_size = 0;
ntfs_dirhint_put(ia_ni, dh);
tag = 0;
goto dh_done;
}
dh->fn_size = size;
}
memcpy(dh->fn, &ictx->entry->key.filename, size);
if (!tag) {
tag = (unsigned)(++ia_ni->dirhint_tag) <<
NTFS_DIR_TAG_SHIFT;
if (!tag || (tag | NTFS_DIR_POS_MASK) == (unsigned)-1) {
ia_ni->dirhint_tag = 1;
tag = (unsigned)1 << NTFS_DIR_TAG_SHIFT;
}
}
dh->ofs = ofs | tag;
}
dh_done:
if (ictx)
ntfs_index_ctx_put(ictx);
if (ia_ni) {
lck_rw_unlock_exclusive(&ia_ni->lock);
(void)vnode_put(ia_ni->vn);
}
ntfs_debug("%s (returned 0x%x entries, %s, now at offset 0x%llx).",
err ? "Failed" : "Done", entries, eof ?
"reached end of directory" : "more entries to follow",
(unsigned long long)ofs);
if (eofflag)
*eofflag = eof;
if (numdirent)
*numdirent = entries;
uio_setoffset(uio, ofs | tag);
return err;
}
errno_t ntfs_dir_is_empty(ntfs_inode *dir_ni)
{
s64 bmp_size, prev_ia_pos, bmp_pos, ia_pos;
ntfs_inode *ia_ni, *bmp_ni = NULL;
ntfs_volume *vol = dir_ni->vol;
MFT_RECORD *m;
ntfs_attr_search_ctx *ctx;
INDEX_ROOT *ir;
u8 *index_end, *bmp, *kaddr;
INDEX_ENTRY *ie;
upl_t bmp_upl, ia_upl = NULL;
upl_page_info_array_t bmp_pl, ia_pl;
INDEX_ALLOCATION *ia;
errno_t err;
int bmp_ofs;
static const char es[] = "%s. Directory mft_no 0x%llx is corrupt. "
"Run chkdsk.";
static const char es1[] = ". Directory mft_no 0x";
static const char es2[] = " is corrupt. Run chkdsk.";
ntfs_debug("Entering for directory mft_no 0x%llx.",
(unsigned long long)dir_ni->mft_no);
if (!S_ISDIR(dir_ni->mode))
return ENOTDIR;
err = ntfs_index_inode_get(dir_ni, I30, 4, FALSE, &ia_ni);
if (err) {
ntfs_error(vol->mp, "Failed to get index inode (error %d).",
err);
return err;
}
lck_rw_lock_shared(&ia_ni->lock);
if (NInoIndexAllocPresent(ia_ni)) {
err = ntfs_attr_inode_get(dir_ni, AT_BITMAP, I30, 4, FALSE,
LCK_RW_TYPE_SHARED, &bmp_ni);
if (err) {
ntfs_error(vol->mp, "Failed to get index bitmap inode "
"(error %d).", err);
bmp_ni = NULL;
goto err;
}
}
err = ntfs_mft_record_map(dir_ni, &m);
if (err) {
ntfs_error(vol->mp, "Failed to map mft record for directory "
"(error %d).", err);
goto err;
}
ctx = ntfs_attr_search_ctx_get(dir_ni, m);
if (!ctx) {
ntfs_error(vol->mp, "Failed to get attribute search context.");
err = ENOMEM;
goto unm_err;
}
err = ntfs_attr_lookup(AT_INDEX_ROOT, I30, 4, 0, NULL, 0, ctx);
if (err) {
if (err == ENOENT) {
ntfs_error(vol->mp, "Index root attribute missing in "
"directory inode 0x%llx.",
(unsigned long long)dir_ni->mft_no);
NVolSetErrors(vol);
err = EIO;
} else
ntfs_error(vol->mp, "Failed to lookup index root "
"attribute in directory inode 0x%llx "
"(error %d).",
(unsigned long long)dir_ni->mft_no,
err);
goto put_err;
}
ir = (INDEX_ROOT*)((u8*)ctx->a + le16_to_cpu(ctx->a->value_offset));
index_end = (u8*)&ir->index + le32_to_cpu(ir->index.index_length);
ie = (INDEX_ENTRY*)((u8*)&ir->index +
le32_to_cpu(ir->index.entries_offset));
if ((u8*)ie < (u8*)&ir->index ||
(u8*)ie + sizeof(INDEX_ENTRY_HEADER) > index_end ||
(u8*)ie + le16_to_cpu(ie->key_length) > index_end)
goto dir_err;
if (!(ie->flags & INDEX_ENTRY_END))
err = ENOTEMPTY;
else if (!(ie->flags & INDEX_ENTRY_NODE)) {
err = 1;
}
ntfs_attr_search_ctx_put(ctx);
ntfs_mft_record_unmap(dir_ni);
if (err) {
if (err == 1)
err = 0;
goto done;
}
if (!NInoIndexAllocPresent(ia_ni)) {
ntfs_error(vol->mp, "No index allocation attribute but index "
"entry requires one. Directory inode 0x%llx "
"is corrupt or driver bug.",
(unsigned long long)dir_ni->mft_no);
goto dir_err;
}
lck_spin_lock(&bmp_ni->size_lock);
bmp_size = bmp_ni->data_size;
lck_spin_unlock(&bmp_ni->size_lock);
ia_pos = bmp_pos = bmp_ofs = 0;
prev_ia_pos = -1;
get_next_bmp_page:
ntfs_debug("Reading index bitmap offset 0x%llx, bit offset 0x%x.",
(unsigned long long)bmp_pos >> 3, bmp_ofs);
err = ntfs_page_map(bmp_ni, (bmp_pos >> 3) & ~PAGE_MASK_64, &bmp_upl,
&bmp_pl, &bmp, FALSE);
if (err) {
ntfs_error(vol->mp, "Failed to read directory index bitmap "
"buffer (error %d).", err);
bmp_upl = NULL;
goto page_err;
}
while (!(bmp[bmp_ofs >> 3] & (1 << (bmp_ofs & 7)))) {
find_next_index_buffer:
bmp_ofs++;
if (((bmp_pos + bmp_ofs) >> 3) >= bmp_size)
goto unm_done;
ia_pos = (bmp_pos + bmp_ofs) << ia_ni->block_size_shift;
if ((bmp_ofs >> 3) >= PAGE_SIZE) {
ntfs_page_unmap(bmp_ni, bmp_upl, bmp_pl, FALSE);
bmp_pos += PAGE_SIZE * 8;
bmp_ofs = 0;
goto get_next_bmp_page;
}
}
ntfs_debug("Handling index allocation block 0x%llx.",
(unsigned long long)bmp_pos + bmp_ofs);
if ((prev_ia_pos & ~PAGE_MASK_64) != (ia_pos & ~PAGE_MASK_64)) {
prev_ia_pos = ia_pos;
if (ia_upl)
ntfs_page_unmap(ia_ni, ia_upl, ia_pl, FALSE);
err = ntfs_page_map(ia_ni, ia_pos & ~PAGE_MASK_64, &ia_upl,
&ia_pl, &kaddr, FALSE);
if (err) {
ntfs_error(vol->mp, "Failed to read directory index "
"allocation page (error %d).", err);
ia_upl = NULL;
goto page_err;
}
}
ia = (INDEX_ALLOCATION*)(kaddr + ((u32)ia_pos & PAGE_MASK &
~(ia_ni->block_size - 1)));
if ((u8*)ia < kaddr || (u8*)ia > kaddr + PAGE_SIZE) {
ntfs_error(vol->mp, es, "Out of bounds check failed",
(unsigned long long)dir_ni->mft_no);
goto vol_err;
}
if (!ntfs_is_indx_record(ia->magic)) {
ntfs_error(vol->mp, "Multi sector transfer error detected in "
"index record vcn 0x%llx%s%llx%s",
(unsigned long long)ia_pos >>
ia_ni->vcn_size_shift, es1,
(unsigned long long)dir_ni->mft_no, es2);
goto vol_err;
}
if (sle64_to_cpu(ia->index_block_vcn) != (ia_pos &
~(s64)(ia_ni->block_size - 1)) >>
ia_ni->vcn_size_shift) {
ntfs_error(vol->mp, "Actual VCN (0x%llx) of index record is "
"different from expected VCN (0x%llx)%s%llx%s",
(unsigned long long)
sle64_to_cpu(ia->index_block_vcn),
(unsigned long long)ia_pos >>
ia_ni->vcn_size_shift, es1,
(unsigned long long)dir_ni->mft_no, es2);
goto vol_err;
}
if (offsetof(INDEX_BLOCK, index) +
le32_to_cpu(ia->index.allocated_size) !=
ia_ni->block_size) {
ntfs_error(vol->mp, "Index buffer (VCN 0x%llx) has a size "
"(%u) differing from the directory specified "
"size (%u)%s%llx%s", (unsigned long long)
(unsigned long long)
sle64_to_cpu(ia->index_block_vcn),
(unsigned)(offsetof(INDEX_BLOCK, index) +
le32_to_cpu(ia->index.allocated_size)),
(unsigned)ia_ni->block_size, es1,
(unsigned long long)dir_ni->mft_no, es2);
goto vol_err;
}
index_end = (u8*)ia + ia_ni->block_size;
if (index_end > kaddr + PAGE_SIZE) {
ntfs_error(vol->mp, "Index buffer (VCN 0x%llx) of directory "
"inode 0x%llx crosses page boundary. This "
"cannot happen and points either to memory "
"corruption or to a driver bug.",
(unsigned long long)
sle64_to_cpu(ia->index_block_vcn),
(unsigned long long)dir_ni->mft_no);
goto vol_err;
}
index_end = (u8*)&ia->index + le32_to_cpu(ia->index.index_length);
if (index_end > (u8*)ia + ia_ni->block_size) {
ntfs_error(vol->mp, "Size of index block (VCN 0x%llx) "
"exceeds maximum size%s%llx%s",
(unsigned long long)
sle64_to_cpu(ia->index_block_vcn), es1,
(unsigned long long)dir_ni->mft_no, es2);
goto vol_err;
}
ie = (INDEX_ENTRY*)((u8*)&ia->index +
le32_to_cpu(ia->index.entries_offset));
if ((u8*)ie < (u8*)&ia->index ||
(u8*)ie + sizeof(INDEX_ENTRY_HEADER) > index_end ||
(u8*)ie + le16_to_cpu(ie->key_length) > index_end)
goto dir_err;
if (ie->flags & INDEX_ENTRY_END)
goto find_next_index_buffer;
err = ENOTEMPTY;
unm_done:
if (ia_upl)
ntfs_page_unmap(ia_ni, ia_upl, ia_pl, FALSE);
ntfs_page_unmap(bmp_ni, bmp_upl, bmp_pl, FALSE);
done:
if (bmp_ni) {
lck_rw_unlock_shared(&bmp_ni->lock);
(void)vnode_put(bmp_ni->vn);
}
lck_rw_unlock_shared(&ia_ni->lock);
(void)vnode_put(ia_ni->vn);
ntfs_debug("Done (directory is%s empty).", !err ? "" : " not");
return err;
dir_err:
ntfs_error(vol->mp, "Corrupt directory inode 0x%llx. Run chkdsk.",
(unsigned long long)dir_ni->mft_no);
NVolSetErrors(vol);
if (ia_upl)
goto page_err;
err = EIO;
put_err:
ntfs_attr_search_ctx_put(ctx);
unm_err:
ntfs_mft_record_unmap(dir_ni);
err:
if (bmp_ni) {
lck_rw_unlock_shared(&bmp_ni->lock);
(void)vnode_put(bmp_ni->vn);
}
lck_rw_unlock_shared(&ia_ni->lock);
(void)vnode_put(ia_ni->vn);
return err;
vol_err:
NVolSetErrors(vol);
page_err:
if (!err)
err = EIO;
if (ia_upl)
ntfs_page_unmap(ia_ni, ia_upl, ia_pl, FALSE);
if (bmp_upl)
ntfs_page_unmap(bmp_ni, bmp_upl, bmp_pl, FALSE);
goto err;
}
errno_t ntfs_dir_entry_delete(ntfs_inode *dir_ni, ntfs_inode *ni,
const FILENAME_ATTR *fn, const u32 fn_len)
{
ntfs_volume *vol = ni->vol;
ntfs_inode *ia_ni;
ntfs_index_context *ictx;
INDEX_ENTRY *ie;
int err;
FILENAME_TYPE_FLAGS fn_type;
ntfs_debug("Entering for mft_no 0x%llx, parent directory mft_no "
"0x%llx.", (unsigned long long)ni->mft_no,
(unsigned long long)dir_ni->mft_no);
if (!S_ISDIR(dir_ni->mode))
panic("%s(): !S_ISDIR(dir_ni->mode\n", __FUNCTION__);
if (fn->parent_directory != MK_LE_MREF(dir_ni->mft_no,
dir_ni->seq_no)) {
ntfs_error(vol->mp, "The reference of the parent directory "
"specified in the filename to be removed does "
"not match the reference of the parent "
"directory inode. Volume is corrupt. Run "
"chkdsk.");
NVolSetErrors(vol);
return EIO;
}
err = ntfs_index_inode_get(dir_ni, I30, 4, FALSE, &ia_ni);
if (err) {
ntfs_error(vol->mp, "Failed to get index vnode (error %d).",
err);
return EIO;
}
lck_rw_lock_exclusive(&ia_ni->lock);
ictx = ntfs_index_ctx_get(ia_ni);
if (!ictx) {
ntfs_error(vol->mp, "Not enough memory to allocate index "
"context.");
err = ENOMEM;
goto err;
}
restart:
err = ntfs_index_lookup(fn, fn_len, &ictx);
if (err) {
if (err == ENOENT) {
ntfs_error(vol->mp, "Failed to delete directory index "
"entry of mft_no 0x%llx because the "
"filename was not found in its parent "
"directory index. Directory 0x%llx "
"is corrupt. Run chkdsk.",
(unsigned long long)ni->mft_no,
(unsigned long long)dir_ni->mft_no);
NVolSetErrors(vol);
} else
ntfs_error(vol->mp, "Failed to delete directory index "
"entry of mft_no 0x%llx because "
"looking up the filename in its "
"parent directory 0x%llx failed "
"(error %d).",
(unsigned long long)ni->mft_no,
(unsigned long long)dir_ni->mft_no,
err);
goto put_err;
}
ie = ictx->entry;
if (fn->parent_directory != ie->key.filename.parent_directory) {
ntfs_error(vol->mp, "The reference of the parent directory "
"(0x%llx) specified in the filename to be "
"removed does not match the reference of the "
"parent directory (0x%llx) specified in the "
"matching directory index entry. Volume is "
"corrupt. Run chkdsk.", (unsigned long long)
le64_to_cpu(fn->parent_directory),
(unsigned long long)le64_to_cpu(
ie->key.filename.parent_directory));
NVolSetErrors(vol);
err = EIO;
goto put_err;
}
if (MK_LE_MREF(ni->mft_no, ni->seq_no) != ie->indexed_file) {
ntfs_error(vol->mp, "The reference of the inode (0x%llx) to "
"which the filename to be removed belongs "
"does not match the reference of the inode "
"(0x%llx) specified in the matching directory "
"index entry. Volume is corrupt. Run "
"chkdsk.", (unsigned long long)
MK_MREF(ni->mft_no, ni->seq_no),
(unsigned long long)
le64_to_cpu(ie->indexed_file));
NVolSetErrors(vol);
err = EIO;
goto put_err;
}
fn_type = ie->key.filename.filename_type;
err = ntfs_index_entry_delete(ictx);
if (!err) {
ntfs_index_ctx_put(ictx);
dir_ni->last_mft_change_time = dir_ni->last_data_change_time =
ntfs_utc_current_time();
NInoSetDirtyTimes(dir_ni);
lck_rw_unlock_exclusive(&ia_ni->lock);
(void)vnode_put(ia_ni->vn);
ntfs_debug("Done.");
return 0;
}
if (err == -EAGAIN) {
ntfs_debug("Restarting delete as tree was rearranged.");
ntfs_index_ctx_reinit(ictx, ia_ni);
goto restart;
}
if (fn_type == FILENAME_WIN32 && fn->filename_type == FILENAME_POSIX) {
errno_t err2;
ntfs_debug("Switching namespace of directory index entry from "
"WIN32 to POSIX to match the namespace of the "
"corresponding filename attribute.");
ntfs_index_ctx_reinit(ictx, ia_ni);
err2 = ntfs_index_lookup(fn, fn_len, &ictx);
if (err2) {
ntfs_error(vol->mp, "Failed to switch namespace of "
"directory index entry of inode "
"0x%llx from WIN32 to POSIX because "
"re-looking up the filename in its "
"parent directory inode 0x%llx failed "
"(error %d). Leaving inconsistent "
"metadata. Run chkdsk.",
(unsigned long long)ni->mft_no,
(unsigned long long)dir_ni->mft_no,
err2);
NVolSetErrors(vol);
goto put_err;
}
ictx->entry->key.filename.filename_type = FILENAME_POSIX;
ntfs_index_entry_mark_dirty(ictx);
dir_ni->last_mft_change_time = dir_ni->last_data_change_time =
ntfs_utc_current_time();
NInoSetDirtyTimes(dir_ni);
}
put_err:
ntfs_index_ctx_put(ictx);
err:
lck_rw_unlock_exclusive(&ia_ni->lock);
(void)vnode_put(ia_ni->vn);
ntfs_debug("Failed (error %d).", err);
return err;
}
errno_t ntfs_dir_entry_add(ntfs_inode *dir_ni, const FILENAME_ATTR *fn,
const u32 fn_len, const leMFT_REF mref)
{
const leMFT_REF tmp_mref = mref;
ntfs_inode *ia_ni;
ntfs_index_context *ictx;
errno_t err;
ntfs_debug("Entering for mft_no 0x%llx, parent directory mft_no "
"0x%llx.", (unsigned long long)MREF_LE(tmp_mref),
(unsigned long long)dir_ni->mft_no);
if (!S_ISDIR(dir_ni->mode))
panic("%s(): !S_ISDIR(dir_ni->mode\n", __FUNCTION__);
err = ntfs_index_inode_get(dir_ni, I30, 4, FALSE, &ia_ni);
if (err) {
ntfs_error(dir_ni->vol->mp, "Failed to get index vnode (error "
"%d).", err);
return err;
}
lck_rw_lock_exclusive(&ia_ni->lock);
ictx = ntfs_index_ctx_get(ia_ni);
if (!ictx) {
ntfs_error(dir_ni->vol->mp, "Not enough memory to allocate "
"index context.");
err = ENOMEM;
goto err;
}
err = ntfs_index_lookup(fn, fn_len, &ictx);
if (err != ENOENT) {
if (!err) {
ntfs_debug("Failed (filename already present in "
"directory index).");
err = EEXIST;
} else
ntfs_error(dir_ni->vol->mp, "Failed to add directory "
"index entry of mft_no 0x%llx to "
"directory mft_no 0x%llx because "
"looking up the filename in the "
"directory index failed (error %d).",
(unsigned long long)MREF_LE(tmp_mref),
(unsigned long long)dir_ni->mft_no,
err);
ntfs_index_ctx_put(ictx);
goto err;
}
err = ntfs_index_entry_add(ictx, fn, fn_len, &tmp_mref, 0);
ntfs_index_ctx_put(ictx);
if (!err) {
lck_rw_unlock_exclusive(&ia_ni->lock);
(void)vnode_put(ia_ni->vn);
dir_ni->last_mft_change_time = dir_ni->last_data_change_time =
ntfs_utc_current_time();
NInoSetDirtyTimes(dir_ni);
ntfs_debug("Done.");
return 0;
}
err:
lck_rw_unlock_exclusive(&ia_ni->lock);
(void)vnode_put(ia_ni->vn);
return err;
}