#include <sys/cdefs.h>
#include <sys/errno.h>
#include <sys/kernel_types.h>
#include <sys/queue.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/ucred.h>
#include <sys/ubc.h>
#include <sys/vnode.h>
#include <string.h>
#include <libkern/libkern.h>
#include <libkern/OSAtomic.h>
#include <libkern/OSMalloc.h>
#include <kern/debug.h>
#include <kern/locks.h>
#include <mach/machine/vm_param.h>
#include "ntfs.h"
#include "ntfs_attr.h"
#include "ntfs_debug.h"
#include "ntfs_dir.h"
#include "ntfs_hash.h"
#include "ntfs_inode.h"
#include "ntfs_mft.h"
#include "ntfs_page.h"
#include "ntfs_runlist.h"
#include "ntfs_sfm.h"
#include "ntfs_time.h"
#include "ntfs_types.h"
#include "ntfs_unistr.h"
#include "ntfs_volume.h"
#include "ntfs_vnops.h"
BOOL ntfs_inode_test(ntfs_inode *ni, const ntfs_attr *na)
{
if (ni->mft_no != na->mft_no)
return FALSE;
if (!NInoAttr(ni)) {
if (na->type != AT_UNUSED)
return FALSE;
} else {
ntfs_volume *vol;
if (ni->type != na->type)
return FALSE;
vol = ni->vol;
if (!ntfs_are_names_equal(ni->name, ni->name_len,
na->name, na->name_len, NVolCaseSensitive(vol),
vol->upcase, vol->upcase_len))
return FALSE;
}
if ((BOOL)NInoRaw(ni) != na->raw)
return FALSE;
return TRUE;
}
static inline void __ntfs_inode_init(ntfs_volume *vol, ntfs_inode *ni)
{
ni->vol = vol;
ni->vn = NULL;
ni->nr_refs = 0;
ni->nr_opens = 0;
lck_rw_init(&ni->lock, ntfs_lock_grp, ntfs_lock_attr);
ni->block_size = vol->sector_size;
ni->block_size_shift = vol->sector_size_shift;
lck_spin_init(&ni->size_lock, ntfs_lock_grp, ntfs_lock_attr);
ni->allocated_size = ni->data_size = ni->initialized_size = 0;
ni->seq_no = 0;
ni->link_count = 0;
ni->uid = vol->uid;
ni->gid = vol->gid;
ni->mode = 0;
ni->rdev = (dev_t)0;
ni->file_attributes = 0;
ni->last_access_time = ni->last_mft_change_time =
ni->last_data_change_time = ni->creation_time =
(struct timespec) {
.tv_sec = 0,
.tv_nsec = 0,
};
ntfs_rl_init(&ni->rl);
ni->m_buf = NULL;
ni->m = NULL;
ni->attr_list_size = 0;
ni->attr_list_alloc = 0;
ni->attr_list = NULL;
ntfs_rl_init(&ni->attr_list_rl);
ni->last_set_bit = -1;
ni->vcn_size = 0;
ni->collation_rule = 0;
ni->vcn_size_shift = 0;
ni->nr_dirhints = 0;
ni->dirhint_tag = 0;
TAILQ_INIT(&ni->dirhint_list);
lck_mtx_init(&ni->extent_lock, ntfs_lock_grp, ntfs_lock_attr);
ni->nr_extents = 0;
ni->extent_alloc = 0;
ni->base_ni = NULL;
}
errno_t ntfs_inode_init(ntfs_volume *vol, ntfs_inode *ni, const ntfs_attr *na)
{
ni->flags = (1 << NI_Locked) | (1 << NI_Alloc);
ni->mft_no = na->mft_no;
ni->type = na->type;
if (na->type == AT_INDEX_ALLOCATION)
NInoSetMstProtected(ni);
ni->name = na->name;
ni->name_len = na->name_len;
if (na->raw)
NInoSetRaw(ni);
__ntfs_inode_init(vol, ni);
if (na->type == AT_UNUSED)
return 0;
NInoSetAttr(ni);
if (na->name_len && na->name != I30 &&
na->name != NTFS_SFM_RESOURCEFORK_NAME &&
na->name != NTFS_SFM_AFPINFO_NAME) {
unsigned i = na->name_len * sizeof(ntfschar);
ni->name = OSMalloc(i + sizeof(ntfschar), ntfs_malloc_tag);
if (!ni->name)
return ENOMEM;
memcpy(ni->name, na->name, i);
ni->name[na->name_len] = 0;
}
return 0;
}
static errno_t ntfs_inode_read(ntfs_inode *ni);
static errno_t ntfs_attr_inode_read_or_create(ntfs_inode *base_ni,
ntfs_inode *ni, const int options);
static errno_t ntfs_index_inode_read(ntfs_inode *base_ni, ntfs_inode *ni);
static inline enum vtype ntfs_inode_get_vtype(ntfs_inode *ni)
{
if (NInoAttr(ni))
return VREG;
return IFTOVT(ni->mode);
}
errno_t ntfs_inode_add_vnode_attr(ntfs_inode *ni, const BOOL is_system,
vnode_t parent_vn, struct componentname *cn, BOOL isstream)
{
s64 data_size;
errno_t err;
enum vtype vtype;
struct vnode_fsparam vn_fsp;
BOOL cache_name = FALSE;
ntfs_debug("Entering.");
vtype = ntfs_inode_get_vtype(ni);
data_size = 0;
if (vtype == VREG || vtype == VDIR || vtype == VLNK)
data_size = ni->data_size;
vn_fsp = (struct vnode_fsparam) {
.vnfs_mp = ni->vol->mp,
.vnfs_vtype = vtype,
.vnfs_str = "ntfs",
.vnfs_dvp = parent_vn,
.vnfs_fsnode = ni,
.vnfs_vops = ntfs_vnodeop_p,
.vnfs_markroot = ((ni->mft_no != FILE_root) || NInoAttr(ni)) ?
0 : 1,
.vnfs_marksystem = is_system ? 1 : 0,
.vnfs_rdev = ni->rdev,
.vnfs_filesize = data_size,
.vnfs_cnp = cn,
.vnfs_flags = VNFS_ADDFSREF,
};
if (cn && cn->cn_flags & MAKEENTRY) {
cn->cn_flags &= ~MAKEENTRY;
cache_name = TRUE;
}
if (!parent_vn || !cache_name)
vn_fsp.vnfs_flags |= VNFS_NOCACHE;
if (isstream) {
vn_fsp.vnfs_dvp = NULL;
}
err = vnode_create(VNCREATE_FLAVOR, VCREATESIZE, &vn_fsp, &ni->vn);
if (!err) {
vnode_t vn = ni->vn;
vnode_settag(vn, VT_OTHER);
ntfs_debug("Done.");
return err;
}
ntfs_debug("Failed (error %d).", err);
return err;
}
errno_t ntfs_inode_get(ntfs_volume *vol, ino64_t mft_no, const BOOL is_system,
const lck_rw_type_t lock, ntfs_inode **nni, vnode_t parent_vn,
struct componentname *cn)
{
ntfs_inode *ni;
vnode_t vn;
errno_t err;
ntfs_attr na;
ntfs_debug("Entering for mft_no 0x%llx, is_system is %s, lock 0x%x.",
(unsigned long long)mft_no,
is_system ? "true" : "false", (unsigned)lock);
retry:
na = (ntfs_attr) {
.mft_no = mft_no,
.type = AT_UNUSED,
.raw = FALSE,
};
ni = ntfs_inode_hash_get(vol, &na);
if (!ni) {
ntfs_debug("Failed (ENOMEM).");
return ENOMEM;
}
switch (lock) {
case LCK_RW_TYPE_EXCLUSIVE:
lck_rw_lock_exclusive(&ni->lock);
break;
case LCK_RW_TYPE_SHARED:
lck_rw_lock_shared(&ni->lock);
break;
case 0:
if (NInoAlloc(ni))
panic("%s(): !lock but NInoAlloc(ni)\n", __FUNCTION__);
break;
default:
panic("%s(): lock is 0x%x which is invalid!\n", __FUNCTION__,
lock);
}
if (!NInoAlloc(ni)) {
vn = ni->vn;
if (!ni->link_count) {
ntfs_debug("Mft_no 0x%llx has been unlinked, "
"returning ENOENT.",
(unsigned long long)ni->mft_no);
err = ENOENT;
goto err;
}
if (NInoDeleted(ni)) {
if (lock == LCK_RW_TYPE_EXCLUSIVE)
lck_rw_unlock_exclusive(&ni->lock);
else if (lock == LCK_RW_TYPE_SHARED)
lck_rw_unlock_shared(&ni->lock);
if (vn) {
cache_purge(vn);
(void)vnode_put(vn);
} else
ntfs_inode_reclaim(ni);
goto retry;
}
if (vn) {
vnode_t old_parent_vn;
const char *old_name;
if (is_system && !vnode_issystem(vn))
panic("%s(): mft_no 0x%llx, is_system is TRUE "
"but vnode exists and is not "
"marked VSYSTEM\n",
__FUNCTION__,
(unsigned long long)mft_no);
old_parent_vn = vnode_getparent(vn);
old_name = vnode_getname(vn);
if (ni->link_count > 1 || !old_parent_vn || !old_name) {
char *name = NULL;
int len, hash, flags;
flags = hash = len = 0;
if (parent_vn && old_parent_vn != parent_vn) {
ntfs_debug("Updating vnode identity "
"with new parent "
"vnode.");
flags |= VNODE_UPDATE_PARENT;
}
if (cn && (!old_name ||
(long)strlen(old_name) !=
cn->cn_namelen ||
bcmp(old_name, cn->cn_nameptr,
cn->cn_namelen))) {
ntfs_debug("Updating vnode identity "
"with new name.");
name = cn->cn_nameptr;
len = cn->cn_namelen;
hash = cn->cn_hash;
flags |= VNODE_UPDATE_NAME |
VNODE_UPDATE_CACHE;
}
if (flags)
vnode_update_identity(vn, parent_vn,
name, len, hash, flags);
}
if (old_name)
(void)vnode_putname(old_name);
if (!parent_vn)
parent_vn = old_parent_vn;
if (cn && cn->cn_flags & MAKEENTRY) {
if (parent_vn)
cache_enter(parent_vn, vn, cn);
cn->cn_flags &= ~MAKEENTRY;
}
if (old_parent_vn)
(void)vnode_put(old_parent_vn);
}
*nni = ni;
ntfs_debug("Done (found in cache).");
return 0;
}
err = ntfs_inode_read(ni);
if (!err)
err = ntfs_inode_add_vnode(ni, is_system, parent_vn, cn);
if (!err) {
if (S_ISDIR(ni->mode)) {
ntfs_inode *ini;
err = ntfs_index_inode_get(ni, I30, 4, is_system, &ini);
if (err) {
ntfs_error(vol->mp, "Failed to get index "
"inode.");
vn = ni->vn;
(void)vnode_recycle(vn);
goto err;
}
lck_spin_lock(&ini->size_lock);
ni->allocated_size = ini->allocated_size;
ni->data_size = ini->data_size;
ni->initialized_size = ini->initialized_size;
lck_spin_unlock(&ini->size_lock);
(void)vnode_put(ini->vn);
}
ntfs_inode_unlock_alloc(ni);
*nni = ni;
ntfs_debug("Done (added to cache).");
return err;
}
if (lock == LCK_RW_TYPE_EXCLUSIVE)
lck_rw_unlock_exclusive(&ni->lock);
else if (lock == LCK_RW_TYPE_SHARED)
lck_rw_unlock_shared(&ni->lock);
ntfs_inode_reclaim(ni);
ntfs_debug("Failed (inode read/vnode create).");
return err;
err:
if (lock == LCK_RW_TYPE_EXCLUSIVE)
lck_rw_unlock_exclusive(&ni->lock);
else if (lock == LCK_RW_TYPE_SHARED)
lck_rw_unlock_shared(&ni->lock);
if (vn)
(void)vnode_put(vn);
else
ntfs_inode_reclaim(ni);
return err;
}
errno_t ntfs_attr_inode_lookup(ntfs_inode *base_ni, ATTR_TYPE type,
ntfschar *name, u32 name_len, const BOOL raw, ntfs_inode **nni)
{
ntfs_inode *ni;
ntfs_attr na;
ntfs_debug("Entering for mft_no 0x%llx, type 0x%x, name_len 0x%x, "
"raw is %s.", (unsigned long long)base_ni->mft_no,
le32_to_cpu(type), (unsigned)name_len,
raw ? "true" : "false");
if (type == AT_INDEX_ALLOCATION)
panic("%s() called for an index.\n", __FUNCTION__);
if (!base_ni->vn)
panic("%s() called with a base inode that does not have a "
"vnode attached.\n", __FUNCTION__);
na = (ntfs_attr) {
.mft_no = base_ni->mft_no,
.type = type,
.name = name,
.name_len = name_len,
.raw = raw,
};
ni = ntfs_inode_hash_lookup(base_ni->vol, &na);
if (!ni) {
ntfs_debug("Not cached (ENOENT).");
return ENOENT;
}
*nni = ni;
ntfs_debug("Done (found in cache).");
return 0;
}
errno_t ntfs_attr_inode_get_or_create(ntfs_inode *base_ni, ATTR_TYPE type,
ntfschar *name, u32 name_len, const BOOL is_system,
const BOOL raw, const int options, const lck_rw_type_t lock,
ntfs_inode **nni)
{
ntfs_inode *ni;
vnode_t vn;
int err;
BOOL promoted;
ntfs_attr na;
BOOL isstream = FALSE;
ntfs_debug("Entering for mft_no 0x%llx, type 0x%x, name_len 0x%x, "
"is_system is %s, raw is %s, options 0x%x, lock 0x%x.",
(unsigned long long)base_ni->mft_no, le32_to_cpu(type),
(unsigned)name_len, is_system ? "true" : "false",
raw ? "true" : "false", (unsigned)options,
(unsigned)lock);
if (type == AT_INDEX_ALLOCATION)
panic("%s() called for an index.\n", __FUNCTION__);
if (!base_ni->vn)
panic("%s() called with a base inode that does not have a "
"vnode attached.\n", __FUNCTION__);
promoted = FALSE;
retry:
na = (ntfs_attr) {
.mft_no = base_ni->mft_no,
.type = type,
.name = name,
.name_len = name_len,
.raw = raw,
};
ni = ntfs_inode_hash_get(base_ni->vol, &na);
if (!ni) {
ntfs_debug("Failed (ENOMEM).");
return ENOMEM;
}
if (lock) {
if (promoted || lock == LCK_RW_TYPE_EXCLUSIVE)
lck_rw_lock_exclusive(&ni->lock);
else if (lock == LCK_RW_TYPE_SHARED)
lck_rw_lock_shared(&ni->lock);
else
panic("%s(): lock is 0x%x which is invalid!\n",
__FUNCTION__, lock);
} else if (NInoAlloc(ni))
panic("%s(): !lock but NInoAlloc(ni)\n", __FUNCTION__);
if (!NInoAlloc(ni)) {
vn = ni->vn;
if (NInoDeleted(ni) || !ni->link_count) {
if (NInoDeleted(ni)) {
if (vn)
cache_purge(vn);
}
if (options & XATTR_REPLACE) {
ntfs_debug("Attribute in mft_no 0x%llx is "
"deleted/unlinked, returning "
"ENOENT.",
(unsigned long long)ni->mft_no);
err = ENOENT;
goto err;
}
relocked:
if (NInoDeleted(ni)) {
if (lock) {
if (promoted || lock ==
LCK_RW_TYPE_EXCLUSIVE)
lck_rw_unlock_exclusive(
&ni->lock);
else
lck_rw_unlock_shared(&ni->lock);
}
if (vn)
(void)vnode_put(vn);
else
ntfs_inode_reclaim(ni);
goto retry;
}
if (lock == LCK_RW_TYPE_SHARED && !promoted) {
promoted = TRUE;
if (!lck_rw_lock_shared_to_exclusive(
&ni->lock)) {
lck_rw_lock_exclusive(&ni->lock);
goto relocked;
}
}
if (ni->link_count) {
goto exists;
}
ntfs_debug("Re-instantiating open-unlinked attribute "
"in mft_no 0x%llx.",
(unsigned long long)ni->mft_no);
ni->link_count = 1;
err = ntfs_attr_resize(ni, 0, 0, NULL);
if (err) {
ntfs_error(ni->vol->mp, "Failed to truncate "
"re-linked attribute in "
"mft_no 0x%llx (error %d).",
(unsigned long long)ni->mft_no,
err);
goto err;
}
} else {
exists:
if (name == NTFS_SFM_RESOURCEFORK_NAME) {
s64 size;
if (vn)
size = ubc_getsize(vn);
else {
lck_spin_lock(&ni->size_lock);
size = ni->data_size;
lck_spin_unlock(&ni->size_lock);
}
if (!size) {
if (options & XATTR_REPLACE) {
ntfs_debug("Attribute mft_no "
"0x%llx does "
"not exist, "
"returning "
"ENOENT.",
(unsigned long
long)
ni->mft_no);
err = ENOENT;
goto err;
}
if (options & XATTR_CREATE)
goto allow_rsrc_fork;
}
}
if (options & XATTR_CREATE) {
ntfs_debug("Attribute mft_no 0x%llx already "
"exists, returning EEXIST.",
(unsigned long long)ni->mft_no);
err = EEXIST;
goto err;
}
}
allow_rsrc_fork:
if (vn) {
vnode_t parent_vn;
parent_vn = vnode_getparent(vn);
if (parent_vn != base_ni->vn) {
ntfs_debug("Updating vnode identity with new "
"parent vnode.");
vnode_update_identity(vn, base_ni->vn, NULL,
0, 0, VNODE_UPDATE_PARENT);
}
if (parent_vn)
(void)vnode_put(parent_vn);
}
if (promoted)
lck_rw_lock_exclusive_to_shared(&ni->lock);
*nni = ni;
ntfs_debug("Done (found in cache).");
return 0;
}
if (promoted)
lck_rw_lock_exclusive_to_shared(&ni->lock);
err = ntfs_attr_inode_read_or_create(base_ni, ni, options);
if (!err) {
if (name == NTFS_SFM_RESOURCEFORK_NAME)
isstream = TRUE;
err = ntfs_inode_add_vnode_attr(ni, is_system, base_ni->vn, NULL, isstream);
}
if (!err) {
ntfs_inode_unlock_alloc(ni);
*nni = ni;
ntfs_debug("Done (added to cache).");
return err;
}
if (lock) {
if (lock == LCK_RW_TYPE_SHARED)
lck_rw_unlock_shared(&ni->lock);
else
lck_rw_unlock_exclusive(&ni->lock);
}
ntfs_inode_reclaim(ni);
ntfs_debug("Failed (inode read/vnode create, error %d).", err);
return err;
err:
if (lock) {
if (promoted || lock == LCK_RW_TYPE_EXCLUSIVE)
lck_rw_unlock_exclusive(&ni->lock);
else
lck_rw_unlock_shared(&ni->lock);
}
if (vn)
(void)vnode_put(vn);
else
ntfs_inode_reclaim(ni);
return err;
}
errno_t ntfs_index_inode_get(ntfs_inode *base_ni, ntfschar *name, u32 name_len,
const BOOL is_system, ntfs_inode **nni)
{
ntfs_inode *ni;
ntfs_attr na;
int err;
ntfs_debug("Entering for mft_no 0x%llx, name_len 0x%x, is_system is "
"%s.", (unsigned long long)base_ni->mft_no,
(unsigned)name_len, is_system ? "true" : "false");
if (!base_ni->vn)
panic("%s() called with a base inode that does not have a "
"vnode attached.\n", __FUNCTION__);
na = (ntfs_attr) {
.mft_no = base_ni->mft_no,
.type = AT_INDEX_ALLOCATION,
.name = name,
.name_len = name_len,
.raw = FALSE,
};
ni = ntfs_inode_hash_get(base_ni->vol, &na);
if (!ni) {
ntfs_debug("Failed (ENOMEM).");
return ENOMEM;
}
if (!NInoAlloc(ni)) {
vnode_t vn;
vn = ni->vn;
if (!ni->link_count) {
ntfs_debug("Mft_no 0x%llx has been unlinked, "
"returning ENOENT.",
(unsigned long long)ni->mft_no);
if (vn)
(void)vnode_put(vn);
else
ntfs_inode_reclaim(ni);
return ENOENT;
}
if (vn) {
vnode_t parent_vn;
parent_vn = vnode_getparent(vn);
if (parent_vn != base_ni->vn) {
ntfs_debug("Updating vnode identity with new "
"parent vnode.");
vnode_update_identity(vn, base_ni->vn, NULL,
0, 0, VNODE_UPDATE_PARENT);
}
if (parent_vn)
(void)vnode_put(parent_vn);
}
*nni = ni;
ntfs_debug("Done (found in cache).");
return 0;
}
err = ntfs_index_inode_read(base_ni, ni);
if (!err)
err = ntfs_inode_add_vnode(ni, is_system, base_ni->vn, NULL);
if (!err) {
ntfs_inode_unlock_alloc(ni);
*nni = ni;
ntfs_debug("Done (added to cache).");
return err;
}
ntfs_inode_reclaim(ni);
ntfs_debug("Failed (inode read/vnode create).");
return err;
}
errno_t ntfs_extent_inode_get(ntfs_inode *base_ni, MFT_REF mref,
ntfs_inode **ext_ni)
{
ntfs_inode *ni;
ntfs_attr na;
u16 seq_no = MSEQNO(mref);
ntfs_debug("Entering for mft_no 0x%llx.",
(unsigned long long)MREF(mref));
na = (ntfs_attr) {
.mft_no = MREF(mref),
.type = AT_UNUSED,
.raw = FALSE,
};
ni = ntfs_inode_hash_get(base_ni->vol, &na);
if (!ni) {
ntfs_debug("Failed (ENOMEM).");
return ENOMEM;
}
if (!NInoAlloc(ni)) {
if (!seq_no || ni->seq_no == seq_no) {
*ext_ni = ni;
ntfs_debug("Done (found in cache).");
return 0;
}
ntfs_inode_reclaim(ni);
ntfs_error(base_ni->vol->mp, "Found stale extent mft "
"reference! Corrupt file system. Run "
"chkdsk.");
return EIO;
}
ni->seq_no = seq_no;
ni->nr_extents = -1;
ni->base_ni = base_ni;
ntfs_inode_unlock_alloc(ni);
*ext_ni = ni;
ntfs_debug("Done (added to cache).");
return 0;
}
static errno_t ntfs_inode_is_extended_system(ntfs_attr_search_ctx *ctx,
BOOL *is_system)
{
ntfs_volume *vol;
unsigned nr_links;
errno_t err;
ntfs_debug("Entering for mft_no 0x%llx.",
(unsigned long long)ctx->ni->mft_no);
vol = ctx->ni->vol;
ntfs_attr_search_ctx_reinit(ctx);
nr_links = le16_to_cpu(ctx->m->link_count);
if (!nr_links) {
ntfs_error(vol->mp, "Hard link count is zero.");
return EIO;
}
while (!(err = ntfs_attr_lookup(AT_FILENAME, AT_UNNAMED, 0, 0, NULL, 0,
ctx))) {
FILENAME_ATTR *fn;
ATTR_RECORD *a = ctx->a;
u8 *a_end, *fn_end;
nr_links--;
if (a->non_resident) {
ntfs_error(vol->mp, "Filename is non-resident.");
return EIO;
}
if (a->flags) {
ntfs_error(vol->mp, "Filename has invalid flags.");
return EIO;
}
if (!(a->resident_flags & RESIDENT_ATTR_IS_INDEXED)) {
ntfs_error(vol->mp, "Filename is not indexed.");
return EIO;
}
a_end = (u8*)a + le32_to_cpu(a->length);
fn = (FILENAME_ATTR*)((u8*)a + le16_to_cpu(a->value_offset));
fn_end = (u8*)fn + le32_to_cpu(a->value_length);
if ((u8*)fn < (u8*)a || fn_end < (u8*)a || fn_end > a_end ||
a_end > (u8*)ctx->m + vol->mft_record_size) {
ntfs_error(vol->mp, "Filename attribute is corrupt.");
return EIO;
}
if (MREF_LE(fn->parent_directory) == FILE_Extend) {
ntfs_debug("Done (system).");
*is_system = TRUE;
return 0;
}
}
if (err != ENOENT) {
ntfs_error(vol->mp, "Failed to lookup filename attribute.");
return err;
}
if (nr_links) {
ntfs_error(vol->mp, "Hard link count does not match number of "
"filename attributes.");
return EIO;
}
ntfs_debug("Done (not system).");
*is_system = FALSE;
return 0;
}
void ntfs_inode_afpinfo_cache(ntfs_inode *ni, AFPINFO *afp,
const unsigned afp_size)
{
if (afp && (afp->signature != AfpInfo_Signature ||
afp->version != AfpInfo_Version ||
afp_size < sizeof(*afp))) {
ntfs_warning(ni->vol->mp, "AFP_AfpInfo data attribute of "
"mft_no 0x%llx contains invalid data (wrong "
"signature, wrong version, or wrong size), "
"ignoring and using defaults.",
(unsigned long long)ni->mft_no);
afp = NULL;
}
if (!NInoValidBackupTime(ni)) {
if (afp)
ni->backup_time = ntfs_ad2utc(afp->backup_time);
else
ni->backup_time = ntfs_ad2utc(const_cpu_to_sle32(
INT32_MIN));
NInoSetValidBackupTime(ni);
}
if (!NInoValidFinderInfo(ni)) {
if (afp)
memcpy(&ni->finder_info, &afp->finder_info,
sizeof(ni->finder_info));
else
bzero(&ni->finder_info, sizeof(ni->finder_info));
if (ni->file_attributes & FILE_ATTR_HIDDEN)
ni->finder_info.attrs |= FINDER_ATTR_IS_HIDDEN;
else if (ni->finder_info.attrs & FINDER_ATTR_IS_HIDDEN) {
ni->file_attributes |= FILE_ATTR_HIDDEN;
NInoSetDirtyFileAttributes(ni);
}
NInoSetValidFinderInfo(ni);
}
}
errno_t ntfs_inode_afpinfo_read(ntfs_inode *ni)
{
ntfs_inode *afp_ni;
upl_t upl;
upl_page_info_array_t pl;
AFPINFO *afp;
unsigned afp_size;
errno_t err;
ntfs_debug("Entering for mft_no 0x%llx.",
(unsigned long long)ni->mft_no);
if (NInoValidBackupTime(ni) && NInoValidFinderInfo(ni)) {
ntfs_debug("Done (both backup time and Finder info are "
"already valid).");
return 0;
}
err = ntfs_attr_inode_get(ni, AT_DATA, NTFS_SFM_AFPINFO_NAME, 11,
FALSE, LCK_RW_TYPE_SHARED, &afp_ni);
if (err) {
ntfs_error(ni->vol->mp, "Failed to get $DATA/AFP_AfpInfo "
"attribute inode mft_no 0x%llx (error %d).",
(unsigned long long)ni->mft_no, err);
return err;
}
err = ntfs_page_map(afp_ni, 0, &upl, &pl, (u8**)&afp, FALSE);
if (err) {
ntfs_error(ni->vol->mp, "Failed to read AfpInfo from "
"$DATA/AFP_AfpInfo attribute inode mft_no "
"0x%llx (error %d).",
(unsigned long long)ni->mft_no, err);
goto err;
}
lck_spin_lock(&afp_ni->size_lock);
afp_size = afp_ni->data_size;
lck_spin_unlock(&afp_ni->size_lock);
if (afp_size > PAGE_SIZE)
afp_size = PAGE_SIZE;
ntfs_inode_afpinfo_cache(ni, afp, afp_size);
ntfs_page_unmap(afp_ni, upl, pl, FALSE);
ntfs_debug("Done.");
err:
lck_rw_unlock_shared(&afp_ni->lock);
(void)vnode_put(afp_ni->vn);
return err;
}
static BOOL ntfs_finder_info_is_unused(ntfs_inode *ni)
{
FINDER_INFO fi;
if (!NInoValidFinderInfo(ni))
panic("%s(): !NInoValidFinderInfo(ni)\n", __FUNCTION__);
memcpy(&fi, &ni->finder_info, sizeof(fi));
fi.attrs &= ~FINDER_ATTR_IS_HIDDEN;
return !bcmp(&fi, &ntfs_empty_finder_info, sizeof(fi));
}
static void ntfs_inode_afpinfo_sync(AFPINFO *afp, const unsigned afp_size,
ntfs_inode *ni)
{
if (NInoTestClearDirtyBackupTime(ni))
afp->backup_time = ntfs_utc2ad(ni->backup_time);
if (NInoTestClearDirtyFinderInfo(ni)) {
if (afp_size < sizeof(ni->finder_info))
panic("%s(): afp_size < sizeof(ni->finder_info)!\n",
__FUNCTION__);
memcpy(&afp->finder_info, &ni->finder_info,
sizeof(ni->finder_info));
if (ni->finder_info.attrs & FINDER_ATTR_IS_HIDDEN &&
!(ni->file_attributes & FILE_ATTR_HIDDEN)) {
ni->file_attributes |= FILE_ATTR_HIDDEN;
NInoSetDirtyFileAttributes(ni);
}
afp->finder_info.attrs &= ~FINDER_ATTR_IS_HIDDEN;
}
}
errno_t ntfs_inode_afpinfo_write(ntfs_inode *ni)
{
ntfs_inode *afp_ni;
upl_t upl;
upl_page_info_array_t pl;
AFPINFO *afp;
unsigned afp_size;
sle32 backup_time;
errno_t err;
BOOL delete, update;
backup_time = ntfs_utc2ad(ni->backup_time);
delete = FALSE;
if (backup_time == const_cpu_to_sle32(INT32_MIN) &&
ntfs_finder_info_is_unused(ni))
delete = TRUE;
ntfs_debug("Entering for mft_no 0x%llx, delete is %s.",
(unsigned long long)ni->mft_no,
delete ? "true" : "false");
if (NInoEncrypted(ni)) {
ntfs_warning(ni->vol->mp, "Inode 0x%llx is encrypted thus "
"cannot write AFP_AfpInfo attribute. "
"Pretending the update succeeded to keep the "
"system happy.",
(unsigned long long)ni->mft_no);
err = 0;
goto err;
}
if (!NInoValidBackupTime(ni) || !NInoValidFinderInfo(ni)) {
err = ntfs_inode_afpinfo_read(ni);
if (err) {
ntfs_error(ni->vol->mp, "Failed to read AFP_AfpInfo "
"attribute from inode mft_no 0x%llx "
"(error %d).",
(unsigned long long)ni->mft_no, err);
goto err;
}
}
err = ntfs_attr_inode_get_or_create(ni, AT_DATA, NTFS_SFM_AFPINFO_NAME,
11, FALSE, FALSE, delete ? XATTR_REPLACE : 0,
LCK_RW_TYPE_EXCLUSIVE, &afp_ni);
if (err) {
if (err == ENOENT && delete) {
ntfs_debug("AFP_AfpInfo attribute does not exist in "
"mft_no 0x%llx, no need to delete it.",
(unsigned long long)ni->mft_no);
err = 0;
} else
ntfs_error(ni->vol->mp, "Failed to get or create "
"$DATA/AFP_AfpInfo attribute inode "
"mft_no 0x%llx (error %d).",
(unsigned long long)ni->mft_no, err);
goto err;
}
if (delete) {
ntfs_debug("Unlinking AFP_AfpInfo attribute inode mft_no "
"0x%llx.", (unsigned long long)ni->mft_no);
afp_ni->link_count = 0;
ni->last_mft_change_time = ntfs_utc_current_time();
NInoSetDirtyTimes(ni);
if (!S_ISDIR(ni->mode) || NInoEncrypted(ni)) {
BOOL need_set_archive_bit = TRUE;
if (ni->vol->major_ver >= 2) {
if (ni->mft_no <= FILE_Extend)
need_set_archive_bit = FALSE;
} else {
if (ni->mft_no <= FILE_UpCase)
need_set_archive_bit = FALSE;
}
if (need_set_archive_bit) {
ni->file_attributes |= FILE_ATTR_ARCHIVE;
NInoSetDirtyFileAttributes(ni);
}
}
goto done;
}
update = TRUE;
lck_spin_lock(&afp_ni->size_lock);
afp_size = afp_ni->data_size;
lck_spin_unlock(&afp_ni->size_lock);
if (afp_ni->data_size != sizeof(AFPINFO)) {
err = ntfs_attr_resize(afp_ni, sizeof(AFPINFO), 0, NULL);
if (err) {
ntfs_warning(ni->vol->mp, "Failed to set size of "
"$DATA/AFP_AfpInfo attribute inode "
"mft_no 0x%llx (error %d). Cannot "
"update AfpInfo.",
(unsigned long long)ni->mft_no, err);
goto unl_err;
}
ntfs_debug("Set size of $DATA/AFP_AfpInfo attribute inode "
"mft_no 0x%llx to sizeof(AFPINFO) (%ld) "
"bytes.", (unsigned long long)ni->mft_no,
sizeof(AFPINFO));
lck_spin_lock(&afp_ni->size_lock);
afp_size = afp_ni->data_size;
lck_spin_unlock(&afp_ni->size_lock);
if (afp_size != sizeof(AFPINFO))
panic("%s(): afp_size != sizeof(AFPINFO)\n",
__FUNCTION__);
update = FALSE;
}
err = ntfs_page_map_ext(afp_ni, 0, &upl, &pl, (u8**)&afp, update, TRUE);
if (err) {
ntfs_error(ni->vol->mp, "Failed to map AfpInfo data of "
"$DATA/AFP_AfpInfo attribute inode mft_no "
"0x%llx (error %d).",
(unsigned long long)ni->mft_no, err);
goto unl_err;
}
if (!update) {
bzero(afp, PAGE_SIZE);
afp->signature = AfpInfo_Signature;
afp->version = AfpInfo_Version;
afp->backup_time = const_cpu_to_sle32(INT32_MIN);
}
ntfs_inode_afpinfo_sync(afp, afp_size, ni);
ntfs_page_unmap(afp_ni, upl, pl, TRUE);
done:
lck_rw_unlock_exclusive(&afp_ni->lock);
(void)vnode_put(afp_ni->vn);
ntfs_debug("Done.");
return 0;
unl_err:
lck_rw_unlock_exclusive(&afp_ni->lock);
(void)vnode_put(afp_ni->vn);
err:
NInoClearDirtyBackupTime(ni);
NInoClearDirtyFinderInfo(ni);
return err;
}
static errno_t ntfs_inode_read(ntfs_inode *ni)
{
ntfs_volume *vol = ni->vol;
MFT_RECORD *m;
ntfs_attr_search_ctx *ctx;
ATTR_RECORD *a;
STANDARD_INFORMATION *si;
errno_t err;
ntfs_debug("Entering for mft_no 0x%llx.",
(unsigned long long)ni->mft_no);
err = ntfs_mft_record_map(ni, &m);
if (err) {
ntfs_error(vol->mp, "Failed to map mft record.");
m = NULL;
ctx = NULL;
goto err;
}
ctx = ntfs_attr_search_ctx_get(ni, m);
if (!ctx) {
ntfs_error(vol->mp, "Failed to get attribute search context.");
err = ENOMEM;
goto err;
}
if (!(m->flags & MFT_RECORD_IN_USE)) {
ntfs_error(vol->mp, "Inode is not in use.");
err = ENOENT;
goto err;
}
if (m->base_mft_record) {
ntfs_error(vol->mp, "Inode is an extent inode.");
err = ENOENT;
goto err;
}
ni->seq_no = le16_to_cpu(m->sequence_number);
ni->link_count = le16_to_cpu(m->link_count);
if (!ni->link_count) {
ntfs_error(vol->mp, "Inode had been deleted.");
err = ENOENT;
goto err;
}
ni->mode |= ACCESSPERMS;
if (m->flags & MFT_RECORD_IS_DIRECTORY) {
ni->mode |= S_IFDIR;
ni->mode &= ~vol->dmask;
} else {
ni->mode |= S_IFREG;
ni->mode &= ~vol->fmask;
}
err = ntfs_attr_lookup(AT_STANDARD_INFORMATION, AT_UNNAMED, 0, 0, NULL,
0, ctx);
a = ctx->a;
if (err || a->non_resident || a->flags) {
if (err) {
if (err == ENOENT) {
ntfs_error(vol->mp, "Standard information "
"attribute is missing.");
} else
ntfs_error(vol->mp, "Failed to lookup "
"standard information "
"attribute.");
} else {
info_err:
ntfs_error(vol->mp, "Standard information attribute "
"is corrupt.");
}
goto err;
}
si = (STANDARD_INFORMATION*)((u8*)a + le16_to_cpu(a->value_offset));
if ((u8*)si < (u8*)a || (u8*)si + le32_to_cpu(a->value_length) >
(u8*)a + le32_to_cpu(a->length) ||
(u8*)a + le32_to_cpu(a->length) > (u8*)ctx->m +
vol->mft_record_size)
goto info_err;
ni->file_attributes = si->file_attributes;
ni->creation_time = ntfs2utc(si->creation_time);
ni->last_data_change_time = ntfs2utc(si->last_data_change_time);
ni->last_mft_change_time = ntfs2utc(si->last_mft_change_time);
ni->last_access_time = ntfs2utc(si->last_access_time);
ntfs_attr_search_ctx_reinit(ctx);
err = ntfs_attr_lookup(AT_ATTRIBUTE_LIST, AT_UNNAMED, 0, 0, NULL, 0,
ctx);
a = ctx->a;
if (err) {
if (err != ENOENT) {
ntfs_error(vol->mp, "Failed to lookup attribute list "
"attribute.");
goto err;
}
} else {
ntfs_debug("Attribute list found in inode 0x%llx.",
(unsigned long long)ni->mft_no);
NInoSetAttrList(ni);
if (a->flags & ATTR_COMPRESSION_MASK) {
ntfs_error(vol->mp, "Attribute list attribute is "
"compressed. Not allowed.");
goto err;
}
if (a->flags & (ATTR_IS_ENCRYPTED | ATTR_IS_SPARSE)) {
if (a->non_resident) {
ntfs_error(vol->mp, "Non-resident attribute "
"list attribute is encrypted/"
"sparse. Not allowed.");
goto err;
}
ntfs_warning(vol->mp, "Resident attribute list "
"attribute is marked encrypted/sparse "
"which is not true. However, Windows "
"allows this and chkdsk does not "
"detect or correct it so we will just "
"ignore the invalid flags and pretend "
"they are not set.");
}
ni->attr_list_size = (u32)ntfs_attr_size(a);
ni->attr_list_alloc = (ni->attr_list_size + NTFS_ALLOC_BLOCK -
1) & ~(NTFS_ALLOC_BLOCK - 1);
ni->attr_list = OSMalloc(ni->attr_list_alloc, ntfs_malloc_tag);
if (!ni->attr_list) {
ni->attr_list_alloc = 0;
ntfs_error(vol->mp, "Not enough memory to allocate "
"buffer for attribute list.");
err = ENOMEM;
goto err;
}
if (a->non_resident) {
NInoSetAttrListNonResident(ni);
if (a->lowest_vcn) {
ntfs_error(vol->mp, "Attribute list has non "
"zero lowest_vcn.");
goto err;
}
err = ntfs_mapping_pairs_decompress(vol, a,
&ni->attr_list_rl);
if (err) {
ntfs_error(vol->mp, "Mapping pairs "
"decompression failed.");
goto err;
}
err = ntfs_rl_read(vol, &ni->attr_list_rl,
ni->attr_list, ni->attr_list_size,
sle64_to_cpu(a->initialized_size));
if (err) {
ntfs_error(vol->mp, "Failed to load attribute "
"list attribute.");
goto err;
}
} else {
u8 *a_end, *al;
u32 al_len;
a_end = (u8*)a + le32_to_cpu(a->length);
al = (u8*)a + le16_to_cpu(a->value_offset);
al_len = le32_to_cpu(a->value_length);
if (al < (u8*)a || al + al_len > a_end || (u8*)a_end >
(u8*)ctx->m + vol->mft_record_size) {
ntfs_error(vol->mp, "Resident attribute list "
"attribute is corrupt.");
goto err;
}
memcpy(ni->attr_list, al, al_len);
}
}
if (S_ISDIR(ni->mode)) {
NInoSetMstProtected(ni);
ni->type = AT_INDEX_ALLOCATION;
ni->name = I30;
ni->name_len = 4;
ni->vcn_size = 0;
ni->collation_rule = 0;
ni->vcn_size_shift = 0;
} else {
ntfs_attr_search_ctx_reinit(ctx);
ni->type = AT_DATA;
ni->name = NULL;
ni->name_len = 0;
err = ntfs_attr_lookup(AT_DATA, AT_UNNAMED, 0, 0, NULL, 0, ctx);
if (err) {
BOOL is_system;
ni->allocated_size = ni->data_size =
ni->initialized_size = 0;
if (err != ENOENT) {
ntfs_error(vol->mp, "Failed to lookup data "
"attribute.");
goto err;
}
if (ni->mft_no == FILE_Secure)
goto no_data_attr_special_case;
err = ntfs_inode_is_extended_system(ctx, &is_system);
if (!err && is_system)
goto no_data_attr_special_case;
ntfs_error(vol->mp, "Data attribute is missing.");
goto err;
}
a = ctx->a;
if (a->flags & (ATTR_COMPRESSION_MASK | ATTR_IS_SPARSE)) {
if (a->flags & ATTR_COMPRESSION_MASK) {
NInoSetCompressed(ni);
if (!NVolCompressionEnabled(vol)) {
ntfs_error(vol->mp, "Found compressed "
"data but compression "
"is disabled on this "
"volume and/or mount.");
goto err;
}
if ((a->flags & ATTR_COMPRESSION_MASK)
!= ATTR_IS_COMPRESSED) {
ntfs_error(vol->mp, "Found unknown "
"compression method "
"or corrupt file.");
goto err;
}
}
if (a->flags & ATTR_IS_SPARSE)
NInoSetSparse(ni);
}
if (a->flags & ATTR_IS_ENCRYPTED) {
if (NInoCompressed(ni)) {
ntfs_error(vol->mp, "Found encrypted and "
"compressed data.");
goto err;
}
NInoSetEncrypted(ni);
}
if (a->non_resident) {
NInoSetNonResident(ni);
if (NInoCompressed(ni) || NInoSparse(ni)) {
if (NInoCompressed(ni) &&
a->compression_unit !=
NTFS_COMPRESSION_UNIT) {
ntfs_error(vol->mp, "Found "
"non-standard "
"compression unit (%d "
"instead of %d). "
"Cannot handle this.",
a->compression_unit,
NTFS_COMPRESSION_UNIT);
err = ENOTSUP;
goto err;
}
if (!NInoCompressed(ni) &&
a->compression_unit != 0 &&
a->compression_unit !=
NTFS_COMPRESSION_UNIT) {
ntfs_error(vol->mp, "Found "
"non-standard "
"compression unit (%d "
"instead of 0 or %d). "
"Cannot handle this.",
a->compression_unit,
NTFS_COMPRESSION_UNIT);
err = ENOTSUP;
goto err;
}
if (a->compression_unit) {
ni->compression_block_clusters = 1U <<
a->compression_unit;
ni->compression_block_size = 1U << (
a->compression_unit +
vol->
cluster_size_shift);
ni->compression_block_size_shift = ffs(
ni->
compression_block_size)
- 1;
} else {
ni->compression_block_clusters = 0;
ni->compression_block_size = 0;
ni->compression_block_size_shift = 0;
}
ni->compressed_size = sle64_to_cpu(
a->compressed_size);
}
if (a->lowest_vcn) {
ntfs_error(vol->mp, "First extent of data "
"attribute has non-zero "
"lowest_vcn.");
goto err;
}
ni->allocated_size = sle64_to_cpu(a->allocated_size);
ni->data_size = sle64_to_cpu(a->data_size);
ni->initialized_size = sle64_to_cpu(
a->initialized_size);
} else {
u8 *a_end, *data;
u32 data_len;
a_end = (u8*)a + le32_to_cpu(a->length);
data = (u8*)a + le16_to_cpu(a->value_offset);
data_len = le32_to_cpu(a->value_length);
if (data < (u8*)a || data + data_len > a_end ||
(u8*)a_end > (u8*)ctx->m +
vol->mft_record_size) {
ntfs_error(vol->mp, "Resident data attribute "
"is corrupt.");
goto err;
}
ni->allocated_size = a_end - data;
ni->data_size = ni->initialized_size = data_len;
if (ni->file_attributes & FILE_ATTR_SYSTEM) {
INTX_FILE *ix;
ix = (INTX_FILE*)data;
if (!ni->data_size) {
ni->mode &= ~S_IFREG;
ni->mode |= S_IFIFO;
} else if (ni->data_size == 1) {
ni->mode &= ~S_IFREG;
ni->mode |= S_IFSOCK;
} else if (data_len == offsetof(INTX_FILE,
device) + sizeof(ix->device) &&
(ix->magic ==
INTX_BLOCK_DEVICE ||
ix->magic ==
INTX_CHAR_DEVICE)) {
ni->mode &= ~S_IFREG;
if (ix->magic == INTX_BLOCK_DEVICE)
ni->mode |= S_IFBLK;
else
ni->mode |= S_IFCHR;
ni->rdev = makedev(le64_to_cpu(
ix->device.major),
le64_to_cpu(
ix->device.minor));
}
}
}
}
no_data_attr_special_case:
if (NInoEncrypted(ni)) {
ntfs_inode_afpinfo_cache(ni, NULL, 0);
goto done;
}
ntfs_attr_search_ctx_reinit(ctx);
err = ntfs_attr_lookup(AT_DATA, NTFS_SFM_AFPINFO_NAME, 11, 0, NULL, 0,
ctx);
if (err) {
if (err != ENOENT) {
ntfs_error(vol->mp, "Failed to lookup AfpInfo "
"attribute (error %d).", err);
goto err;
}
ntfs_inode_afpinfo_cache(ni, NULL, 0);
} else {
s64 ai_size;
ntfs_runlist ai_runlist;
AFPINFO ai;
a = ctx->a;
if (!a->non_resident) {
u8 *a_end, *val;
unsigned val_len;
a_end = (u8*)a + le32_to_cpu(a->length);
val = (u8*)a + le16_to_cpu(a->value_offset);
val_len = le32_to_cpu(a->value_length);
if (val < (u8*)a || val + val_len > a_end ||
(u8*)a_end >
(u8*)ctx->m + vol->mft_record_size ||
a->flags & ATTR_IS_ENCRYPTED) {
ntfs_error(vol->mp, "Resident AfpInfo "
"attribute is corrupt.");
goto err;
}
ntfs_inode_afpinfo_cache(ni, (AFPINFO*)val, val_len);
goto done;
}
ai_size = sle64_to_cpu(a->data_size);
if (a->lowest_vcn ||
sle64_to_cpu(a->initialized_size) > ai_size ||
ai_size > sle64_to_cpu(a->allocated_size)) {
ntfs_error(vol->mp, "AfpInfo attribute is corrupt.");
goto err;
}
if (!S_ISREG(ni->mode) || ni->data_size > MAXPATHLEN)
goto done;
if (ai_size > (s64)sizeof(AFPINFO))
ai_size = sizeof(AFPINFO);
if (a->flags & (ATTR_COMPRESSION_MASK | ATTR_IS_ENCRYPTED)) {
if (a->flags & ATTR_COMPRESSION_MASK)
ntfs_warning(vol->mp, "AfpInfo is compressed, "
"ignoring it. %s",
ntfs_please_email);
ntfs_inode_afpinfo_cache(ni, NULL, 0);
goto done;
}
ai_runlist.rl = NULL;
ai_runlist.alloc = ai_runlist.elements = 0;
err = ntfs_mapping_pairs_decompress(vol, a, &ai_runlist);
if (err) {
ntfs_error(vol->mp, "Mapping pairs decompression "
"failed for AfpInfo (error %d).", err);
goto err;
}
err = ntfs_rl_read(vol, &ai_runlist, (u8*)&ai, ai_size,
sle64_to_cpu(a->initialized_size));
if (err) {
ntfs_error(vol->mp, "Failed to load AfpInfo (error "
"%d).", err);
OSFree(ai_runlist.rl, ai_runlist.alloc,
ntfs_malloc_tag);
goto err;
}
OSFree(ai_runlist.rl, ai_runlist.alloc, ntfs_malloc_tag);
ntfs_inode_afpinfo_cache(ni, &ai, ai_size);
}
done:
if (S_ISREG(ni->mode) && ni->data_size <= MAXPATHLEN) {
if (!NInoValidFinderInfo(ni))
panic("%s(): !NInoValidFinderInfo(ni)\n",
__FUNCTION__);
if (ni->finder_info.type == FINDER_TYPE_SYMBOLIC_LINK &&
ni->finder_info.creator ==
FINDER_CREATOR_SYMBOLIC_LINK) {
if (NInoNonResident(ni) && (NInoCompressed(ni) ||
NInoEncrypted(ni)))
ntfs_warning(vol->mp, "Treating %s symbolic "
"link mft_no 0x%llx as a "
"regular file due to "
"<rdar://problem/5794900>.",
NInoCompressed(ni) ?
"compressed" : "encrypted",
(unsigned long long)
ni->mft_no);
else {
ni->mode = S_IFLNK | ACCESSPERMS;
}
}
}
ntfs_attr_search_ctx_put(ctx);
ntfs_mft_record_unmap(ni);
ntfs_debug("Done.");
return 0;
err:
if (ctx)
ntfs_attr_search_ctx_put(ctx);
if (m)
ntfs_mft_record_unmap(ni);
if (!err)
err = EIO;
ntfs_error(vol->mp, "Failed (error %d) for inode 0x%llx. Run chkdsk.",
(int)err, (unsigned long long)ni->mft_no);
if (err != ENOTSUP && err != ENOMEM)
NVolSetErrors(vol);
return err;
}
static errno_t ntfs_attr_inode_read_or_create(ntfs_inode *base_ni,
ntfs_inode *ni, const int options)
{
ntfs_volume *vol = ni->vol;
MFT_RECORD *m;
ntfs_attr_search_ctx *ctx;
ATTR_RECORD *a;
vnode_t vn;
errno_t err;
ntfs_debug("Entering for mft_no 0x%llx, attribute type 0x%x, "
"attribute name length 0x%x.",
(unsigned long long)ni->mft_no,
(unsigned)le32_to_cpu(ni->type),
(unsigned)ni->name_len);
if (!NInoAttr(ni))
panic("%s(): !NInoAttr(ni)\n", __FUNCTION__);
ni->seq_no = base_ni->seq_no;
ni->uid = base_ni->uid;
ni->gid = base_ni->gid;
ni->link_count = 1;
ni->mode = base_ni->mode & ~S_IFMT;
if (NInoRaw(ni)) {
if (NInoCompressed(base_ni))
NInoSetCompressed(ni);
if (NInoSparse(base_ni))
NInoSetSparse(ni);
if (NInoEncrypted(base_ni))
NInoSetEncrypted(ni);
if (NInoNonResident(base_ni))
NInoSetNonResident(ni);
lck_spin_lock(&base_ni->size_lock);
if (NInoCompressed(base_ni) || NInoSparse(base_ni)) {
ni->compression_block_clusters =
base_ni->compression_block_clusters;
ni->compression_block_size =
base_ni->compression_block_size;
ni->compression_block_size_shift =
base_ni->compression_block_size_shift;
ni->compressed_size = base_ni->compressed_size;
}
if (S_ISLNK(base_ni->mode)) {
ni->allocated_size = base_ni->allocated_size;
ni->data_size = base_ni->data_size;
ni->initialized_size = base_ni->initialized_size;
} else {
ni->initialized_size = ni->data_size =
ni->allocated_size =
base_ni->allocated_size;
}
lck_spin_unlock(&base_ni->size_lock);
if (NInoAttr(base_ni)) {
if (base_ni->nr_extents != -1)
panic("%s(): Called for non-raw attribute "
"inode which does not have a "
"base inode.", __FUNCTION__);
base_ni = base_ni->base_ni;
}
goto done;
}
err = ntfs_mft_record_map(base_ni, &m);
if (err) {
ntfs_error(vol->mp, "Failed to map base mft record.");
m = NULL;
ctx = NULL;
goto err;
}
ctx = ntfs_attr_search_ctx_get(base_ni, m);
if (!ctx) {
ntfs_error(vol->mp, "Failed to get attribute search context.");
err = ENOMEM;
goto err;
}
err = ntfs_attr_lookup(ni->type, ni->name, ni->name_len, 0, NULL, 0,
ctx);
a = ctx->a;
if (err) {
if (err != ENOENT) {
ntfs_error(vol->mp, "Failed to lookup attribute "
"(error %d).", err);
goto err;
}
if (options & XATTR_REPLACE) {
ntfs_debug("Attribute in mft_no 0x%llx does not "
"exist, returning ENOENT.",
(unsigned long long)ni->mft_no);
err = ENOENT;
goto err;
}
ntfs_debug("Attribute does not exist, creating it.");
if (ntfs_attr_can_be_resident(vol, ni->type)) {
ntfs_warning(vol->mp, "Attribute type 0x%x cannot be "
"resident. Cannot create "
"non-resident attributes yet.",
le32_to_cpu(ni->type));
err = ENOTSUP;
goto err;
}
err = ntfs_resident_attr_record_insert(base_ni, ctx, ni->type,
ni->name, ni->name_len, NULL, 0);
if (err || ctx->is_error) {
if (!err)
err = ctx->error;
ntfs_error(vol->mp, "Failed to %s mft_no 0x%llx "
"(error %d).", ctx->is_error ?
"remap extent mft record of" :
"add resident attribute to",
(unsigned long long)ni->mft_no, err);
goto err;
}
a = ctx->a;
ni->allocated_size = le32_to_cpu(a->length) -
le16_to_cpu(a->value_offset);
ni->initialized_size = ni->data_size =
le32_to_cpu(a->value_length);
NInoSetMrecNeedsDirtying(ctx->ni);
base_ni->last_mft_change_time = ntfs_utc_current_time();
NInoSetDirtyTimes(base_ni);
if (!S_ISDIR(base_ni->mode) || NInoEncrypted(base_ni)) {
BOOL need_set_archive_bit = TRUE;
if (vol->major_ver >= 2) {
if (base_ni->mft_no <= FILE_Extend)
need_set_archive_bit = FALSE;
} else {
if (base_ni->mft_no <= FILE_UpCase)
need_set_archive_bit = FALSE;
}
if (need_set_archive_bit) {
base_ni->file_attributes |= FILE_ATTR_ARCHIVE;
NInoSetDirtyFileAttributes(base_ni);
}
}
goto put_done;
}
if (ni->name == NTFS_SFM_RESOURCEFORK_NAME && !a->value_length) {
if (options & XATTR_REPLACE) {
ntfs_debug("Attribute mft_no 0x%llx does not exist, "
"returning ENOENT.",
(unsigned long long)ni->mft_no);
err = ENOENT;
goto err;
}
} else if (options & XATTR_CREATE) {
ntfs_debug("Attribute mft_no 0x%llx already exists, returning "
"EEXIST.", (unsigned long long)ni->mft_no);
err = EEXIST;
goto err;
}
if (a->flags & (ATTR_COMPRESSION_MASK | ATTR_IS_SPARSE)) {
if (a->flags & ATTR_COMPRESSION_MASK) {
NInoSetCompressed(ni);
if (ni->type != AT_DATA) {
ntfs_error(vol->mp, "Found compressed "
"non-data attribute. Please "
"report you saw this message "
"to %s.", ntfs_dev_email);
goto err;
}
if (!NVolCompressionEnabled(vol)) {
ntfs_error(vol->mp, "Found compressed data "
"but compression is disabled "
"on this volume and/or "
"mount.");
goto err;
}
if ((a->flags & ATTR_COMPRESSION_MASK) !=
ATTR_IS_COMPRESSED) {
ntfs_error(vol->mp, "Found unknown "
"compression method or "
"corrupt file.");
goto err;
}
}
if (a->flags & ATTR_IS_SPARSE)
NInoSetSparse(ni);
if (NInoMstProtected(ni)) {
ntfs_error(vol->mp, "Found mst protected attribute "
"but the attribute is %s. Please "
"report you saw this message to %s.",
NInoCompressed(ni) ?
"compressed" : "sparse",
ntfs_dev_email);
goto err;
}
}
if (a->flags & ATTR_IS_ENCRYPTED) {
if (ni->type != AT_DATA) {
ntfs_error(vol->mp, "Found encrypted non-data "
"attribute. Please report you saw "
"this message to %s.", ntfs_dev_email);
goto err;
}
if (NInoMstProtected(ni)) {
ntfs_error(vol->mp, "Found mst protected attribute "
"but the attribute is encrypted. "
"Please report you saw this message "
"to %s.", ntfs_dev_email);
goto err;
}
if (NInoCompressed(ni)) {
ntfs_error(vol->mp, "Found encrypted and compressed "
"data.");
goto err;
}
NInoSetEncrypted(ni);
}
if (!a->non_resident) {
u8 *a_end, *val;
u32 val_len;
if (a->name_length && (le16_to_cpu(a->name_offset) >=
le16_to_cpu(a->value_offset))) {
ntfs_error(vol->mp, "Attribute name is placed after "
"the attribute value.");
goto err;
}
if (NInoMstProtected(ni)) {
ntfs_error(vol->mp, "Found mst protected attribute "
"but the attribute is resident. "
"Please report you saw this message "
"to %s.", ntfs_dev_email);
goto err;
}
a_end = (u8*)a + le32_to_cpu(a->length);
val = (u8*)a + le16_to_cpu(a->value_offset);
val_len = le32_to_cpu(a->value_length);
if (val < (u8*)a || val + val_len > a_end || (u8*)a_end >
(u8*)ctx->m + vol->mft_record_size) {
ntfs_error(vol->mp, "Resident attribute is corrupt.");
goto err;
}
ni->allocated_size = a_end - val;
ni->data_size = ni->initialized_size = val_len;
} else {
NInoSetNonResident(ni);
if (a->name_length && (le16_to_cpu(a->name_offset) >=
le16_to_cpu(a->mapping_pairs_offset))) {
ntfs_error(vol->mp, "Attribute name is placed after "
"the mapping pairs array.");
goto err;
}
if (NInoCompressed(ni) || NInoSparse(ni)) {
if (NInoCompressed(ni) && a->compression_unit !=
NTFS_COMPRESSION_UNIT) {
ntfs_error(vol->mp, "Found non-standard "
"compression unit (%d instead "
"of %d). Cannot handle this.",
a->compression_unit,
NTFS_COMPRESSION_UNIT);
err = ENOTSUP;
goto err;
}
if (!NInoCompressed(ni) && a->compression_unit != 0 &&
a->compression_unit !=
NTFS_COMPRESSION_UNIT) {
ntfs_error(vol->mp, "Found non-standard "
"compression unit (%d instead "
"of 0 or %d). Cannot handle "
"this.", a->compression_unit,
NTFS_COMPRESSION_UNIT);
err = ENOTSUP;
goto err;
}
if (a->compression_unit) {
ni->compression_block_clusters = 1U <<
a->compression_unit;
ni->compression_block_size = 1U << (
a->compression_unit +
vol->cluster_size_shift);
ni->compression_block_size_shift = ffs(
ni->compression_block_size) - 1;
} else {
ni->compression_block_clusters = 0;
ni->compression_block_size = 0;
ni->compression_block_size_shift = 0;
}
ni->compressed_size = sle64_to_cpu(a->compressed_size);
}
if (a->lowest_vcn) {
ntfs_error(vol->mp, "First extent of attribute has "
"non-zero lowest_vcn.");
goto err;
}
ni->allocated_size = sle64_to_cpu(a->allocated_size);
ni->data_size = sle64_to_cpu(a->data_size);
ni->initialized_size = sle64_to_cpu(a->initialized_size);
}
put_done:
ntfs_attr_search_ctx_put(ctx);
ntfs_mft_record_unmap(base_ni);
done:
vn = base_ni->vn;
if (!vn)
panic("%s(): No vnode attached to base inode 0x%llx.",
__FUNCTION__,
(unsigned long long)base_ni->mft_no);
err = vnode_ref(vn);
if (err)
ntfs_error(vol->mp, "vnode_ref() failed!");
OSIncrementAtomic(&base_ni->nr_refs);
ni->base_ni = base_ni;
ni->nr_extents = -1;
ntfs_debug("Done.");
return 0;
err:
if (ctx)
ntfs_attr_search_ctx_put(ctx);
if (m)
ntfs_mft_record_unmap(base_ni);
if (!err)
err = EIO;
if (err != ENOENT) {
ntfs_error(vol->mp, "Failed (error %d) for attribute inode "
"0x%llx, attribute type 0x%x, name_len 0x%x. "
"Run chkdsk.", (int)err,
(unsigned long long)ni->mft_no,
(unsigned)le32_to_cpu(ni->type),
(unsigned)ni->name_len);
if (err != ENOTSUP && err != ENOMEM)
NVolSetErrors(vol);
}
return err;
}
static errno_t ntfs_index_inode_read(ntfs_inode *base_ni, ntfs_inode *ni)
{
ntfs_volume *vol = ni->vol;
MFT_RECORD *m;
ATTR_RECORD *a;
ntfs_attr_search_ctx *ctx;
INDEX_ROOT *ir;
u8 *ir_end, *index_end;
ntfs_inode *bni;
vnode_t vn;
errno_t err;
BOOL is_dir_index = (S_ISDIR(base_ni->mode) && ni->name == I30);
ntfs_debug("Entering for mft_no 0x%llx, index name length 0x%x.",
(unsigned long long)ni->mft_no,
(unsigned)ni->name_len);
ni->seq_no = base_ni->seq_no;
ni->uid = base_ni->uid;
ni->gid = base_ni->gid;
ni->link_count = 1;
ni->mode = base_ni->mode & ~S_IFMT;
err = ntfs_mft_record_map(base_ni, &m);
if (err) {
ntfs_error(vol->mp, "Failed to map base mft record.");
m = NULL;
ctx = NULL;
goto err;
}
ctx = ntfs_attr_search_ctx_get(base_ni, m);
if (!ctx) {
ntfs_error(vol->mp, "Failed to get attribute search context.");
err = ENOMEM;
goto err;
}
err = ntfs_attr_lookup(AT_INDEX_ROOT, ni->name, ni->name_len, 0, NULL,
0, ctx);
if (err) {
if (err == ENOENT)
ntfs_error(vol->mp, "$INDEX_ROOT attribute is "
"missing.");
else
ntfs_error(vol->mp, "Failed to lookup index root "
"attribute.");
goto err;
}
a = ctx->a;
if (a->non_resident) {
ntfs_error(vol->mp, "Index root attribute is not resident.");
goto err;
}
if (a->name_length && (le16_to_cpu(a->name_offset) >=
le16_to_cpu(a->value_offset))) {
ntfs_error(vol->mp, "Index root attribute name is placed "
"after the attribute value.");
goto err;
}
if (is_dir_index) {
if (a->flags & ATTR_COMPRESSION_MASK)
NInoSetCompressed(ni);
if (a->flags & ATTR_IS_ENCRYPTED) {
if (a->flags & ATTR_COMPRESSION_MASK) {
ntfs_error(vol->mp, "Found encrypted and "
"compressed index root "
"attribute.");
goto err;
}
}
if (a->flags & ATTR_IS_SPARSE)
NInoSetSparse(ni);
} else if (a->flags & (ATTR_COMPRESSION_MASK | ATTR_IS_ENCRYPTED |
ATTR_IS_SPARSE)) {
ntfs_error(vol->mp, "Found compressed/encrypted/sparse index "
"root attribute on non-directory index.");
goto err;
}
ir = (INDEX_ROOT*)((u8*)a + le16_to_cpu(a->value_offset));
ir_end = (u8*)ir + le32_to_cpu(a->value_length);
index_end = (u8*)&ir->index + le32_to_cpu(ir->index.index_length);
if (ir_end > (u8*)ctx->m + vol->mft_record_size ||
index_end > ir_end ||
ir->index.index_length != ir->index.allocated_size) {
ntfs_error(vol->mp, "Index root attribute is corrupt.");
goto err;
}
if (is_dir_index) {
if (ir->type != AT_FILENAME) {
ntfs_error(vol->mp, "Indexed attribute is not the "
"filename attribute.");
goto err;
}
if (ir->collation_rule != COLLATION_FILENAME) {
ntfs_error(vol->mp, "Index collation rule is not "
"COLLATION_FILENAME.");
goto err;
}
} else if (ir->type) {
ntfs_error(vol->mp, "Index type is not 0 (type is 0x%x).",
(unsigned)le32_to_cpu(ir->type));
goto err;
}
ntfs_debug("Index collation rule is 0x%x.",
(unsigned)le32_to_cpu(ir->collation_rule));
ni->collation_rule = ir->collation_rule;
ni->block_size = le32_to_cpu(ir->index_block_size);
if (ni->block_size & (ni->block_size - 1)) {
ntfs_error(vol->mp, "Index block size (%u) is not a power of "
"two.", (unsigned)ni->block_size);
goto err;
}
if (ni->block_size > PAGE_SIZE) {
ntfs_error(vol->mp, "Index block size (%u) > PAGE_SIZE (%u) "
"is not supported. Sorry.",
(unsigned)ni->block_size, PAGE_SIZE);
err = ENOTSUP;
goto err;
}
if (ni->block_size < NTFS_BLOCK_SIZE) {
ntfs_error(vol->mp, "Index block size (%u) < NTFS_BLOCK_SIZE "
"(%d) is not supported. Sorry.",
(unsigned)ni->block_size, NTFS_BLOCK_SIZE);
err = ENOTSUP;
goto err;
}
ni->block_size_shift = ffs(ni->block_size) - 1;
if (vol->cluster_size <= ni->block_size) {
ni->vcn_size = vol->cluster_size;
ni->vcn_size_shift = vol->cluster_size_shift;
} else {
ni->vcn_size = vol->sector_size;
ni->vcn_size_shift = vol->sector_size_shift;
}
err = ntfs_attr_lookup(AT_INDEX_ALLOCATION, ni->name, ni->name_len, 0,
NULL, 0, ctx);
if (err) {
if (err != ENOENT) {
ntfs_error(vol->mp, "Failed to lookup index "
"allocation attribute.");
goto err;
}
if (ir->index.flags & LARGE_INDEX) {
ntfs_error(vol->mp, "Index allocation attribute is "
"not present but the index root "
"attribute indicated it is.");
goto err;
}
ni->allocated_size = ni->data_size = ni->initialized_size = 0;
ntfs_attr_search_ctx_put(ctx);
ntfs_mft_record_unmap(base_ni);
} else {
unsigned block_mask;
NInoSetIndexAllocPresent(ni);
a = ctx->a;
if (!a->non_resident) {
ntfs_error(vol->mp, "Index allocation attribute is "
"resident.");
goto err;
}
if (a->name_length && (le16_to_cpu(a->name_offset) >=
le16_to_cpu(a->mapping_pairs_offset))) {
ntfs_error(vol->mp, "Index allocation attribute name "
"is placed after the mapping pairs "
"array.");
goto err;
}
if (a->flags & ATTR_IS_ENCRYPTED) {
ntfs_error(vol->mp, "Index allocation attribute is "
"encrypted.");
goto err;
}
if (a->flags & ATTR_IS_SPARSE) {
ntfs_error(vol->mp, "Index allocation attribute is "
"sparse.");
goto err;
}
if (a->flags & ATTR_COMPRESSION_MASK) {
ntfs_error(vol->mp, "Index allocation attribute is "
"compressed.");
goto err;
}
if (a->lowest_vcn) {
ntfs_error(vol->mp, "First extent of index allocation "
"attribute has non-zero lowest_vcn.");
goto err;
}
ni->allocated_size = sle64_to_cpu(a->allocated_size);
ni->data_size = sle64_to_cpu(a->data_size);
ni->initialized_size = sle64_to_cpu(a->initialized_size);
block_mask = ni->block_size - 1;
if (ni->allocated_size & vol->cluster_size_mask ||
ni->data_size & block_mask ||
ni->initialized_size & block_mask) {
ntfs_error(vol->mp, "$INDEX_ALLOCATION attribute "
"contains invalid size. Inode 0x%llx "
"is corrupt. Run chkdsk.",
(unsigned long long)ni->mft_no);
goto err;
}
ntfs_attr_search_ctx_put(ctx);
ntfs_mft_record_unmap(base_ni);
m = NULL;
ctx = NULL;
err = ntfs_attr_inode_get(base_ni, AT_BITMAP, ni->name,
ni->name_len, FALSE, LCK_RW_TYPE_SHARED, &bni);
if (err) {
ntfs_error(vol->mp, "Failed to get bitmap attribute.");
goto err;
}
if (NInoCompressed(bni) || NInoEncrypted(bni) ||
NInoSparse(bni)) {
ntfs_error(vol->mp, "Bitmap attribute is compressed "
"and/or encrypted and/or sparse.");
lck_rw_unlock_shared(&bni->lock);
(void)vnode_put(bni->vn);
goto err;
}
if ((bni->data_size << 3) < (ni->data_size >>
ni->block_size_shift)) {
ntfs_error(vol->mp, "Index bitmap too small (0x%llx) "
"for index allocation (0x%llx).",
(unsigned long long)bni->data_size,
(unsigned long long)ni->data_size);
lck_rw_unlock_shared(&bni->lock);
(void)vnode_put(bni->vn);
goto err;
}
lck_rw_unlock_shared(&bni->lock);
(void)vnode_put(bni->vn);
}
vn = base_ni->vn;
if (!vn)
panic("%s(): No vnode attached to base inode 0x%llx.",
__FUNCTION__,
(unsigned long long)base_ni->mft_no);
err = vnode_ref(vn);
if (err)
ntfs_error(vol->mp, "vnode_ref() failed!");
OSIncrementAtomic(&base_ni->nr_refs);
ni->base_ni = base_ni;
ni->nr_extents = -1;
ntfs_debug("Done.");
return 0;
err:
if (ctx)
ntfs_attr_search_ctx_put(ctx);
if (m)
ntfs_mft_record_unmap(base_ni);
if (!err)
err = EIO;
ntfs_error(vol->mp, "Failed (error %d) for index inode 0x%llx, "
"index name_len 0x%x. Run chkdsk.", (int)err,
(unsigned long long)ni->mft_no,
(unsigned)ni->name_len);
if (err != ENOTSUP && err != ENOMEM)
NVolSetErrors(vol);
return err;
}
static inline void ntfs_inode_free(ntfs_inode *ni)
{
if (ni->nr_extents > 0) {
int i;
for (i = 0; i < ni->nr_extents; i++)
ntfs_inode_reclaim(ni->extent_nis[i]);
OSFree(ni->extent_nis, ni->extent_alloc, ntfs_malloc_tag);
}
if (NInoAttr(ni) && ni->nr_extents == -1) {
ntfs_inode *base_ni = ni->base_ni;
OSDecrementAtomic(&base_ni->nr_refs);
vnode_rele(base_ni->vn);
}
if (ni->rl.alloc)
OSFree(ni->rl.rl, ni->rl.alloc, ntfs_malloc_tag);
if (ni->attr_list_alloc)
OSFree(ni->attr_list, ni->attr_list_alloc, ntfs_malloc_tag);
if (ni->attr_list_rl.alloc)
OSFree(ni->attr_list_rl.rl, ni->attr_list_rl.alloc,
ntfs_malloc_tag);
ntfs_dirhints_put(ni, 0);
if (ni->name_len && ni->name != I30 &&
ni->name != NTFS_SFM_RESOURCEFORK_NAME &&
ni->name != NTFS_SFM_AFPINFO_NAME)
OSFree(ni->name, (ni->name_len + 1) * sizeof(ntfschar),
ntfs_malloc_tag);
lck_rw_destroy(&ni->lock, ntfs_lock_grp);
lck_spin_destroy(&ni->size_lock, ntfs_lock_grp);
ntfs_rl_deinit(&ni->rl);
ntfs_rl_deinit(&ni->attr_list_rl);
lck_mtx_destroy(&ni->extent_lock, ntfs_lock_grp);
OSFree(ni, sizeof(ntfs_inode), ntfs_malloc_tag);
}
errno_t ntfs_inode_reclaim(ntfs_inode *ni)
{
vnode_t vn;
ntfs_debug("Entering for mft_no 0x%llx.",
(unsigned long long)ni->mft_no);
lck_mtx_lock(&ntfs_inode_hash_lock);
NInoSetReclaim(ni);
if (!NInoDeleted(ni)) {
NInoSetDeleted(ni);
ntfs_inode_hash_rm_nolock(ni);
}
NInoClearAllocLocked(ni);
lck_mtx_unlock(&ntfs_inode_hash_lock);
ntfs_inode_wakeup(ni);
vn = ni->vn;
if (vn)
vnode_clearfsnode(vn);
ntfs_inode_free(ni);
ntfs_debug("Done.");
return 0;
}
static errno_t ntfs_inode_data_sync(ntfs_inode *ni, const int ioflags)
{
ntfs_volume *vol = ni->vol;
vnode_t vn = ni->vn;
errno_t err = 0;
lck_rw_lock_shared(&ni->lock);
if (ni == vol->mft_ni || ni == vol->mftmirr_ni) {
ntfs_debug("Calling buf_flushdirtyblks() for $MFT%s/$DATA.",
ni == vol->mft_ni ? "" : "Mirr");
buf_flushdirtyblks(vn, ioflags & IO_SYNC, 0 ,
"ntfs_inode_sync");
lck_rw_unlock_shared(&ni->lock);
return 0;
}
if (NInoNonResident(ni)) {
int (*callback)(buf_t, void *) = NULL;
if (ni->type != AT_INDEX_ALLOCATION) {
if (NInoCompressed(ni) && !NInoRaw(ni)) {
#if 0
err = ntfs_inode_sync_compressed(ni, uio,
ubc_getsize(vn), ioflags);
if (!err)
ntfs_debug("Done (ntfs_inode_sync_"
"compressed()).");
else
ntfs_error(vol->mp, "Failed (ntfs_"
"inode_sync_"
"compressed(), error "
"%d).", err);
#endif
lck_rw_unlock_shared(&ni->lock);
ntfs_error(vol->mp, "Syncing compressed file "
"inodes is not implemented "
"yet, sorry.");
return ENOTSUP;
}
if (NInoEncrypted(ni)) {
#if 0
callback = ntfs_cluster_iodone;
#endif
lck_rw_unlock_shared(&ni->lock);
ntfs_error(vol->mp, "Syncing encrypted file "
"inodes is not implemented "
"yet, sorry.");
return ENOTSUP;
}
}
if (!NInoMstProtected(ni)) {
ntfs_debug("Calling cluster_push_ext().");
(void)cluster_push_ext(vn, ioflags, callback, NULL);
}
ntfs_debug("Calling buf_flushdirtyblks().");
buf_flushdirtyblks(vn, ioflags & IO_SYNC, 0 ,
"ntfs_inode_sync");
#ifdef DEBUG
} else {
if (vnode_hasdirtyblks(vn))
ntfs_warning(vol->mp, "resident and "
"vnode_hasdirtyblks!");
#endif
}
lck_rw_unlock_shared(&ni->lock);
ntfs_debug("Calling ubc_msync() for inode data.");
err = ubc_msync(vn, 0, ubc_getsize(vn), NULL, UBC_PUSHDIRTY |
(ioflags & IO_SYNC ? UBC_SYNC : 0));
if (err)
ntfs_error(vol->mp, "ubc_msync() of data for mft_no 0x%llx "
"failed (error %d).",
(unsigned long long)ni->mft_no, err);
return err;
}
struct fn_list_entry {
SLIST_ENTRY(fn_list_entry) list_entry;
unsigned alloc, size;
FILENAME_ATTR fn;
};
static errno_t ntfs_inode_sync_to_mft_record(ntfs_inode *ni)
{
sle64 creation_time, last_data_change_time, last_mft_change_time,
last_access_time, allocated_size, data_size;
ino64_t dir_mft_no;
ntfs_volume *vol = ni->vol;
MFT_RECORD *m;
ntfs_attr_search_ctx *actx;
ATTR_RECORD *a;
SLIST_HEAD(, fn_list_entry) fn_list;
struct fn_list_entry *next;
ntfs_index_context *ictx;
ntfs_inode *dir_ni, *dir_ia_ni;
FILENAME_ATTR *fn;
errno_t err;
FILE_ATTR_FLAGS file_attributes = 0;
BOOL ignore_errors, dirty_times, dirty_file_attributes, dirty_sizes;
BOOL dirty_set_file_bits, modified;
static const char ies[] = "Failed to update directory index entry(ies) "
"of inode 0x%llx because %s (error %d). Run chkdsk "
"or touch the inode again to retry the update.";
if (NInoAttr(ni) || !NInoDirty(ni))
return 0;
lck_rw_lock_shared(&ni->lock);
err = ntfs_mft_record_map(ni, &m);
if (err) {
lck_rw_unlock_shared(&ni->lock);
ntfs_error(vol->mp, "Failed to map mft record.");
return err;
}
actx = ntfs_attr_search_ctx_get(ni, m);
if (!actx) {
ntfs_mft_record_unmap(ni);
lck_rw_unlock_shared(&ni->lock);
ntfs_error(vol->mp, "Failed to get attribute search context.");
return ENOMEM;
}
ignore_errors = FALSE;
dirty_times = NInoTestClearDirtyTimes(ni);
dirty_file_attributes = NInoTestClearDirtyFileAttributes(ni);
dirty_sizes = NInoTestClearDirtySizes(ni);
if (S_ISDIR(ni->mode))
dirty_sizes = FALSE;
dirty_set_file_bits = NInoTestClearDirtySetFileBits(ni);
modified = FALSE;
creation_time = last_data_change_time = last_mft_change_time =
last_access_time = 0;
if (dirty_times || dirty_file_attributes) {
STANDARD_INFORMATION *si;
err = ntfs_attr_lookup(AT_STANDARD_INFORMATION, AT_UNNAMED, 0,
0, NULL, 0, actx);
if (err)
goto err;
si = (STANDARD_INFORMATION*)((u8*)actx->a +
le16_to_cpu(actx->a->value_offset));
if (dirty_file_attributes) {
file_attributes = ni->file_attributes;
if (si->file_attributes != file_attributes) {
ntfs_debug("Updating file attributes for "
"inode 0x%llx: old = 0x%x, "
"new = 0x%x",
(unsigned long long)ni->mft_no,
(unsigned)le32_to_cpu(
si->file_attributes),
(unsigned)le32_to_cpu(
file_attributes));
si->file_attributes = file_attributes;
modified = TRUE;
}
if (S_ISDIR(ni->mode))
file_attributes |=
FILE_ATTR_DUP_FILENAME_INDEX_PRESENT;
}
creation_time = utc2ntfs(ni->creation_time);
if (si->creation_time != creation_time) {
ntfs_debug("Updating creation_time for inode 0x%llx: "
"old = 0x%llx, new = 0x%llx",
(unsigned long long)ni->mft_no,
(unsigned long long)
sle64_to_cpu(si->creation_time),
(unsigned long long)
sle64_to_cpu(creation_time));
si->creation_time = creation_time;
modified = TRUE;
}
last_data_change_time = utc2ntfs(ni->last_data_change_time);
if (si->last_data_change_time != last_data_change_time) {
ntfs_debug("Updating last_data_change_time for inode "
"0x%llx: old = 0x%llx, new = 0x%llx",
(unsigned long long)ni->mft_no,
(unsigned long long)
sle64_to_cpu(si->last_data_change_time),
(unsigned long long)
sle64_to_cpu(last_data_change_time));
si->last_data_change_time = last_data_change_time;
modified = TRUE;
}
last_mft_change_time = utc2ntfs(ni->last_mft_change_time);
if (si->last_mft_change_time != last_mft_change_time) {
ntfs_debug("Updating last_mft_change_time for inode "
"0x%llx: old = 0x%llx, new = 0x%llx",
(unsigned long long)ni->mft_no,
(unsigned long long)
sle64_to_cpu(si->last_mft_change_time),
(unsigned long long)
sle64_to_cpu(last_mft_change_time));
si->last_mft_change_time = last_mft_change_time;
modified = TRUE;
}
last_access_time = utc2ntfs(ni->last_access_time);
if (si->last_access_time != last_access_time) {
ntfs_debug("Updating last_access_time for inode "
"0x%llx: old = 0x%llx, new = 0x%llx",
(unsigned long long)ni->mft_no,
(unsigned long long)
sle64_to_cpu(si->last_access_time),
(unsigned long long)
sle64_to_cpu(last_access_time));
si->last_access_time = last_access_time;
modified = TRUE;
}
}
if (modified)
NInoSetMrecNeedsDirtying(actx->ni);
if (dirty_set_file_bits) {
modified = FALSE;
if (modified)
NInoSetMrecNeedsDirtying(actx->ni);
ntfs_attr_search_ctx_reinit(actx);
}
if (dirty_sizes) {
lck_spin_lock(&ni->size_lock);
allocated_size = cpu_to_sle64(NInoNonResident(ni) &&
(NInoSparse(ni) || NInoCompressed(ni)) ?
ni->compressed_size : ni->allocated_size);
data_size = cpu_to_sle64(ni->data_size);
lck_spin_unlock(&ni->size_lock);
} else
allocated_size = data_size = 0;
if ((!dirty_file_attributes && !dirty_times && !dirty_sizes) ||
!ni->link_count) {
ntfs_attr_search_ctx_put(actx);
ntfs_mft_record_unmap(ni);
lck_rw_unlock_shared(&ni->lock);
goto done;
}
ictx = NULL;
ignore_errors = TRUE;
SLIST_INIT(&fn_list);
do {
unsigned size, alloc;
err = ntfs_attr_lookup(AT_FILENAME, AT_UNNAMED, 0, 0, NULL, 0,
actx);
if (err) {
if (err == ENOENT)
break;
ntfs_error(vol->mp, ies,
(unsigned long long)ni->mft_no,
"looking up a filename attribute in "
"the inode failed", err);
ignore_errors = FALSE;
goto list_err;
}
a = actx->a;
if (a->non_resident) {
ntfs_error(vol->mp, "Non-resident filename attribute "
"found. Run chkdsk.");
err = EIO;
ignore_errors = FALSE;
goto list_err;
}
size = le32_to_cpu(a->value_length);
alloc = offsetof(struct fn_list_entry, fn) + size;
next = OSMalloc(alloc, ntfs_malloc_tag);
if (!next) {
ntfs_error(vol->mp, ies,
(unsigned long long)ni->mft_no,
"there was not enough memory to "
"allocate a temporary filename buffer",
ENOMEM);
err = ENOMEM;
goto list_err;
}
next->alloc = alloc;
next->size = size;
memcpy(&next->fn, (u8*)a + le16_to_cpu(a->value_offset), size);
SLIST_INSERT_HEAD(&fn_list, next, list_entry);
} while (1);
ntfs_attr_search_ctx_put(actx);
ntfs_mft_record_unmap(ni);
lck_rw_unlock_shared(&ni->lock);
actx = NULL;
m = NULL;
ictx = ntfs_index_ctx_alloc();
if (!ictx) {
ntfs_debug(ies, (unsigned long long)ni->mft_no, "there was "
"not enough memory to allocate an index "
"context", ENOMEM);
err = ENOMEM;
goto list_err;
}
dir_ni = NULL;
while (!SLIST_EMPTY(&fn_list)) {
next = SLIST_FIRST(&fn_list);
fn = &next->fn;
dir_mft_no = MREF_LE(fn->parent_directory);
if (!dir_ni || dir_ni->mft_no != dir_mft_no) {
if (dir_ni) {
lck_rw_unlock_exclusive(&dir_ia_ni->lock);
lck_rw_unlock_exclusive(&dir_ni->lock);
(void)vnode_put(dir_ia_ni->vn);
(void)vnode_put(dir_ni->vn);
}
err = ntfs_inode_get(vol, dir_mft_no, FALSE,
LCK_RW_TYPE_EXCLUSIVE, &dir_ni, NULL,
NULL);
if (err) {
if (err != ENOENT) {
ntfs_error(vol->mp, ies,
(unsigned long long)
ni->mft_no, "opening "
"the parent directory "
"inode failed", err);
goto list_err;
}
do_skip_name:
ntfs_debug("Skipping name as it and its "
"parent directory were "
"unlinked under our feet.");
goto skip_name;
}
if (dir_ni->seq_no != MSEQNO_LE(fn->parent_directory)) {
lck_rw_unlock_exclusive(&dir_ni->lock);
vnode_put(dir_ni->vn);
goto do_skip_name;
}
err = ntfs_index_inode_get(dir_ni, I30, 4, FALSE,
&dir_ia_ni);
if (err) {
ntfs_debug(ies, (unsigned long long)ni->mft_no,
"opening the parent directory "
"index inode failed", err);
lck_rw_unlock_exclusive(&dir_ni->lock);
(void)vnode_put(dir_ni->vn);
goto list_err;
}
lck_rw_lock_exclusive(&dir_ia_ni->lock);
}
ntfs_index_ctx_init(ictx, dir_ia_ni);
err = ntfs_index_lookup(fn, next->size, &ictx);
if (err || ictx->entry->indexed_file !=
MK_LE_MREF(ni->mft_no, ni->seq_no)) {
if (err && err != ENOENT) {
ntfs_error(vol->mp, ies,
(unsigned long long)ni->mft_no,
"looking up the name in the "
"parent directory inode "
"failed", err);
if (err != ENOMEM)
ignore_errors = FALSE;
ntfs_index_ctx_put_reuse(ictx);
lck_rw_unlock_exclusive(&dir_ia_ni->lock);
lck_rw_unlock_exclusive(&dir_ni->lock);
(void)vnode_put(dir_ia_ni->vn);
(void)vnode_put(dir_ni->vn);
goto list_err;
}
ntfs_debug("Skipping name as it was unlinked under "
"our feet.");
goto put_skip_name;
}
fn = &ictx->entry->key.filename;
modified = FALSE;
if (dirty_file_attributes && fn->file_attributes !=
file_attributes) {
fn->file_attributes = file_attributes;
modified = TRUE;
}
if (dirty_times && (fn->creation_time != creation_time ||
fn->last_data_change_time !=
last_data_change_time ||
fn->last_mft_change_time !=
last_mft_change_time ||
fn->last_access_time != last_access_time)) {
fn->creation_time = creation_time;
fn->last_data_change_time = last_data_change_time;
fn->last_mft_change_time = last_mft_change_time;
fn->last_access_time = last_access_time;
modified = TRUE;
}
if (dirty_sizes && (fn->allocated_size != allocated_size ||
fn->data_size != data_size)) {
fn->allocated_size = allocated_size;
fn->data_size = data_size;
modified = TRUE;
}
if (modified)
ntfs_index_entry_mark_dirty(ictx);
put_skip_name:
ntfs_index_ctx_put_reuse(ictx);
skip_name:
SLIST_REMOVE_HEAD(&fn_list, list_entry);
OSFree(next, next->alloc, ntfs_malloc_tag);
}
if (dir_ni) {
lck_rw_unlock_exclusive(&dir_ia_ni->lock);
lck_rw_unlock_exclusive(&dir_ni->lock);
(void)vnode_put(dir_ia_ni->vn);
(void)vnode_put(dir_ni->vn);
}
ntfs_index_ctx_free(ictx);
done:
ntfs_debug("Done.");
return 0;
list_err:
while (!SLIST_EMPTY(&fn_list)) {
next = SLIST_FIRST(&fn_list);
SLIST_REMOVE_HEAD(&fn_list, list_entry);
OSFree(next, next->alloc, ntfs_malloc_tag);
}
if (ictx)
ntfs_index_ctx_free(ictx);
err:
if (actx)
ntfs_attr_search_ctx_put(actx);
if (m) {
ntfs_mft_record_unmap(ni);
lck_rw_unlock_shared(&ni->lock);
}
if (ignore_errors || err == ENOMEM) {
ntfs_debug("Failed to sync ntfs inode. Marking it dirty "
"again, so that we try again later.");
if (dirty_times)
NInoSetDirtyTimes(ni);
if (dirty_file_attributes)
NInoSetDirtyFileAttributes(ni);
if (dirty_sizes)
NInoSetDirtySizes(ni);
if (dirty_set_file_bits)
NInoSetDirtySetFileBits(ni);
if (ignore_errors)
err = 0;
} else {
NVolSetErrors(vol);
ntfs_error(vol->mp, "Failed (error %d). Run chkdsk.", err);
}
return err;
}
errno_t ntfs_inode_sync(ntfs_inode *ni, const int ioflags,
const BOOL skip_mft_record_sync)
{
ntfs_inode *base_ni;
errno_t err;
ntfs_debug("Entering for %sinode 0x%llx, %ssync i/o, ioflags 0x%04x.",
NInoAttr(ni) ? "attr " : "",
(unsigned long long)ni->mft_no,
(ioflags & IO_SYNC) ? "a" : "", ioflags);
base_ni = ni;
if (NInoAttr(ni)) {
base_ni = ni->base_ni;
lck_rw_lock_shared(&base_ni->lock);
}
lck_rw_lock_shared(&ni->lock);
if (NInoDeleted(ni)) {
cache_purge(ni->vn);
lck_rw_unlock_shared(&ni->lock);
if (ni != base_ni)
lck_rw_unlock_shared(&base_ni->lock);
ntfs_debug("Inode is deleted.");
return ENOENT;
}
if (ni != base_ni && NInoDeleted(base_ni))
panic("%s(): Called for attribute inode whose base inode is "
"NInoDeleted()!\n", __FUNCTION__);
lck_rw_unlock_shared(&ni->lock);
if (ni != base_ni)
lck_rw_unlock_shared(&base_ni->lock);
if (vnode_vtype(ni->vn) == VREG && (!NInoNonResident(ni) ||
ni->type == AT_INDEX_ALLOCATION || NInoRaw(ni) ||
(!NInoEncrypted(ni) && !NInoCompressed(ni)))) {
err = ntfs_inode_data_sync(ni, ioflags);
if (err)
return err;
}
if (ni == base_ni && NInoDirty(ni)) {
err = ntfs_inode_sync_to_mft_record(ni);
if (err)
return err;
}
if (skip_mft_record_sync) {
ntfs_debug("Done (skipped mft record(s) sync).");
return 0;
}
err = ntfs_mft_record_sync(base_ni);
if (NInoAttrList(base_ni)) {
int nr_extents;
lck_mtx_lock(&base_ni->extent_lock);
nr_extents = base_ni->nr_extents;
if (nr_extents > 0) {
ntfs_inode **extent_nis = base_ni->extent_nis;
errno_t err2;
int i;
ntfs_debug("Syncing %d extent inodes.", nr_extents);
for (i = 0; i < nr_extents; i++) {
err2 = ntfs_mft_record_sync(extent_nis[i]);
if (err2 && (!err || err == ENOMEM))
err = err2;
}
}
lck_mtx_unlock(&base_ni->extent_lock);
}
if (!err) {
ntfs_debug("Done.");
return 0;
}
if (err == ENOMEM)
ntfs_warning(ni->vol->mp, "Not enough memory to sync inode.");
else {
NVolSetErrors(ni->vol);
ntfs_error(ni->vol->mp, "Failed to sync mft_no 0x%llx (error "
"%d). Run chkdsk.",
(unsigned long long)ni->mft_no, err);
}
return err;
}
errno_t ntfs_inode_get_name_and_parent_mref(ntfs_inode *ni, BOOL have_parent,
MFT_REF *parent_mref, const char *name)
{
MFT_REF mref;
ntfs_inode *base_ni;
ntfschar *ntfs_name;
MFT_RECORD *m;
ntfs_attr_search_ctx *ctx;
ATTR_RECORD *a;
FILENAME_ATTR *fn;
size_t name_size;
unsigned link_count = ni->link_count;
signed res_size = 0;
errno_t err;
BOOL name_present;
ntfschar ntfs_name_buf[link_count > 1 ? NTFS_MAX_NAME_LEN : 0];
ntfs_debug("Entering for mft_no 0x%llx.",
(unsigned long long)ni->mft_no);
if (have_parent && !name)
panic("%s(): have_parent && !name\n", __FUNCTION__);
ntfs_name = NULL;
if (link_count > 1) {
if (!name) {
const char *vn_name;
vn_name = vnode_getname(ni->vn);
if (vn_name) {
ntfs_name = ntfs_name_buf;
name_size = sizeof(ntfs_name_buf);
res_size = utf8_to_ntfs(ni->vol, (u8*)vn_name,
strlen(vn_name), &ntfs_name,
&name_size);
(void)vnode_putname(vn_name);
if (res_size < 0) {
ntfs_warning(ni->vol->mp, "Failed to "
"convert cached name "
"to Unicode (error "
"%d). This may "
"indicate "
"corruption. You "
"should unmount and "
"run chkdsk.",
-res_size);
NVolSetErrors(ni->vol);
ntfs_name = NULL;
}
}
}
} else
have_parent = FALSE;
base_ni = ni;
if (NInoAttr(ni))
base_ni = ni->base_ni;
if (!link_count || (ni != base_ni && !base_ni->link_count))
goto deleted;
err = ntfs_mft_record_map(base_ni, &m);
if (err) {
ntfs_error(ni->vol->mp, "Failed to map mft record (error %d).",
err);
return err;
}
if (!(m->flags & MFT_RECORD_IN_USE))
goto unm_deleted;
ctx = ntfs_attr_search_ctx_get(base_ni, m);
if (!ctx) {
ntfs_error(ni->vol->mp, "Failed to allocate search context "
"(error %d).", err);
err = ENOMEM;
goto err;
}
name_present = FALSE;
try_next:
err = ntfs_attr_lookup(AT_FILENAME, AT_UNNAMED, 0, 0, NULL, 0, ctx);
if (err) {
if (err == ENOENT && name_present) {
have_parent = name_present = FALSE;
ntfs_name = NULL;
ntfs_attr_search_ctx_reinit(ctx);
goto try_next;
}
ntfs_error(ni->vol->mp, "Failed to find a valid filename "
"attribute (error %d).", err);
goto put_err;
}
a = ctx->a;
fn = (FILENAME_ATTR*)((u8*)a + le16_to_cpu(a->value_offset));
if (a->non_resident || (u8*)fn + le32_to_cpu(a->value_length) >
(u8*)a + le32_to_cpu(a->length)) {
ntfs_error(ni->vol->mp, "Found corrupt filename attribute in "
"mft_no 0x%llx. Unmount and run chkdsk.",
(unsigned long long)ni->mft_no);
NVolSetErrors(ni->vol);
err = EIO;
goto put_err;
}
if (fn->filename_type == FILENAME_DOS)
goto try_next;
mref = le64_to_cpu(fn->parent_directory);
if (ntfs_name && (res_size != fn->filename_length ||
bcmp(ntfs_name, fn->filename, res_size))) {
name_present = TRUE;
goto try_next;
}
if (have_parent && (MREF(*parent_mref) != MREF(mref) ||
(MSEQNO(*parent_mref) &&
MSEQNO(*parent_mref) != MSEQNO(mref)))) {
name_present = TRUE;
goto try_next;
}
if (name) {
name_size = MAXPATHLEN;
res_size = ntfs_to_utf8(ni->vol, (ntfschar*)&fn->filename,
fn->filename_length << NTFSCHAR_SIZE_SHIFT,
(u8**)&name, &name_size);
if (res_size < 0) {
ntfs_warning(ni->vol->mp, "Failed to convert name of "
"mft_no 0x%llx to UTF8 (error %d).",
(unsigned long long)ni->mft_no,
-res_size);
goto try_next;
}
}
*parent_mref = mref;
ntfs_attr_search_ctx_put(ctx);
ntfs_mft_record_unmap(base_ni);
if (name)
ntfs_debug("Done (mft_no 0x%llx has parent mft_no 0x%llx and "
"name %.*s).", (unsigned long long)ni->mft_no,
(unsigned long long)MREF(mref), res_size, name);
else
ntfs_debug("Done (mft_no 0x%llx has parent mft_no 0x%llx "
"(name was not requested and was %scached)).",
(unsigned long long)ni->mft_no,
(unsigned long long)MREF(mref),
ntfs_name ? "" : "not ");
return 0;
unm_deleted:
ntfs_mft_record_unmap(base_ni);
deleted:
ntfs_debug("Inode 0x%llx has been deleted, returning ENOENT.",
(unsigned long long)ni->mft_no);
return ENOENT;
put_err:
ntfs_attr_search_ctx_put(ctx);
err:
ntfs_mft_record_unmap(base_ni);
return err;
}
errno_t ntfs_inode_is_parent(ntfs_inode *parent_ni, ntfs_inode *child_ni,
BOOL *is_parent, ntfs_inode *forbid_ni)
{
ntfs_volume *vol;
ntfs_inode *root_ni, *ni;
vnode_t vn, prev_vn;
if (forbid_ni)
ntfs_debug("Entering for parent mft_no 0x%llx, child mft_no "
"0x%llx, and forbidden mft_no 0x%llx.",
(unsigned long long)parent_ni->mft_no,
(unsigned long long)child_ni->mft_no,
(unsigned long long)forbid_ni->mft_no);
else
ntfs_debug("Entering for parent mft_no 0x%llx and child "
"mft_no 0x%llx.",
(unsigned long long)parent_ni->mft_no,
(unsigned long long)child_ni->mft_no);
vol = child_ni->vol;
root_ni = vol->root_ni;
ni = child_ni;
prev_vn = NULL;
vn = child_ni->vn;
while (ni != root_ni) {
if (ni == forbid_ni) {
ntfs_debug("Forbidden mft_no 0x%llx is a parent of "
"child mft_no 0x%llx. Returning "
"EINVAL.",
(unsigned long long)forbid_ni->mft_no,
(unsigned long long)child_ni->mft_no);
if (prev_vn) {
lck_rw_unlock_shared(&ni->lock);
(void)vnode_put(prev_vn);
}
return EINVAL;
}
vn = vnode_getparent(vn);
if (vn) {
if (prev_vn) {
lck_rw_unlock_shared(&ni->lock);
(void)vnode_put(prev_vn);
}
ni = NTFS_I(vn);
lck_rw_lock_shared(&ni->lock);
if (NInoDeleted(ni))
panic("%s(): vnode_getparent() returned "
"NInoDeleted() inode!\n",
__FUNCTION__);
if (!ni->link_count)
goto deleted;
} else {
MFT_REF mref;
s64 mft_no;
errno_t err;
u16 seq_no;
err = ntfs_inode_get_name_and_parent_mref(ni, FALSE,
&mref, NULL);
mft_no = ni->mft_no;
if (prev_vn) {
lck_rw_unlock_shared(&ni->lock);
(void)vnode_put(prev_vn);
}
if (err) {
ntfs_error(vol->mp, "Failed to determine "
"parent mft reference of "
"mft_no 0x%llx (error %d).",
(unsigned long long)mft_no,
err);
return err;
}
err = ntfs_inode_get(vol, MREF(mref), FALSE,
LCK_RW_TYPE_SHARED, &ni, NULL, NULL);
if (err) {
ntfs_error(vol->mp, "Failed to obtain parent "
"mft_no 0x%llx of mft_no "
"0x%llx (error %d).",
(unsigned long long)MREF(mref),
(unsigned long long)mft_no,
err);
return err;
}
vn = ni->vn;
seq_no = MSEQNO(mref);
if (seq_no && seq_no != ni->seq_no)
goto deleted;
}
if (ni == parent_ni) {
lck_rw_unlock_shared(&ni->lock);
(void)vnode_put(ni->vn);
*is_parent = TRUE;
ntfs_debug("Parent mft_no 0x%llx is a parent of "
"child mft_no 0x%llx.",
(unsigned long long)parent_ni->mft_no,
(unsigned long long)child_ni->mft_no);
return 0;
}
prev_vn = vn;
}
if (prev_vn) {
lck_rw_unlock_shared(&ni->lock);
(void)vnode_put(prev_vn);
}
*is_parent = FALSE;
ntfs_debug("Parent mft_no 0x%llx is not a parent of child mft_no "
"0x%llx.", (unsigned long long)parent_ni->mft_no,
(unsigned long long)child_ni->mft_no);
return 0;
deleted:
ntfs_error(ni->vol->mp, "Parent mft_no 0x%llx has been deleted. "
"Returning ENOENT.", (unsigned long long)ni->mft_no);
lck_rw_unlock_shared(&ni->lock);
(void)vnode_put(vn);
return ENOENT;
}