/**
 * dir.c - Directory handling code. Originated from the Linux-NTFS project.
 *
 * Copyright (c) 2002-2005 Anton Altaparmakov
 * Copyright (c) 2004-2005 Richard Russon
 * Copyright (c) 2004-2008 Szabolcs Szakacsits
 * Copyright (c) 2005-2007 Yura Pakhuchiy
 * Copyright (c) 2008-2021 Jean-Pierre Andre
 *
 * This program/include file is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as published
 * by the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program/include file is distributed in the hope that it will be
 * useful, but WITHOUT ANY WARRANTY; without even the implied warranty
 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program (in the main directory of the NTFS-3G
 * distribution in the file COPYING); if not, write to the Free Software
 * Foundation,Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#ifdef HAVE_ERRNO_H
#include <errno.h>
#endif
#ifdef HAVE_STRING_H
#include <string.h>
#endif
#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif

#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#ifdef MAJOR_IN_MKDEV
#include <sys/mkdev.h>
#endif
#ifdef MAJOR_IN_SYSMACROS
#include <sys/sysmacros.h>
#endif

#include "param.h"
#include "types.h"
#include "debug.h"
#include "attrib.h"
#include "inode.h"
#include "dir.h"
#include "volume.h"
#include "mft.h"
#include "index.h"
#include "ntfstime.h"
#include "lcnalloc.h"
#include "logging.h"
#include "cache.h"
#include "misc.h"
#include "security.h"
#include "reparse.h"
#include "object_id.h"
#include "xattrs.h"
#include "ea.h"

/*
 * The little endian Unicode strings "$I30", "$SII", "$SDH", "$O"
 *  and "$Q" as global constants.
 */
ntfschar NTFS_INDEX_I30[5] = { const_cpu_to_le16('$'), const_cpu_to_le16('I'),
		const_cpu_to_le16('3'), const_cpu_to_le16('0'),
		const_cpu_to_le16('\0') };
ntfschar NTFS_INDEX_SII[5] = { const_cpu_to_le16('$'), const_cpu_to_le16('S'),
		const_cpu_to_le16('I'), const_cpu_to_le16('I'),
		const_cpu_to_le16('\0') };
ntfschar NTFS_INDEX_SDH[5] = { const_cpu_to_le16('$'), const_cpu_to_le16('S'),
		const_cpu_to_le16('D'), const_cpu_to_le16('H'),
		const_cpu_to_le16('\0') };
ntfschar NTFS_INDEX_O[3] = { const_cpu_to_le16('$'), const_cpu_to_le16('O'),
		const_cpu_to_le16('\0') };
ntfschar NTFS_INDEX_Q[3] = { const_cpu_to_le16('$'), const_cpu_to_le16('Q'),
		const_cpu_to_le16('\0') };
ntfschar NTFS_INDEX_R[3] = { const_cpu_to_le16('$'), const_cpu_to_le16('R'),
		const_cpu_to_le16('\0') };

#if CACHE_INODE_SIZE

/*
 *		Pathname hashing
 *
 *	Based on first char and second char (which may be '\0')
 */

int ntfs_dir_inode_hash(const struct CACHED_GENERIC *cached)
{
	const char *path;
	const unsigned char *name;

	path = (const char*)cached->variable;
	if (!path) {
		ntfs_log_error("Bad inode cache entry\n");
		return (-1);
	}
	name = (const unsigned char*)strrchr(path,'/');
	if (!name)
		name = (const unsigned char*)path;
	return (((name[0] << 1) + name[1] + strlen((const char*)name))
				% (2*CACHE_INODE_SIZE));
}

/*
 *		Pathname comparing for entering/fetching from cache
 */

static int inode_cache_compare(const struct CACHED_GENERIC *cached,
			const struct CACHED_GENERIC *wanted)
{
	return (!cached->variable
		    || strcmp(cached->variable, wanted->variable));
}

/*
 *		Pathname comparing for invalidating entries in cache
 *
 *	A partial path is compared in order to invalidate all paths
 *	related to a renamed directory
 *	inode numbers are also checked, as deleting a long name may
 *	imply deleting a short name and conversely
 *
 *	Only use associated with a CACHE_NOHASH flag
 */

static int inode_cache_inv_compare(const struct CACHED_GENERIC *cached,
			const struct CACHED_GENERIC *wanted)
{
	int len;
	BOOL different;
	const struct CACHED_INODE *w;
	const struct CACHED_INODE *c;

	w = (const struct CACHED_INODE*)wanted;
	c = (const struct CACHED_INODE*)cached;
	if (w->pathname) {
		len = strlen(w->pathname);
		different = !cached->variable
			|| ((w->inum != MREF(c->inum))
			   && (strncmp(c->pathname, w->pathname, len)
				|| ((c->pathname[len] != '\0')
				   && (c->pathname[len] != '/'))));
	} else
		different = !c->pathname
			|| (w->inum != MREF(c->inum));
	return (different);
}

#endif

#if CACHE_LOOKUP_SIZE

/*
 *		File name comparing for entering/fetching from lookup cache
 */

static int lookup_cache_compare(const struct CACHED_GENERIC *cached,
			const struct CACHED_GENERIC *wanted)
{
	const struct CACHED_LOOKUP *c = (const struct CACHED_LOOKUP*) cached;
	const struct CACHED_LOOKUP *w = (const struct CACHED_LOOKUP*) wanted;
	return (!c->name
		    || (c->parent != w->parent)
		    || (c->namesize != w->namesize)
		    || memcmp(c->name, w->name, c->namesize));
}

/*
 *		Inode number comparing for invalidating lookup cache
 *
 *	All entries with designated inode number are invalidated
 *
 *	Only use associated with a CACHE_NOHASH flag
 */

static int lookup_cache_inv_compare(const struct CACHED_GENERIC *cached,
			const struct CACHED_GENERIC *wanted)
{
	const struct CACHED_LOOKUP *c = (const struct CACHED_LOOKUP*) cached;
	const struct CACHED_LOOKUP *w = (const struct CACHED_LOOKUP*) wanted;
	return (!c->name
		    || (c->parent != w->parent)
		    || (MREF(c->inum) != MREF(w->inum)));
}

/*
 *		Lookup hashing
 *
 *	Based on first, second and and last char
 */

int ntfs_dir_lookup_hash(const struct CACHED_GENERIC *cached)
{
	const unsigned char *name;
	int count;
	unsigned int val;

	name = (const unsigned char*)cached->variable;
	count = cached->varsize;
	if (!name || !count) {
		ntfs_log_error("Bad lookup cache entry\n");
		return (-1);
	}
	val = (name[0] << 2) + (name[1] << 1) + name[count - 1] + count;
	return (val % (2*CACHE_LOOKUP_SIZE));
}

#endif

/*
 * main implementation part of ntfs_inode_lookup_by_name().
 * If lookup is success, then return INDEX_ENTRY pointer which is
 * duplicated memory of found INDEX_ENTRY.
 *
 * NOTE: Caller should free returned ie memory after use it.
 */
INDEX_ENTRY * __ntfs_inode_lookup_by_name(ntfs_inode *dir_ni,
		const ntfschar *uname, const int uname_len)
{
	VCN vcn;
	s64 br;
	ntfs_volume *vol = dir_ni->vol;
	ntfs_attr_search_ctx *ctx;
	INDEX_ROOT *ir;
	INDEX_ENTRY *ie;
	INDEX_ENTRY *dup_ie;
	INDEX_ALLOCATION *ia;
	IGNORE_CASE_BOOL case_sensitivity;
	u8 *index_end;
	ntfs_attr *ia_na;
	int eo, rc;
	u32 index_block_size;
	u8 index_vcn_size_bits;

	ntfs_log_trace("Entering\n");

	if (!dir_ni || !dir_ni->mrec || !uname || uname_len <= 0) {
		errno = EINVAL;
		return NULL;
	}

	ctx = ntfs_attr_get_search_ctx(dir_ni, NULL);
	if (!ctx)
		return NULL;

	/* Find the index root attribute in the mft record. */
	if (ntfs_attr_lookup(AT_INDEX_ROOT, NTFS_INDEX_I30, 4, CASE_SENSITIVE, 0, NULL,
			0, ctx)) {
		ntfs_log_perror("Index root attribute missing in directory inode "
				"%lld", (unsigned long long)dir_ni->mft_no);
		goto put_err_out;
	}
	case_sensitivity = (NVolCaseSensitive(vol) ? CASE_SENSITIVE : IGNORE_CASE);

	/* Get to the index root value. */
	ir = (INDEX_ROOT*)((u8*)ctx->attr +
			le16_to_cpu(ctx->attr->value_offset));
	index_block_size = le32_to_cpu(ir->index_block_size);
	if (index_block_size < NTFS_BLOCK_SIZE ||
			index_block_size & (index_block_size - 1)) {
		ntfs_log_error("Index block size %u is invalid.\n",
				(unsigned)index_block_size);
		goto put_err_out;
	}

	/* Consistency check of ir done while fetching attribute */
	index_end = (u8*)&ir->index + le32_to_cpu(ir->index.index_length);

	/* The first index entry. */
	ie = (INDEX_ENTRY*)((u8*)&ir->index +
			le32_to_cpu(ir->index.entries_offset));

	/*
	 * Loop until we exceed valid memory (corruption case) or until we
	 * reach the last entry.
	 */
	for (;; ie = (INDEX_ENTRY*)((u8*)ie + le16_to_cpu(ie->length))) {
		/* Bounds checks. */
		if ((u8*)ie < (u8*)ctx->mrec || (u8*)ie +
				sizeof(INDEX_ENTRY_HEADER) > index_end ||
				(u8*)ie + le16_to_cpu(ie->length) >
				index_end) {
			ntfs_log_error("Index root entry out of bounds in"
				" inode %lld\n",
				(unsigned long long)dir_ni->mft_no);
			goto put_err_out;
		}
		/*
		 * The last entry cannot contain a name. It can however contain
		 * a pointer to a child node in the B+tree so we just break out.
		 */
		if (ie->ie_flags & INDEX_ENTRY_END)
			break;

		/* The file name must not overflow from the entry */
		rc = ntfs_index_entry_inconsistent(vol, ie, COLLATION_FILE_NAME,
				dir_ni->mft_no, NULL);
		if (rc < 0) {
			errno = EIO;
			goto put_err_out;
		}
		/*
		 * Not a perfect match, need to do full blown collation so we
		 * know which way in the B+tree we have to go.
		 */
		rc = ntfs_names_full_collate(uname, uname_len,
				(ntfschar*)&ie->key.file_name.file_name,
				ie->key.file_name.file_name_length,
				case_sensitivity, vol->upcase, vol->upcase_len);
		/*
		 * If uname collates before the name of the current entry, there
		 * is definitely no such name in this index but we might need to
		 * descend into the B+tree so we just break out of the loop.
		 */
		if (rc == -1)
			break;
		/* The names are not equal, continue the search. */
		if (rc)
			continue;
		/*
		 * Perfect match, this will never happen as the
		 * ntfs_are_names_equal() call will have gotten a match but we
		 * still treat it correctly.
		 */
		dup_ie = ntfs_ie_dup(ie);
		ntfs_attr_put_search_ctx(ctx);
		return dup_ie;
	}
	/*
	 * We have finished with this index without success. Check for the
	 * presence of a child node and if not present return error code
	 * ENOENT, unless we have got the mft reference of a matching name
	 * cached in mref in which case return mref.
	 */
	if (!(ie->ie_flags & INDEX_ENTRY_NODE)) {
		ntfs_attr_put_search_ctx(ctx);
		ntfs_log_debug("Entry not found - between root entries.\n");
		errno = ENOENT;
		return NULL;
	} /* Child node present, descend into it. */

	/* Open the index allocation attribute. */
	ia_na = ntfs_attr_open(dir_ni, AT_INDEX_ALLOCATION, NTFS_INDEX_I30, 4);
	if (!ia_na) {
		ntfs_log_perror("Failed to open index allocation (inode %lld)",
				(unsigned long long)dir_ni->mft_no);
		goto put_err_out;
	}

	/* Allocate a buffer for the current index block. */
	ia = ntfs_malloc(index_block_size);
	if (!ia) {
		ntfs_attr_close(ia_na);
		goto put_err_out;
	}

	/* Determine the size of a vcn in the directory index. */
	if (vol->cluster_size <= index_block_size) {
		index_vcn_size_bits = vol->cluster_size_bits;
	} else {
		index_vcn_size_bits = NTFS_BLOCK_SIZE_BITS;
	}

	/* Get the starting vcn of the index_block holding the child node. */
	vcn = sle64_to_cpup((sle64*)((u8*)ie + le16_to_cpu(ie->length) - 8));

descend_into_child_node:

	/* Read the index block starting at vcn. */
	br = ntfs_attr_mst_pread(ia_na, vcn << index_vcn_size_bits, 1,
			index_block_size, ia);
	if (br != 1) {
		if (br != -1)
			errno = EIO;
		ntfs_log_perror("Failed to read vcn 0x%llx from inode %lld",
				(unsigned long long)vcn,
				(unsigned long long)ia_na->ni->mft_no);
		goto close_err_out;
	}

	if (ntfs_index_block_inconsistent(vol, ia_na, (INDEX_BLOCK*)ia,
				index_block_size, ia_na->ni->mft_no, vcn)) {
		errno = EIO;
		goto close_err_out;
	}
	index_end = (u8*)&ia->index + le32_to_cpu(ia->index.index_length);

	/* The first index entry. */
	ie = (INDEX_ENTRY*)((u8*)&ia->index +
			le32_to_cpu(ia->index.entries_offset));
	/*
	 * Iterate similar to above big loop but applied to index buffer, thus
	 * loop until we exceed valid memory (corruption case) or until we
	 * reach the last entry.
	 */
	for (;; ie = (INDEX_ENTRY*)((u8*)ie + le16_to_cpu(ie->length))) {
		/* Bounds check. */
		if ((u8*)ie < (u8*)ia || (u8*)ie +
				sizeof(INDEX_ENTRY_HEADER) > index_end ||
				(u8*)ie + le16_to_cpu(ie->length) >
				index_end) {
			ntfs_log_error("Index entry out of bounds in directory "
				       "inode %lld.\n",
				       (unsigned long long)dir_ni->mft_no);
			errno = EIO;
			goto close_err_out;
		}
		/*
		 * The last entry cannot contain a name. It can however contain
		 * a pointer to a child node in the B+tree so we just break out.
		 */
		if (ie->ie_flags & INDEX_ENTRY_END)
			break;

		/* The file name must not overflow from the entry */
		rc = ntfs_index_entry_inconsistent(vol, ie, COLLATION_FILE_NAME,
				dir_ni->mft_no, NULL);
		if (rc < 0) {
			errno = EIO;
			goto close_err_out;
		}
		/*
		 * Not a perfect match, need to do full blown collation so we
		 * know which way in the B+tree we have to go.
		 */
		rc = ntfs_names_full_collate(uname, uname_len,
				(ntfschar*)&ie->key.file_name.file_name,
				ie->key.file_name.file_name_length,
				case_sensitivity, vol->upcase, vol->upcase_len);
		/*
		 * If uname collates before the name of the current entry, there
		 * is definitely no such name in this index but we might need to
		 * descend into the B+tree so we just break out of the loop.
		 */
		if (rc == -1)
			break;
		/* The names are not equal, continue the search. */
		if (rc)
			continue;
		dup_ie = ntfs_ie_dup(ie);
		free(ia);
		ntfs_attr_close(ia_na);
		ntfs_attr_put_search_ctx(ctx);
		return dup_ie;
	}
	/*
	 * We have finished with this index buffer without success. Check for
	 * the presence of a child node.
	 */
	if (ie->ie_flags & INDEX_ENTRY_NODE) {
		if ((ia->index.ih_flags & NODE_MASK) == LEAF_NODE) {
			ntfs_log_error("Index entry with child node found in a leaf "
					"node in directory inode %lld.\n",
					(unsigned long long)dir_ni->mft_no);
			errno = EIO;
			goto close_err_out;
		}
		/* Child node present, descend into it. */
		vcn = sle64_to_cpup((sle64*)((u8*)ie + le16_to_cpu(ie->length) - 8));
		if (vcn >= 0)
			goto descend_into_child_node;
		ntfs_log_error("Negative child node vcn in directory inode "
			       "0x%llx.\n", (unsigned long long)dir_ni->mft_no);
		errno = EIO;
		goto close_err_out;
	}
	free(ia);
	ntfs_attr_close(ia_na);
	ntfs_attr_put_search_ctx(ctx);
	/*
	 * No child node present, return error code ENOENT, unless we have got
	 * the mft reference of a matching name cached in mref in which case
	 * return mref.
	 */
	ntfs_log_debug("Entry not found.\n");
	errno = ENOENT;
	return NULL;
put_err_out:
	eo = EIO;
	ntfs_log_debug("Corrupt directory. Aborting lookup.\n");
eo_put_err_out:
	ntfs_attr_put_search_ctx(ctx);
	errno = eo;
	return NULL;
close_err_out:
	eo = errno;
	free(ia);
	ntfs_attr_close(ia_na);
	goto eo_put_err_out;
}

/**
 * ntfs_inode_lookup_by_name - find an inode in a directory given its name
 * @dir_ni:	ntfs inode of the directory in which to search for the name
 * @uname:	Unicode name for which to search in the directory
 * @uname_len:	length of the name @uname in Unicode characters
 *
 * Look for an inode with name @uname in the directory with inode @dir_ni.
 * ntfs_inode_lookup_by_name() walks the contents of the directory looking for
 * the Unicode name. If the name is found in the directory, the corresponding
 * inode number (>= 0) is returned as a mft reference in cpu format, i.e. it
 * is a 64-bit number containing the sequence number.
 *
 * On error, return -1 with errno set to the error code. If the inode is is not
 * found errno is ENOENT.
 *
 * Note, @uname_len does not include the (optional) terminating NULL character.
 *
 * Note, we look for a case sensitive match first but we also look for a case
 * insensitive match at the same time. If we find a case insensitive match, we
 * save that for the case that we don't find an exact match, where we return
 * the mft reference of the case insensitive match.
 *
 * If the volume is mounted with the case sensitive flag set, then we only
 * allow exact matches.
 */
u64 ntfs_inode_lookup_by_name(ntfs_inode *dir_ni,
		const ntfschar *uname, const int uname_len)
{
	u64 mref = 0;
	INDEX_ENTRY *ie;

	ntfs_log_trace("Entering\n");

	if (!dir_ni || !dir_ni->mrec || !uname || uname_len <= 0) {
		errno = EINVAL;
		return -1;
	}

	ie = __ntfs_inode_lookup_by_name(dir_ni, uname, uname_len);
	if (!ie)
		return -1;

	mref = le64_to_cpu(ie->indexed_file);
	free(ie);
	return mref;
}

/*
 *		Lookup a file in a directory from its UTF-8 name
 *
 *	The name is first fetched from cache if one is defined
 *
 *	Returns the inode number
 *		or -1 if not possible (errno tells why)
 */

u64 ntfs_inode_lookup_by_mbsname(ntfs_inode *dir_ni, const char *name)
{
	int uname_len;
	ntfschar *uname = (ntfschar*)NULL;
	u64 inum;
	char *cached_name;
	const char *const_name;

	if (!NVolCaseSensitive(dir_ni->vol)) {
		cached_name = ntfs_uppercase_mbs(name,
			dir_ni->vol->upcase, dir_ni->vol->upcase_len);
		const_name = cached_name;
	} else {
		cached_name = (char*)NULL;
		const_name = name;
	}
	if (const_name) {
#if CACHE_LOOKUP_SIZE

		/*
		 * fetch inode from cache
		 */

		if (dir_ni->vol->lookup_cache) {
			struct CACHED_LOOKUP item;
			struct CACHED_LOOKUP *cached;

			item.name = const_name;
			item.namesize = strlen(const_name) + 1;
			item.parent = dir_ni->mft_no;
			cached = (struct CACHED_LOOKUP*)ntfs_fetch_cache(
					dir_ni->vol->lookup_cache,
					GENERIC(&item), lookup_cache_compare);
			if (cached) {
				inum = cached->inum;
				if (inum == (u64)-1)
					errno = ENOENT;
			} else {
				/* Generate unicode name. */
				uname_len = ntfs_mbstoucs(name, &uname);
				if (uname_len >= 0) {
					inum = ntfs_inode_lookup_by_name(dir_ni,
							uname, uname_len);
					item.inum = inum;
				/* enter into cache, even if not found */
					ntfs_enter_cache(dir_ni->vol->lookup_cache,
							GENERIC(&item),
							lookup_cache_compare);
					free(uname);
				} else
					inum = (s64)-1;
			}
		} else
#endif
			{
				/* Generate unicode name. */
			uname_len = ntfs_mbstoucs(cached_name, &uname);
			if (uname_len >= 0) {
				inum = ntfs_inode_lookup_by_name(dir_ni,
						uname, uname_len);
				free(uname);
			}
			else
				inum = (s64)-1;
		}
		if (cached_name)
			free(cached_name);
	} else
		inum = (s64)-1;
	return (inum);
}

/*
 *		Update a cache lookup record when a name has been defined
 *
 *	The UTF-8 name is required
 */

void ntfs_inode_update_mbsname(ntfs_inode *dir_ni, const char *name, u64 inum)
{
#if CACHE_LOOKUP_SIZE
	struct CACHED_LOOKUP item;
	struct CACHED_LOOKUP *cached;
	char *cached_name;

	if (dir_ni->vol->lookup_cache) {
		if (!NVolCaseSensitive(dir_ni->vol)) {
			cached_name = ntfs_uppercase_mbs(name,
				dir_ni->vol->upcase, dir_ni->vol->upcase_len);
			item.name = cached_name;
		} else {
			cached_name = (char*)NULL;
			item.name = name;
		}
		if (item.name) {
			item.namesize = strlen(item.name) + 1;
			item.parent = dir_ni->mft_no;
			item.inum = inum;
			cached = (struct CACHED_LOOKUP*)ntfs_enter_cache(
					dir_ni->vol->lookup_cache,
					GENERIC(&item), lookup_cache_compare);
			if (cached)
				cached->inum = inum;
			if (cached_name)
				free(cached_name);
		}
	}
#endif
}

/**
 * ntfs_pathname_to_inode - Find the inode which represents the given pathname
 * @vol:       An ntfs volume obtained from ntfs_mount
 * @parent:    A directory inode to begin the search (may be NULL)
 * @pathname:  Pathname to be located
 *
 * Take an ASCII pathname and find the inode that represents it.  The function
 * splits the path and then descends the directory tree.  If @parent is NULL,
 * then the root directory '.' will be used as the base for the search.
 *
 * Return:  inode  Success, the pathname was valid
 *	    NULL   Error, the pathname was invalid, or some other error occurred
 */
ntfs_inode *ntfs_pathname_to_inode(ntfs_volume *vol, ntfs_inode *parent,
		const char *pathname)
{
	u64 inum;
	int len, err = 0;
	char *p, *q;
	ntfs_inode *ni;
	ntfs_inode *result = NULL;
	ntfschar *unicode = NULL;
	char *ascii = NULL;
#if CACHE_INODE_SIZE
	struct CACHED_INODE item;
	struct CACHED_INODE *cached;
	char *fullname;
#endif

	if (!vol || !pathname) {
		errno = EINVAL;
		return NULL;
	}

	ntfs_log_trace("path: '%s'\n", pathname);

	ascii = strdup(pathname);
	if (!ascii) {
		ntfs_log_error("Out of memory.\n");
		err = ENOMEM;
		goto out;
	}

	p = ascii;
	/* Remove leading /'s. */
	while (p && *p && *p == PATH_SEP)
		p++;
#if CACHE_INODE_SIZE
	fullname = p;
	if (p[0] && (p[strlen(p)-1] == PATH_SEP))
		ntfs_log_error("Unnormalized path %s\n",ascii);
#endif
	if (parent) {
		ni = parent;
	} else {
#if CACHE_INODE_SIZE
			/*
			 * fetch inode for full path from cache
			 */
		if (*fullname) {
			item.pathname = fullname;
			item.varsize = strlen(fullname) + 1;
			cached = (struct CACHED_INODE*)ntfs_fetch_cache(
				vol->xinode_cache, GENERIC(&item),
				inode_cache_compare);
		} else
			cached = (struct CACHED_INODE*)NULL;
		if (cached) {
			/*
			 * return opened inode if found in cache
			 */
			inum = MREF(cached->inum);
			ni = ntfs_inode_open(vol, inum);
			if (!ni) {
				ntfs_log_debug("Cannot open inode %llu: %s.\n",
						(unsigned long long)inum, p);
				err = EIO;
			}
			result = ni;
			goto out;
		}
#endif
		ni = ntfs_inode_open(vol, FILE_root);
		if (!ni) {
			ntfs_log_debug("Couldn't open the inode of the root "
					"directory.\n");
			err = EIO;
			result = (ntfs_inode*)NULL;
			goto out;
		}
	}

	while (p && *p) {
		/* Find the end of the first token. */
		q = strchr(p, PATH_SEP);
		if (q != NULL) {
			*q = '\0';
		}
#if CACHE_INODE_SIZE
			/*
			 * fetch inode for partial path from cache
			 */
		cached = (struct CACHED_INODE*)NULL;
		if (!parent) {
			item.pathname = fullname;
			item.varsize = strlen(fullname) + 1;
			cached = (struct CACHED_INODE*)ntfs_fetch_cache(
					vol->xinode_cache, GENERIC(&item),
					inode_cache_compare);
			if (cached) {
				inum = cached->inum;
			}
		}
			/*
			 * if not in cache, translate, search, then
			 * insert into cache if found
			 */
		if (!cached) {
			len = ntfs_mbstoucs(p, &unicode);
			if (len < 0) {
				ntfs_log_perror("Could not convert filename to Unicode:"
					" '%s'", p);
				err = errno;
				goto close;
			} else if (len > NTFS_MAX_NAME_LEN) {
				err = ENAMETOOLONG;
				goto close;
			}
			inum = ntfs_inode_lookup_by_name(ni, unicode, len);
			if (!parent && (inum != (u64) -1)) {
				item.inum = inum;
				ntfs_enter_cache(vol->xinode_cache,
						GENERIC(&item),
						inode_cache_compare);
			}
		}
#else
		len = ntfs_mbstoucs(p, &unicode);
		if (len < 0) {
			ntfs_log_perror("Could not convert filename to Unicode:"
					" '%s'", p);
			err = errno;
			goto close;
		} else if (len > NTFS_MAX_NAME_LEN) {
			err = ENAMETOOLONG;
			goto close;
		}
		inum = ntfs_inode_lookup_by_name(ni, unicode, len);
#endif
		if (inum == (u64) -1) {
			ntfs_log_debug("Couldn't find name '%s' in pathname "
					"'%s'.\n", p, pathname);
			err = ENOENT;
			goto close;
		}

		if (ni != parent)
			if (ntfs_inode_close(ni)) {
				err = errno;
				goto out;
			}

		inum = MREF(inum);
		ni = ntfs_inode_open(vol, inum);
		if (!ni) {
			ntfs_log_debug("Cannot open inode %llu: %s.\n",
					(unsigned long long)inum, p);
			err = EIO;
			goto close;
		}

		free(unicode);
		unicode = NULL;

		if (q) *q++ = PATH_SEP; /* JPA */
		p = q;
		while (p && *p && *p == PATH_SEP)
			p++;
	}

	result = ni;
	ni = NULL;
close:
	if (ni && (ni != parent))
		if (ntfs_inode_close(ni) && !err)
			err = errno;
out:
	free(ascii);
	free(unicode);
	if (err)
		errno = err;
	return result;
}

/*
 * The little endian Unicode string ".." for ntfs_readdir().
 */
static const ntfschar dotdot[3] = { const_cpu_to_le16('.'),
				   const_cpu_to_le16('.'),
				   const_cpu_to_le16('\0') };

/*
 * union index_union -
 * More helpers for ntfs_readdir().
 */
typedef union {
	INDEX_ROOT *ir;
	INDEX_ALLOCATION *ia;
} index_union __attribute__((__transparent_union__));

/**
 * enum INDEX_TYPE -
 * More helpers for ntfs_readdir().
 */
typedef enum {
	INDEX_TYPE_ROOT,	/* index root */
	INDEX_TYPE_ALLOCATION,	/* index allocation */
} INDEX_TYPE;

/*
 *		Decode Interix file types
 *
 *	Non-Interix types are returned as plain files, because a
 *	Windows user may force patterns very similar to Interix,
 *	and most metadata files have such similar patters.
 */

u32 ntfs_interix_types(ntfs_inode *ni)
{
	ntfs_attr *na;
	u32 dt_type;
	le64 magic;

	dt_type = NTFS_DT_UNKNOWN;
	na = ntfs_attr_open(ni, AT_DATA, NULL, 0);
	if (na) {
		/*
		 * Unrecognized patterns (eg HID + SYST for metadata)
		 * are plain files or directories
		 */
		if (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY)
			dt_type = NTFS_DT_DIR;
		else
			dt_type = NTFS_DT_REG;
		if (na->data_size <= 1) {
			if (!(ni->flags & FILE_ATTR_HIDDEN))
				dt_type = (na->data_size ?
						NTFS_DT_SOCK : NTFS_DT_FIFO);
		} else {
			if ((na->data_size >= (s64)sizeof(magic))
			    && (ntfs_attr_pread(na, 0, sizeof(magic), &magic)
				== sizeof(magic))) {
				if (magic == INTX_SYMBOLIC_LINK)
					dt_type = NTFS_DT_LNK;
				else if (magic == INTX_BLOCK_DEVICE)
					dt_type = NTFS_DT_BLK;
				else if (magic == INTX_CHARACTER_DEVICE)
					dt_type = NTFS_DT_CHR;
			}
		}
		ntfs_attr_close(na);
	}
	return (dt_type);
}

/*
 *		Decode file types
 *
 *	Better only use for Interix types and junctions,
 *	unneeded complexity when used for plain files or directories
 *
 *	Error cases are logged and returned as unknown.
 */

static u32 ntfs_dir_entry_type(ntfs_inode *dir_ni, MFT_REF mref,
					FILE_ATTR_FLAGS attributes)
{
	ntfs_inode *ni;
	u32 dt_type;

	dt_type = NTFS_DT_UNKNOWN;
	ni = ntfs_inode_open(dir_ni->vol, mref);
	if (ni) {
		if (attributes & FILE_ATTR_REPARSE_POINT)
			dt_type = (ntfs_possible_symlink(ni)
				? NTFS_DT_LNK : NTFS_DT_REPARSE);
		else
			if ((attributes & FILE_ATTR_SYSTEM)
			   && !(attributes & FILE_ATTR_I30_INDEX_PRESENT))
				dt_type = ntfs_interix_types(ni);
			else
				dt_type = (attributes
						& FILE_ATTR_I30_INDEX_PRESENT
					? NTFS_DT_DIR : NTFS_DT_REG);
		if (ntfs_inode_close(ni)) {
				 /* anything special worth doing ? */
			ntfs_log_error("Failed to close inode %lld\n",
				(long long)MREF(mref));
		}
	}
	if (dt_type == NTFS_DT_UNKNOWN)
		ntfs_log_error("Could not decode the type of inode %lld\n",
				(long long)MREF(mref));
	return (dt_type);
}

/**
 * ntfs_filldir - ntfs specific filldir method
 * @dir_ni:	ntfs inode of current directory
 * @pos:	current position in directory
 * @ivcn_bits:	log(2) of index vcn size
 * @index_type:	specifies whether @iu is an index root or an index allocation
 * @iu:		index root or index block to which @ie belongs
 * @ie:		current index entry
 * @dirent:	context for filldir callback supplied by the caller
 * @filldir:	filldir callback supplied by the caller
 *
 * Pass information specifying the current directory entry @ie to the @filldir
 * callback.
 */
static int ntfs_filldir(ntfs_inode *dir_ni, s64 *pos, u8 ivcn_bits,
		const INDEX_TYPE index_type, index_union iu, INDEX_ENTRY *ie,
		void *dirent, ntfs_filldir_t filldir)
{
	FILE_NAME_ATTR *fn = &ie->key.file_name;
	unsigned dt_type;
	BOOL metadata;
	ntfschar *loname;
	int res;
	MFT_REF mref;

	ntfs_log_trace("Entering.\n");

	/* Advance the position even if going to skip the entry. */
	if (index_type == INDEX_TYPE_ALLOCATION)
		*pos = (u8*)ie - (u8*)iu.ia + (sle64_to_cpu(
				iu.ia->index_block_vcn) << ivcn_bits) +
				dir_ni->vol->mft_record_size;
	else /* if (index_type == INDEX_TYPE_ROOT) */
		*pos = (u8*)ie - (u8*)iu.ir;
	mref = le64_to_cpu(ie->indexed_file);
	metadata = (MREF(mref) != FILE_root) && (MREF(mref) < FILE_first_user);
	/* Skip root directory self reference entry. */
	if (MREF_LE(ie->indexed_file) == FILE_root)
		return 0;
	if ((ie->key.file_name.file_attributes
		     & (FILE_ATTR_REPARSE_POINT | FILE_ATTR_SYSTEM))
	    && !metadata)
		dt_type = ntfs_dir_entry_type(dir_ni, mref,
					ie->key.file_name.file_attributes);
	else if (ie->key.file_name.file_attributes
		     & FILE_ATTR_I30_INDEX_PRESENT)
		dt_type = NTFS_DT_DIR;
	else
		dt_type = NTFS_DT_REG;

		/* return metadata files and hidden files if requested */
        if ((!metadata && (NVolShowHidFiles(dir_ni->vol)
				|| !(fn->file_attributes & FILE_ATTR_HIDDEN)))
            || (NVolShowSysFiles(dir_ni->vol) && (NVolShowHidFiles(dir_ni->vol)
				|| metadata))) {
		if (NVolCaseSensitive(dir_ni->vol)) {
			res = filldir(dirent, fn->file_name,
					fn->file_name_length,
					fn->file_name_type, *pos,
					mref, dt_type);
		} else {
			loname = (ntfschar*)ntfs_malloc(2*fn->file_name_length);
			if (loname) {
				memcpy(loname, fn->file_name,
					2*fn->file_name_length);
				ntfs_name_locase(loname, fn->file_name_length,
					dir_ni->vol->locase,
					dir_ni->vol->upcase_len);
				res = filldir(dirent, loname,
					fn->file_name_length,
					fn->file_name_type, *pos,
					mref, dt_type);
				free(loname);
			} else
				res = -1;
		}
	} else
		res = 0;
	return (res);
}

/**
 * ntfs_mft_get_parent_ref - find mft reference of parent directory of an inode
 * @ni:		ntfs inode whose parent directory to find
 *
 * Find the parent directory of the ntfs inode @ni. To do this, find the first
 * file name attribute in the mft record of @ni and return the parent mft
 * reference from that.
 *
 * Note this only makes sense for directories, since files can be hard linked
 * from multiple directories and there is no way for us to tell which one is
 * being looked for.
 *
 * Technically directories can have hard links, too, but we consider that as
 * illegal as Linux/UNIX do not support directory hard links.
 *
 * Return the mft reference of the parent directory on success or -1 on error
 * with errno set to the error code.
 */
static MFT_REF ntfs_mft_get_parent_ref(ntfs_inode *ni)
{
	MFT_REF mref;
	ntfs_attr_search_ctx *ctx;
	FILE_NAME_ATTR *fn;
	int eo;

	ntfs_log_trace("Entering.\n");

	if (!ni) {
		errno = EINVAL;
		return ERR_MREF(-1);
	}

	ctx = ntfs_attr_get_search_ctx(ni, NULL);
	if (!ctx)
		return ERR_MREF(-1);
	if (ntfs_attr_lookup(AT_FILE_NAME, AT_UNNAMED, 0, 0, 0, NULL, 0, ctx)) {
		ntfs_log_error("No file name found in inode %lld\n",
			       (unsigned long long)ni->mft_no);
		goto err_out;
	}
	if (ctx->attr->non_resident) {
		ntfs_log_error("File name attribute must be resident (inode "
			       "%lld)\n", (unsigned long long)ni->mft_no);
		goto io_err_out;
	}
	fn = (FILE_NAME_ATTR*)((u8*)ctx->attr +
			le16_to_cpu(ctx->attr->value_offset));
	mref = le64_to_cpu(fn->parent_directory);
	ntfs_attr_put_search_ctx(ctx);
	return mref;
io_err_out:
	errno = EIO;
err_out:
	eo = errno;
	ntfs_attr_put_search_ctx(ctx);
	errno = eo;
	return ERR_MREF(-1);
}

/**
 * ntfs_readdir - read the contents of an ntfs directory
 * @dir_ni:	ntfs inode of current directory
 * @pos:	current position in directory
 * @dirent:	context for filldir callback supplied by the caller
 * @filldir:	filldir callback supplied by the caller
 *
 * Parse the index root and the index blocks that are marked in use in the
 * index bitmap and hand each found directory entry to the @filldir callback
 * supplied by the caller.
 *
 * Return 0 on success or -1 on error with errno set to the error code.
 *
 * Note: Index blocks are parsed in ascending vcn order, from which follows
 * that the directory entries are not returned sorted.
 */
int ntfs_readdir(ntfs_inode *dir_ni, s64 *pos,
		void *dirent, ntfs_filldir_t filldir)
{
	s64 i_size, br, ia_pos, bmp_pos, ia_start;
	ntfs_volume *vol;
	ntfs_attr *ia_na, *bmp_na = NULL;
	ntfs_attr_search_ctx *ctx = NULL;
	u8 *index_end, *bmp = NULL;
	INDEX_ROOT *ir;
	INDEX_ENTRY *ie;
	INDEX_ALLOCATION *ia = NULL;
	int rc, ir_pos, bmp_buf_size, bmp_buf_pos, eo;
	u32 index_block_size;
	u8 index_block_size_bits, index_vcn_size_bits;

	ntfs_log_trace("Entering.\n");

	if (!dir_ni || !pos || !filldir) {
		errno = EINVAL;
		return -1;
	}

	if (!(dir_ni->mrec->flags & MFT_RECORD_IS_DIRECTORY)) {
		errno = ENOTDIR;
		return -1;
	}

	vol = dir_ni->vol;

	ntfs_log_trace("Entering for inode %lld, *pos 0x%llx.\n",
			(unsigned long long)dir_ni->mft_no, (long long)*pos);

	/* Open the index allocation attribute. */
	ia_na = ntfs_attr_open(dir_ni, AT_INDEX_ALLOCATION, NTFS_INDEX_I30, 4);
	if (!ia_na) {
		if (errno != ENOENT) {
			ntfs_log_perror("Failed to open index allocation attribute. "
				"Directory inode %lld is corrupt or bug",
				(unsigned long long)dir_ni->mft_no);
			return -1;
		}
		i_size = 0;
	} else
		i_size = ia_na->data_size;

	rc = 0;

	/* Are we at end of dir yet? */
	if (*pos >= i_size + vol->mft_record_size)
		goto done;

	/* Emulate . and .. for all directories. */
	if (!*pos) {
		rc = filldir(dirent, dotdot, 1, FILE_NAME_POSIX, *pos,
				MK_MREF(dir_ni->mft_no,
				le16_to_cpu(dir_ni->mrec->sequence_number)),
				NTFS_DT_DIR);
		if (rc)
			goto err_out;
		++*pos;
	}
	if (*pos == 1) {
		MFT_REF parent_mref;

		parent_mref = ntfs_mft_get_parent_ref(dir_ni);
		if (parent_mref == ERR_MREF(-1)) {
			ntfs_log_perror("Parent directory not found");
			goto dir_err_out;
		}

		rc = filldir(dirent, dotdot, 2, FILE_NAME_POSIX, *pos,
				parent_mref, NTFS_DT_DIR);
		if (rc)
			goto err_out;
		++*pos;
	}

	ctx = ntfs_attr_get_search_ctx(dir_ni, NULL);
	if (!ctx)
		goto err_out;

	/* Get the offset into the index root attribute. */
	ir_pos = (int)*pos;
	/* Find the index root attribute in the mft record. */
	if (ntfs_attr_lookup(AT_INDEX_ROOT, NTFS_INDEX_I30, 4, CASE_SENSITIVE, 0, NULL,
			0, ctx)) {
		ntfs_log_perror("Index root attribute missing in directory inode "
				"%lld", (unsigned long long)dir_ni->mft_no);
		goto dir_err_out;
	}
	/* Get to the index root value. */
	ir = (INDEX_ROOT*)((u8*)ctx->attr +
			le16_to_cpu(ctx->attr->value_offset));

	/* Determine the size of a vcn in the directory index. */
	index_block_size = le32_to_cpu(ir->index_block_size);
	if (index_block_size < NTFS_BLOCK_SIZE ||
			index_block_size & (index_block_size - 1)) {
		ntfs_log_error("Index block size %u is invalid.\n",
				(unsigned)index_block_size);
		goto dir_err_out;
	}
	index_block_size_bits = ffs(index_block_size) - 1;
	if (vol->cluster_size <= index_block_size) {
		index_vcn_size_bits = vol->cluster_size_bits;
	} else {
		index_vcn_size_bits = NTFS_BLOCK_SIZE_BITS;
	}

	/* Are we jumping straight into the index allocation attribute? */
	if (*pos >= vol->mft_record_size) {
		ntfs_attr_put_search_ctx(ctx);
		ctx = NULL;
		goto skip_index_root;
	}

	index_end = (u8*)&ir->index + le32_to_cpu(ir->index.index_length);
	/* The first index entry. */
	ie = (INDEX_ENTRY*)((u8*)&ir->index +
			le32_to_cpu(ir->index.entries_offset));
	/*
	 * Loop until we exceed valid memory (corruption case) or until we
	 * reach the last entry or until filldir tells us it has had enough
	 * or signals an error (both covered by the rc test).
	 */
	for (;; ie = (INDEX_ENTRY*)((u8*)ie + le16_to_cpu(ie->length))) {
		ntfs_log_debug("In index root, offset %d.\n", (int)((u8*)ie - (u8*)ir));
		/* Bounds checks. */
		if ((u8*)ie < (u8*)ctx->mrec || (u8*)ie +
				sizeof(INDEX_ENTRY_HEADER) > index_end ||
				(u8*)ie + le16_to_cpu(ie->length) >
				index_end) {
			ntfs_log_error("Index root entry out of bounds in"
					" inode %lld\n",
					(unsigned long long)dir_ni->mft_no);
			goto dir_err_out;
		}
		/* The last entry cannot contain a name. */
		if (ie->ie_flags & INDEX_ENTRY_END)
			break;

		if (!le16_to_cpu(ie->length))
			goto dir_err_out;

		/* Skip index root entry if continuing previous readdir. */
		if (ir_pos > (u8*)ie - (u8*)ir)
			continue;

		/* The file name must not overflow from the entry */
		rc = ntfs_index_entry_inconsistent(vol, ie, COLLATION_FILE_NAME,
				dir_ni->mft_no, NULL);
		if (rc < 0) {
			errno = EIO;
			goto dir_err_out;
		}
		/*
		 * Submit the directory entry to ntfs_filldir(), which will
		 * invoke the filldir() callback as appropriate.
		 */
		rc = ntfs_filldir(dir_ni, pos, index_vcn_size_bits,
				INDEX_TYPE_ROOT, ir, ie, dirent, filldir);
		if (rc) {
			ntfs_attr_put_search_ctx(ctx);
			ctx = NULL;
			goto err_out;
		}
	}
	ntfs_attr_put_search_ctx(ctx);
	ctx = NULL;

	/* If there is no index allocation attribute we are finished. */
	if (!ia_na)
		goto EOD;

	/* Advance *pos to the beginning of the index allocation. */
	*pos = vol->mft_record_size;

skip_index_root:

	if (!ia_na)
		goto done;

	/* Allocate a buffer for the current index block. */
	ia = ntfs_malloc(index_block_size);
	if (!ia)
		goto err_out;

	bmp_na = ntfs_attr_open(dir_ni, AT_BITMAP, NTFS_INDEX_I30, 4);
	if (!bmp_na) {
		ntfs_log_perror("Failed to open index bitmap attribute");
		goto dir_err_out;
	}

	/* Get the offset into the index allocation attribute. */
	ia_pos = *pos - vol->mft_record_size;

	bmp_pos = ia_pos >> index_block_size_bits;
	if (bmp_pos >> 3 >= bmp_na->data_size) {
		ntfs_log_error("Current index position exceeds index bitmap "
				"size.\n");
		goto dir_err_out;
	}

	bmp_buf_size = min(bmp_na->data_size - (bmp_pos >> 3), 4096);
	bmp = ntfs_malloc(bmp_buf_size);
	if (!bmp)
		goto err_out;

	br = ntfs_attr_pread(bmp_na, bmp_pos >> 3, bmp_buf_size, bmp);
	if (br != bmp_buf_size) {
		if (br != -1)
			errno = EIO;
		ntfs_log_perror("Failed to read from index bitmap attribute");
		goto err_out;
	}

	bmp_buf_pos = 0;
	/* If the index block is not in use find the next one that is. */
	while (!(bmp[bmp_buf_pos >> 3] & (1 << (bmp_buf_pos & 7)))) {
find_next_index_buffer:
		bmp_pos++;
		bmp_buf_pos++;
		/* If we have reached the end of the bitmap, we are done. */
		if (bmp_pos >> 3 >= bmp_na->data_size)
			goto EOD;
		ia_pos = bmp_pos << index_block_size_bits;
		if (bmp_buf_pos >> 3 < bmp_buf_size)
			continue;
		/* Read next chunk from the index bitmap. */
		bmp_buf_pos = 0;
		if ((bmp_pos >> 3) + bmp_buf_size > bmp_na->data_size)
			bmp_buf_size = bmp_na->data_size - (bmp_pos >> 3);
		br = ntfs_attr_pread(bmp_na, bmp_pos >> 3, bmp_buf_size, bmp);
		if (br != bmp_buf_size) {
			if (br != -1)
				errno = EIO;
			ntfs_log_perror("Failed to read from index bitmap attribute");
			goto err_out;
		}
	}

	ntfs_log_debug("Handling index block 0x%llx.\n", (long long)bmp_pos);

	/* Read the index block starting at bmp_pos. */
	br = ntfs_attr_mst_pread(ia_na, bmp_pos << index_block_size_bits, 1,
			index_block_size, ia);
	if (br != 1) {
		if (br != -1)
			errno = EIO;
		ntfs_log_perror("Failed to read index block");
		goto err_out;
	}

	ia_start = ia_pos & ~(s64)(index_block_size - 1);
	if (ntfs_index_block_inconsistent(vol, ia_na, (INDEX_BLOCK*)ia, index_block_size,
			ia_na->ni->mft_no, ia_start >> index_vcn_size_bits)) {
		goto dir_err_out;
	}
	index_end = (u8*)&ia->index + le32_to_cpu(ia->index.index_length);

	/* The first index entry. */
	ie = (INDEX_ENTRY*)((u8*)&ia->index +
			le32_to_cpu(ia->index.entries_offset));
	/*
	 * Loop until we exceed valid memory (corruption case) or until we
	 * reach the last entry or until ntfs_filldir tells us it has had
	 * enough or signals an error (both covered by the rc test).
	 */
	for (;; ie = (INDEX_ENTRY*)((u8*)ie + le16_to_cpu(ie->length))) {
		ntfs_log_debug("In index allocation, offset 0x%llx.\n",
				(long long)ia_start + ((u8*)ie - (u8*)ia));
		/* Bounds checks. */
		if ((u8*)ie < (u8*)ia || (u8*)ie +
				sizeof(INDEX_ENTRY_HEADER) > index_end ||
				(u8*)ie + le16_to_cpu(ie->length) >
				index_end) {
			ntfs_log_error("Index entry out of bounds in directory inode "
				"%lld.\n", (unsigned long long)dir_ni->mft_no);
			goto dir_err_out;
		}
		/* The last entry cannot contain a name. */
		if (ie->ie_flags & INDEX_ENTRY_END)
			break;

		if (!le16_to_cpu(ie->length))
			goto dir_err_out;

		/* Skip index entry if continuing previous readdir. */
		if (ia_pos - ia_start > (u8*)ie - (u8*)ia)
			continue;

		/* The file name must not overflow from the entry */
		rc = ntfs_index_entry_inconsistent(vol, ie, COLLATION_FILE_NAME,
				dir_ni->mft_no, NULL);
		if (rc < 0) {
			errno = EIO;
			goto dir_err_out;
		}
		/*
		 * Submit the directory entry to ntfs_filldir(), which will
		 * invoke the filldir() callback as appropriate.
		 */
		rc = ntfs_filldir(dir_ni, pos, index_vcn_size_bits,
				INDEX_TYPE_ALLOCATION, ia, ie, dirent, filldir);
		if (rc)
			goto err_out;
	}
	goto find_next_index_buffer;
EOD:
	/* We are finished, set *pos to EOD. */
	*pos = i_size + vol->mft_record_size;
done:
	free(ia);
	free(bmp);
	if (bmp_na)
		ntfs_attr_close(bmp_na);
	if (ia_na)
		ntfs_attr_close(ia_na);
	ntfs_log_debug("EOD, *pos 0x%llx, returning 0.\n", (long long)*pos);
	return 0;
dir_err_out:
	errno = EIO;
err_out:
	eo = errno;
	ntfs_log_trace("failed.\n");
	if (ctx)
		ntfs_attr_put_search_ctx(ctx);
	free(ia);
	free(bmp);
	if (bmp_na)
		ntfs_attr_close(bmp_na);
	if (ia_na)
		ntfs_attr_close(ia_na);
	errno = eo;
	return -1;
}


/**
 * __ntfs_create - create object on ntfs volume
 * @dir_ni:	ntfs inode for directory in which create new object
 * @securid:	id of inheritable security descriptor, 0 if none
 * @name:	unicode name of new object
 * @name_len:	length of the name in unicode characters
 * @type:	type of the object to create
 * @dev:	major and minor device numbers (obtained from makedev())
 * @target:	target in unicode (only for symlinks)
 * @target_len:	length of target in unicode characters
 *
 * Internal, use ntfs_create{,_device,_symlink} wrappers instead.
 *
 * @type can be:
 *	S_IFREG		to create regular file
 *	S_IFDIR		to create directory
 *	S_IFBLK		to create block device
 *	S_IFCHR		to create character device
 *	S_IFLNK		to create symbolic link
 *	S_IFIFO		to create FIFO
 *	S_IFSOCK	to create socket
 * other values are invalid.
 *
 * @dev is used only if @type is S_IFBLK or S_IFCHR, in other cases its value
 * ignored.
 *
 * @target and @target_len are used only if @type is S_IFLNK, in other cases
 * their value ignored.
 *
 * Return opened ntfs inode that describes created object on success or NULL
 * on error with errno set to the error code.
 */
static ntfs_inode *__ntfs_create(ntfs_inode *dir_ni, le32 securid,
		const ntfschar *name, u8 name_len, mode_t type, dev_t dev,
		const ntfschar *target, int target_len)
{
	ntfs_inode *ni;
	int rollback_data = 0, rollback_sd = 0;
	int rollback_dir = 0;
	FILE_NAME_ATTR *fn = NULL;
	STANDARD_INFORMATION *si = NULL;
	int err, fn_len, si_len;
	ntfs_volume_special_files special_files;

	ntfs_log_trace("Entering.\n");

	/* Sanity checks. */
	if (!dir_ni || !name || !name_len) {
		ntfs_log_error("Invalid arguments.\n");
		errno = EINVAL;
		return NULL;
	}

	ni = ntfs_mft_record_alloc(dir_ni->vol, NULL);
	if (!ni)
		return NULL;
#if CACHE_NIDATA_SIZE
	ntfs_inode_invalidate(dir_ni->vol, ni->mft_no);
#endif
	special_files = dir_ni->vol->special_files;
	/*
	 * Create STANDARD_INFORMATION attribute.
	 * JPA Depending on available inherited security descriptor,
	 * Write STANDARD_INFORMATION v1.2 (no inheritance) or v3
	 */
	if (securid)
		si_len = sizeof(STANDARD_INFORMATION);
	else
		si_len = offsetof(STANDARD_INFORMATION, v1_end);
	si = ntfs_calloc(si_len);
	if (!si) {
		err = errno;
		goto err_out;
	}
	si->creation_time = ni->creation_time;
	si->last_data_change_time = ni->last_data_change_time;
	si->last_mft_change_time = ni->last_mft_change_time;
	si->last_access_time = ni->last_access_time;
	if (securid) {
		set_nino_flag(ni, v3_Extensions);
		ni->owner_id = si->owner_id = const_cpu_to_le32(0);
		ni->security_id = si->security_id = securid;
		ni->quota_charged = si->quota_charged = const_cpu_to_le64(0);
		ni->usn = si->usn = const_cpu_to_le64(0);
	} else
		clear_nino_flag(ni, v3_Extensions);
	if (!S_ISREG(type) && !S_ISDIR(type)) {
		switch (special_files) {
		case NTFS_FILES_WSL :
			if (!S_ISLNK(type)) {
				si->file_attributes
					= FILE_ATTRIBUTE_RECALL_ON_OPEN;
				ni->flags = FILE_ATTRIBUTE_RECALL_ON_OPEN;
			}
			break;
		default :
			si->file_attributes = FILE_ATTR_SYSTEM;
			ni->flags = FILE_ATTR_SYSTEM;
			break;
		}
	}
	ni->flags |= FILE_ATTR_ARCHIVE;
	if (NVolHideDotFiles(dir_ni->vol)
	    && (name_len > 1)
	    && (name[0] == const_cpu_to_le16('.'))
	    && (name[1] != const_cpu_to_le16('.')))
		ni->flags |= FILE_ATTR_HIDDEN;
		/*
		 * Set compression flag according to parent directory
		 * unless NTFS version < 3.0 or cluster size > 4K
		 * or compression has been disabled
		 */
	if ((dir_ni->flags & FILE_ATTR_COMPRESSED)
	   && (dir_ni->vol->major_ver >= 3)
	   && NVolCompression(dir_ni->vol)
	   && (dir_ni->vol->cluster_size <= MAX_COMPRESSION_CLUSTER_SIZE)
	   && (S_ISREG(type) || S_ISDIR(type)))
		ni->flags |= FILE_ATTR_COMPRESSED;
	/* Add STANDARD_INFORMATION to inode. */
	if (ntfs_attr_add(ni, AT_STANDARD_INFORMATION, AT_UNNAMED, 0,
			(u8*)si, si_len)) {
		err = errno;
		ntfs_log_error("Failed to add STANDARD_INFORMATION "
				"attribute.\n");
		goto err_out;
	}

	if (!securid) {
		if (ntfs_sd_add_everyone(ni)) {
			err = errno;
			goto err_out;
		}
		rollback_sd = 1;
	}

	if (S_ISDIR(type)) {
		INDEX_ROOT *ir = NULL;
		INDEX_ENTRY *ie;
		int ir_len, index_len;

		/* Create INDEX_ROOT attribute. */
		index_len = sizeof(INDEX_HEADER) + sizeof(INDEX_ENTRY_HEADER);
		ir_len = offsetof(INDEX_ROOT, index) + index_len;
		ir = ntfs_calloc(ir_len);
		if (!ir) {
			err = errno;
			goto err_out;
		}
		ir->type = AT_FILE_NAME;
		ir->collation_rule = COLLATION_FILE_NAME;
		ir->index_block_size = cpu_to_le32(ni->vol->indx_record_size);
		if (ni->vol->cluster_size <= ni->vol->indx_record_size)
			ir->clusters_per_index_block =
					ni->vol->indx_record_size >>
					ni->vol->cluster_size_bits;
		else
			ir->clusters_per_index_block =
					ni->vol->indx_record_size >>
					NTFS_BLOCK_SIZE_BITS;
		ir->index.entries_offset = const_cpu_to_le32(sizeof(INDEX_HEADER));
		ir->index.index_length = cpu_to_le32(index_len);
		ir->index.allocated_size = cpu_to_le32(index_len);
		ie = (INDEX_ENTRY*)((u8*)ir + sizeof(INDEX_ROOT));
		ie->length = const_cpu_to_le16(sizeof(INDEX_ENTRY_HEADER));
		ie->key_length = const_cpu_to_le16(0);
		ie->ie_flags = INDEX_ENTRY_END;
		/* Add INDEX_ROOT attribute to inode. */
		if (ntfs_attr_add(ni, AT_INDEX_ROOT, NTFS_INDEX_I30, 4,
				(u8*)ir, ir_len)) {
			err = errno;
			free(ir);
			ntfs_log_error("Failed to add INDEX_ROOT attribute.\n");
			goto err_out;
		}
		free(ir);
	} else {
		INTX_FILE *data;
		int data_len;

		switch (type) {
			case S_IFBLK:
			case S_IFCHR:
				switch (special_files) {
				case NTFS_FILES_WSL :
					data_len = 0;
					data = (INTX_FILE*)NULL;
					break;
				default :
					data_len = offsetof(INTX_FILE,
								device_end);
					data = (INTX_FILE*)ntfs_malloc(
								data_len);
					if (!data) {
						err = errno;
						goto err_out;
					}
					data->major = cpu_to_le64(major(dev));
					data->minor = cpu_to_le64(minor(dev));
					if (type == S_IFBLK)
						data->magic
							= INTX_BLOCK_DEVICE;
					if (type == S_IFCHR)
						data->magic
							= INTX_CHARACTER_DEVICE;
					break;
				}
				break;
			case S_IFLNK:
				switch (special_files) {
				case NTFS_FILES_WSL :
					data_len = 0;
					data = (INTX_FILE*)NULL;
					break;
				default :
					data_len = sizeof(INTX_FILE_TYPES) +
						target_len * sizeof(ntfschar);
					data = (INTX_FILE*)ntfs_malloc(
								data_len);
					if (!data) {
						err = errno;
						goto err_out;
					}
					data->magic = INTX_SYMBOLIC_LINK;
					memcpy(data->target, target,
						target_len * sizeof(ntfschar));
					break;
				}
				break;
			case S_IFSOCK:
				data = NULL;
				if (special_files == NTFS_FILES_WSL)
					data_len = 0;
				else
					data_len = 1;
				break;
			default: /* FIFO or regular file. */
				data = NULL;
				data_len = 0;
				break;
		}
		/* Add DATA attribute to inode. */
		if (ntfs_attr_add(ni, AT_DATA, AT_UNNAMED, 0, (u8*)data,
				data_len)) {
			err = errno;
			ntfs_log_error("Failed to add DATA attribute.\n");
			free(data);
			goto err_out;
		}
		rollback_data = 1;
		free(data);
	}
	/* Create FILE_NAME attribute. */
	fn_len = sizeof(FILE_NAME_ATTR) + name_len * sizeof(ntfschar);
	fn = ntfs_calloc(fn_len);
	if (!fn) {
		err = errno;
		goto err_out;
	}
	fn->parent_directory = MK_LE_MREF(dir_ni->mft_no,
			le16_to_cpu(dir_ni->mrec->sequence_number));
	fn->file_name_length = name_len;
	fn->file_name_type = FILE_NAME_POSIX;
	if (S_ISDIR(type))
		fn->file_attributes = FILE_ATTR_I30_INDEX_PRESENT;
	if (!S_ISREG(type) && !S_ISDIR(type)) {
		if (special_files == NTFS_FILES_INTERIX)
			fn->file_attributes = FILE_ATTR_SYSTEM;
	} else
		fn->file_attributes |= ni->flags & FILE_ATTR_COMPRESSED;
	fn->file_attributes |= FILE_ATTR_ARCHIVE;
	fn->file_attributes |= ni->flags & FILE_ATTR_HIDDEN;
	fn->creation_time = ni->creation_time;
	fn->last_data_change_time = ni->last_data_change_time;
	fn->last_mft_change_time = ni->last_mft_change_time;
	fn->last_access_time = ni->last_access_time;
	if (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY)
		fn->data_size = fn->allocated_size = const_cpu_to_sle64(0);
	else {
		fn->data_size = cpu_to_sle64(ni->data_size);
		fn->allocated_size = cpu_to_sle64(ni->allocated_size);
	}
	memcpy(fn->file_name, name, name_len * sizeof(ntfschar));
	/* Add FILE_NAME attribute to inode. */
	if (ntfs_attr_add(ni, AT_FILE_NAME, AT_UNNAMED, 0, (u8*)fn, fn_len)) {
		err = errno;
		ntfs_log_error("Failed to add FILE_NAME attribute.\n");
		goto err_out;
	}
	/* Add FILE_NAME attribute to index. */
	if (ntfs_index_add_filename(dir_ni, fn, MK_MREF(ni->mft_no,
			le16_to_cpu(ni->mrec->sequence_number)))) {
		err = errno;
		ntfs_log_perror("Failed to add entry to the index");
		goto err_out;
	}
	rollback_dir = 1;
	/* Set hard links count and directory flag. */
	ni->mrec->link_count = const_cpu_to_le16(1);
	if (S_ISDIR(type))
		ni->mrec->flags |= MFT_RECORD_IS_DIRECTORY;
	/* Add reparse data */
	if (special_files == NTFS_FILES_WSL) {
		switch (type) {
		case S_IFLNK :
			err = ntfs_reparse_set_wsl_symlink(ni, target,
					target_len);
			break;
		case S_IFIFO :
		case S_IFSOCK :
		case S_IFCHR :
		case S_IFBLK :
			err = ntfs_reparse_set_wsl_not_symlink(ni,
					type);
			if (!err) {
				err = ntfs_ea_set_wsl_not_symlink(ni,
						type, dev);
				if (err)
					ntfs_remove_ntfs_reparse_data(ni);
			}
			break;
		default :
			err = 0;
			break;
		}
		if (err) {
			err = errno;
			goto err_out;
		}
	}
	ntfs_inode_mark_dirty(ni);
	/* Done! */
	free(fn);
	free(si);
	ntfs_log_trace("Done.\n");
	return ni;
err_out:
	ntfs_log_trace("Failed.\n");

	if (rollback_dir)
		ntfs_index_remove(dir_ni, ni, fn, fn_len);

	if (rollback_sd)
		ntfs_attr_remove(ni, AT_SECURITY_DESCRIPTOR, AT_UNNAMED, 0);

	if (rollback_data)
		ntfs_attr_remove(ni, AT_DATA, AT_UNNAMED, 0);
	/*
	 * Free extent MFT records (should not exist any with current
	 * ntfs_create implementation, but for any case if something will be
	 * changed in the future).
	 */
	while (ni->nr_extents)
		if (ntfs_mft_record_free(ni->vol, *(ni->extent_nis))) {
			err = errno;
			ntfs_log_error("Failed to free extent MFT record.  "
					"Leaving inconsistent metadata.\n");
		}
	if (ntfs_mft_record_free(ni->vol, ni))
		ntfs_log_error("Failed to free MFT record.  "
				"Leaving inconsistent metadata. Run chkdsk.\n");
	free(fn);
	free(si);
	errno = err;
	return NULL;
}

/**
 * Some wrappers around __ntfs_create() ...
 */

ntfs_inode *ntfs_create(ntfs_inode *dir_ni, le32 securid, const ntfschar *name,
		u8 name_len, mode_t type)
{
	if (type != S_IFREG && type != S_IFDIR && type != S_IFIFO &&
			type != S_IFSOCK) {
		ntfs_log_error("Invalid arguments.\n");
		return NULL;
	}
	return __ntfs_create(dir_ni, securid, name, name_len, type, 0, NULL, 0);
}

ntfs_inode *ntfs_create_device(ntfs_inode *dir_ni, le32 securid,
		const ntfschar *name, u8 name_len, mode_t type, dev_t dev)
{
	if (type != S_IFCHR && type != S_IFBLK) {
		ntfs_log_error("Invalid arguments.\n");
		return NULL;
	}
	return __ntfs_create(dir_ni, securid, name, name_len, type, dev, NULL, 0);
}

ntfs_inode *ntfs_create_symlink(ntfs_inode *dir_ni, le32 securid,
		const ntfschar *name, u8 name_len, const ntfschar *target,
		int target_len)
{
	if (!target || !target_len) {
		ntfs_log_error("%s: Invalid argument (%p, %d)\n", __FUNCTION__,
			       target, target_len);
		return NULL;
	}
	return __ntfs_create(dir_ni, securid, name, name_len, S_IFLNK, 0,
			target, target_len);
}

int ntfs_check_empty_dir(ntfs_inode *ni)
{
	ntfs_attr *na;
	int ret = 0;

	if (!(ni->mrec->flags & MFT_RECORD_IS_DIRECTORY))
		return 0;

	na = ntfs_attr_open(ni, AT_INDEX_ROOT, NTFS_INDEX_I30, 4);
	if (!na) {
		errno = EIO;
		ntfs_log_perror("Failed to open directory");
		return -1;
	}

	/* Non-empty directory? */
	if ((na->data_size != sizeof(INDEX_ROOT) + sizeof(INDEX_ENTRY_HEADER))){
		/* Both ENOTEMPTY and EEXIST are ok. We use the more common. */
		errno = ENOTEMPTY;
		ntfs_log_debug("Directory is not empty\n");
		ret = -1;
	}

	ntfs_attr_close(na);
	return ret;
}

static int ntfs_check_unlinkable_dir(ntfs_inode *ni, FILE_NAME_ATTR *fn)
{
	int link_count = le16_to_cpu(ni->mrec->link_count);
	int ret;

	ret = ntfs_check_empty_dir(ni);
	if (!ret || errno != ENOTEMPTY)
		return ret;
	/*
	 * Directory is non-empty, so we can unlink only if there is more than
	 * one "real" hard link, i.e. links aren't different DOS and WIN32 names
	 */
	if ((link_count == 1) ||
	    (link_count == 2 && fn->file_name_type == FILE_NAME_DOS)) {
		errno = ENOTEMPTY;
		ntfs_log_debug("Non-empty directory without hard links\n");
		goto no_hardlink;
	}

	ret = 0;
no_hardlink:
	return ret;
}

/**
 * ntfs_delete - delete file or directory from ntfs volume
 * @ni:		ntfs inode for object to delte
 * @dir_ni:	ntfs inode for directory in which delete object
 * @name:	unicode name of the object to delete
 * @name_len:	length of the name in unicode characters
 *
 * @ni is always closed after the call to this function (even if it failed),
 * user does not need to call ntfs_inode_close himself.
 *
 * Return 0 on success or -1 on error with errno set to the error code.
 */
int ntfs_delete(ntfs_volume *vol, const char *pathname,
		ntfs_inode *ni, ntfs_inode *dir_ni, const ntfschar *name,
		u8 name_len)
{
	ntfs_attr_search_ctx *actx = NULL;
	FILE_NAME_ATTR *fn = NULL;
	BOOL looking_for_dos_name = FALSE, looking_for_win32_name = FALSE;
	BOOL case_sensitive_match = TRUE;
	int err = 0;
#if CACHE_NIDATA_SIZE
	int i;
#endif
#if CACHE_INODE_SIZE
	struct CACHED_INODE item;
	const char *p;
	u64 inum = (u64)-1;
	int count;
#endif
#if CACHE_LOOKUP_SIZE
	struct CACHED_LOOKUP lkitem;
#endif

	ntfs_log_trace("Entering.\n");

	if (!ni || !dir_ni || !name || !name_len) {
		ntfs_log_error("Invalid arguments.\n");
		errno = EINVAL;
		goto err_out;
	}
	if (ni->nr_extents == -1)
		ni = ni->base_ni;
	if (dir_ni->nr_extents == -1)
		dir_ni = dir_ni->base_ni;
	/*
	 * Search for FILE_NAME attribute with such name. If it's in POSIX or
	 * WIN32_AND_DOS namespace, then simply remove it from index and inode.
	 * If filename in DOS or in WIN32 namespace, then remove DOS name first,
	 * only then remove WIN32 name.
	 */
	actx = ntfs_attr_get_search_ctx(ni, NULL);
	if (!actx)
		goto err_out;
search:
	while (!(err = ntfs_attr_lookup(AT_FILE_NAME, AT_UNNAMED, 0,
					CASE_SENSITIVE, 0, NULL, 0, actx))) {
	#ifdef DEBUG
		char *s;
	#endif
		IGNORE_CASE_BOOL case_sensitive = IGNORE_CASE;

		fn = (FILE_NAME_ATTR*)((u8*)actx->attr +
				le16_to_cpu(actx->attr->value_offset));
	#ifdef DEBUG
		s = ntfs_attr_name_get(fn->file_name, fn->file_name_length);
		ntfs_log_trace("name: '%s'  type: %d  dos: %d  win32: %d  "
			       "case: %d\n", s, fn->file_name_type,
			       looking_for_dos_name, looking_for_win32_name,
			       case_sensitive_match);
		ntfs_attr_name_free(&s);
	#endif
		if (looking_for_dos_name) {
			if (fn->file_name_type == FILE_NAME_DOS)
				break;
			else
				continue;
		}
		if (looking_for_win32_name) {
			if  (fn->file_name_type == FILE_NAME_WIN32)
				break;
			else
				continue;
		}

		/* Ignore hard links from other directories */
		if (dir_ni->mft_no != MREF_LE(fn->parent_directory)) {
			ntfs_log_debug("MFT record numbers don't match "
				       "(%llu != %llu)\n",
				       (long long unsigned)dir_ni->mft_no,
				       (long long unsigned)MREF_LE(fn->parent_directory));
			continue;
		}
		if (case_sensitive_match
		    || ((fn->file_name_type == FILE_NAME_POSIX)
			&& NVolCaseSensitive(ni->vol)))
			case_sensitive = CASE_SENSITIVE;

		if (ntfs_names_are_equal(fn->file_name, fn->file_name_length,
					 name, name_len, case_sensitive,
					 ni->vol->upcase, ni->vol->upcase_len)){

			if (fn->file_name_type == FILE_NAME_WIN32) {
				looking_for_dos_name = TRUE;
				ntfs_attr_reinit_search_ctx(actx);
				continue;
			}
			if (fn->file_name_type == FILE_NAME_DOS)
				looking_for_dos_name = TRUE;
			break;
		}
	}
	if (err) {
		/*
		 * If case sensitive search failed, then try once again
		 * ignoring case.
		 */
		if (errno == ENOENT && case_sensitive_match) {
			case_sensitive_match = FALSE;
			ntfs_attr_reinit_search_ctx(actx);
			goto search;
		}
		goto err_out;
	}

	if (ntfs_check_unlinkable_dir(ni, fn) < 0)
		goto err_out;

	if (ntfs_index_remove(dir_ni, ni, fn, le32_to_cpu(actx->attr->value_length)))
		goto err_out;

	/*
	 * Keep the last name in place, this is useful for undeletion
	 * (Windows also does so), however delete the name if it were
	 * in an extent, to avoid leaving an attribute list.
	 */
	if ((ni->mrec->link_count == const_cpu_to_le16(1)) && !actx->base_ntfs_ino) {
			/* make sure to not loop to another search */
		looking_for_dos_name = FALSE;
	} else {
		if (ntfs_attr_record_rm(actx))
			goto err_out;
	}

	ni->mrec->link_count = cpu_to_le16(le16_to_cpu(
			ni->mrec->link_count) - 1);

	ntfs_inode_mark_dirty(ni);
	if (looking_for_dos_name) {
		looking_for_dos_name = FALSE;
		looking_for_win32_name = TRUE;
		ntfs_attr_reinit_search_ctx(actx);
		goto search;
	}
	/* TODO: Update object id, quota and securiry indexes if required. */
	/*
	 * If hard link count is not equal to zero then we are done. In other
	 * case there are no reference to this inode left, so we should free all
	 * non-resident attributes and mark all MFT record as not in use.
	 */
#if CACHE_LOOKUP_SIZE
			/* invalidate entry in lookup cache */
	lkitem.name = (const char*)NULL;
	lkitem.namesize = 0;
	lkitem.inum = ni->mft_no;
	lkitem.parent = dir_ni->mft_no;
	ntfs_invalidate_cache(vol->lookup_cache, GENERIC(&lkitem),
			lookup_cache_inv_compare, CACHE_NOHASH);
#endif
#if CACHE_INODE_SIZE
	inum = ni->mft_no;
	if (pathname) {
			/* invalide cache entry, even if there was an error */
		/* Remove leading /'s. */
		p = pathname;
		while (*p == PATH_SEP)
			p++;
		if (p[0] && (p[strlen(p)-1] == PATH_SEP))
			ntfs_log_error("Unnormalized path %s\n",pathname);
		item.pathname = p;
		item.varsize = strlen(p);
	} else {
		item.pathname = (const char*)NULL;
		item.varsize = 0;
	}
	item.inum = inum;
	count = ntfs_invalidate_cache(vol->xinode_cache, GENERIC(&item),
				inode_cache_inv_compare, CACHE_NOHASH);
	if (pathname && !count)
		ntfs_log_error("Could not delete inode cache entry for %s\n",
			pathname);
#endif
	if (ni->mrec->link_count) {
		ntfs_inode_update_times(ni, NTFS_UPDATE_CTIME);
		goto ok;
	}
	if (ntfs_delete_reparse_index(ni)) {
		/*
		 * Failed to remove the reparse index : proceed anyway
		 * This is not a critical error, the entry is useless
		 * because of sequence_number, and stopping file deletion
		 * would be much worse as the file is not referenced now.
		 */
		err = errno;
	}
	if (ntfs_delete_object_id_index(ni)) {
		/*
		 * Failed to remove the object id index : proceed anyway
		 * This is not a critical error.
		 */
		err = errno;
	}
	ntfs_attr_reinit_search_ctx(actx);
	while (!ntfs_attrs_walk(actx)) {
		if (actx->attr->non_resident) {
			runlist *rl;

			rl = ntfs_mapping_pairs_decompress(ni->vol, actx->attr,
					NULL);
			if (!rl) {
				err = errno;
				ntfs_log_error("Failed to decompress runlist.  "
						"Leaving inconsistent metadata.\n");
				continue;
			}
			if (ntfs_cluster_free_from_rl(ni->vol, rl)) {
				err = errno;
				ntfs_log_error("Failed to free clusters.  "
						"Leaving inconsistent metadata.\n");
				continue;
			}
			free(rl);
		}
	}
	if (errno != ENOENT) {
		err = errno;
		ntfs_log_error("Attribute enumeration failed.  "
				"Probably leaving inconsistent metadata.\n");
	}
	/* All extents should be attached after attribute walk. */
#if CACHE_NIDATA_SIZE
		/*
		 * Disconnect extents before deleting them, so they are
		 * not wrongly moved to cache through the chainings
		 */
	for (i=ni->nr_extents-1; i>=0; i--) {
		ni->extent_nis[i]->base_ni = (ntfs_inode*)NULL;
		ni->extent_nis[i]->nr_extents = 0;
		if (ntfs_mft_record_free(ni->vol, ni->extent_nis[i])) {
			err = errno;
			ntfs_log_error("Failed to free extent MFT record.  "
					"Leaving inconsistent metadata.\n");
		}
	}
	free(ni->extent_nis);
	ni->nr_extents = 0;
	ni->extent_nis = (ntfs_inode**)NULL;
#else
	while (ni->nr_extents)
		if (ntfs_mft_record_free(ni->vol, *(ni->extent_nis))) {
			err = errno;
			ntfs_log_error("Failed to free extent MFT record.  "
					"Leaving inconsistent metadata.\n");
		}
#endif
	debug_double_inode(ni->mft_no,0);
	if (ntfs_mft_record_free(ni->vol, ni)) {
		err = errno;
		ntfs_log_error("Failed to free base MFT record.  "
				"Leaving inconsistent metadata.\n");
	}
	ni = NULL;
ok:
	ntfs_inode_update_times(dir_ni, NTFS_UPDATE_MCTIME);
out:
	if (actx)
		ntfs_attr_put_search_ctx(actx);
	if (ntfs_inode_close(dir_ni) && !err)
		err = errno;
	if (ntfs_inode_close(ni) && !err)
		err = errno;
	if (err) {
		errno = err;
		ntfs_log_debug("Could not delete file: %s\n", strerror(errno));
		return -1;
	}
	ntfs_log_trace("Done.\n");
	return 0;
err_out:
	err = errno;
	goto out;
}

/**
 * ntfs_link - create hard link for file or directory
 * @ni:		ntfs inode for object to create hard link
 * @dir_ni:	ntfs inode for directory in which new link should be placed
 * @name:	unicode name of the new link
 * @name_len:	length of the name in unicode characters
 *
 * NOTE: At present we allow creating hardlinks to directories, we use them
 * in a temporary state during rename. But it's defenitely bad idea to have
 * hard links to directories as a result of operation.
 * FIXME: Create internal  __ntfs_link that allows hard links to a directories
 * and external ntfs_link that do not. Write ntfs_rename that uses __ntfs_link.
 *
 * Return 0 on success or -1 on error with errno set to the error code.
 */
static int ntfs_link_i(ntfs_inode *ni, ntfs_inode *dir_ni, const ntfschar *name,
			 u8 name_len, FILE_NAME_TYPE_FLAGS nametype)
{
	FILE_NAME_ATTR *fn = NULL;
	int fn_len, err;

	ntfs_log_trace("Entering.\n");

	if (!ni || !dir_ni || !name || !name_len ||
			ni->mft_no == dir_ni->mft_no) {
		err = EINVAL;
		ntfs_log_perror("ntfs_link wrong arguments");
		goto err_out;
	}

	if (NVolHideDotFiles(dir_ni->vol)) {
		/* Set hidden flag according to the latest name */
		if ((name_len > 1)
		    && (name[0] == const_cpu_to_le16('.'))
		    && (name[1] != const_cpu_to_le16('.')))
			ni->flags |= FILE_ATTR_HIDDEN;
		else
			ni->flags &= ~FILE_ATTR_HIDDEN;
	}

	/* Create FILE_NAME attribute. */
	fn_len = sizeof(FILE_NAME_ATTR) + name_len * sizeof(ntfschar);
	fn = ntfs_calloc(fn_len);
	if (!fn) {
		err = errno;
		goto err_out;
	}
	fn->parent_directory = MK_LE_MREF(dir_ni->mft_no,
			le16_to_cpu(dir_ni->mrec->sequence_number));
	fn->file_name_length = name_len;
	fn->file_name_type = nametype;
	fn->file_attributes = ni->flags;
	if (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) {
		fn->file_attributes |= FILE_ATTR_I30_INDEX_PRESENT;
		fn->data_size = fn->allocated_size = const_cpu_to_sle64(0);
	} else {
		fn->allocated_size = cpu_to_sle64(ni->allocated_size);
		fn->data_size = cpu_to_sle64(ni->data_size);
	}
	fn->creation_time = ni->creation_time;
	fn->last_data_change_time = ni->last_data_change_time;
	fn->last_mft_change_time = ni->last_mft_change_time;
	fn->last_access_time = ni->last_access_time;
	memcpy(fn->file_name, name, name_len * sizeof(ntfschar));
	/* Add FILE_NAME attribute to index. */
	if (ntfs_index_add_filename(dir_ni, fn, MK_MREF(ni->mft_no,
			le16_to_cpu(ni->mrec->sequence_number)))) {
		err = errno;
		ntfs_log_perror("Failed to add filename to the index");
		goto err_out;
	}
	/* Add FILE_NAME attribute to inode. */
	if (ntfs_attr_add(ni, AT_FILE_NAME, AT_UNNAMED, 0, (u8*)fn, fn_len)) {
		ntfs_log_error("Failed to add FILE_NAME attribute.\n");
		err = errno;
		/* Try to remove just added attribute from index. */
		if (ntfs_index_remove(dir_ni, ni, fn, fn_len))
			goto rollback_failed;
		goto err_out;
	}
	/* Increment hard links count. */
	ni->mrec->link_count = cpu_to_le16(le16_to_cpu(
			ni->mrec->link_count) + 1);
	/* Done! */
	ntfs_inode_mark_dirty(ni);
	free(fn);
	ntfs_log_trace("Done.\n");
	return 0;
rollback_failed:
	ntfs_log_error("Rollback failed. Leaving inconsistent metadata.\n");
err_out:
	free(fn);
	errno = err;
	return -1;
}

int ntfs_link(ntfs_inode *ni, ntfs_inode *dir_ni, const ntfschar *name,
		u8 name_len)
{
	return (ntfs_link_i(ni, dir_ni, name, name_len, FILE_NAME_POSIX));
}

/*
 *		Get a parent directory from an inode entry
 *
 *	This is only used in situations where the path used to access
 *	the current file is not known for sure. The result may be different
 *	from the path when the file is linked in several parent directories.
 *
 *	Currently this is only used for translating ".." in the target
 *	of a Vista relative symbolic link
 */

ntfs_inode *ntfs_dir_parent_inode(ntfs_inode *ni)
{
	ntfs_inode *dir_ni = (ntfs_inode*)NULL;
	u64 inum;
	FILE_NAME_ATTR *fn;
	ntfs_attr_search_ctx *ctx;

	if (ni->mft_no != FILE_root) {
			/* find the name in the attributes */
		ctx = ntfs_attr_get_search_ctx(ni, NULL);
		if (!ctx)
			return ((ntfs_inode*)NULL);

		if (!ntfs_attr_lookup(AT_FILE_NAME, AT_UNNAMED, 0,
				CASE_SENSITIVE,	0, NULL, 0, ctx)) {
			/* We know this will always be resident. */
			fn = (FILE_NAME_ATTR*)((u8*)ctx->attr +
					le16_to_cpu(ctx->attr->value_offset));
			inum = le64_to_cpu(fn->parent_directory);
			if (inum != (u64)-1) {
				dir_ni = ntfs_inode_open(ni->vol, MREF(inum));
			}
		}
		ntfs_attr_put_search_ctx(ctx);
	}
	return (dir_ni);
}

#define MAX_DOS_NAME_LENGTH	 12

/*
 *		Get a DOS name for a file in designated directory
 *
 *	Not allowed if there are several non-dos names (EMLINK)
 *
 *	Returns size if found
 *		0 if not found
 *		-1 if there was an error (described by errno)
 */

static int get_dos_name(ntfs_inode *ni, u64 dnum, ntfschar *dosname)
{
	size_t outsize = 0;
	int namecount = 0;
	FILE_NAME_ATTR *fn;
	ntfs_attr_search_ctx *ctx;

		/* find the name in the attributes */
	ctx = ntfs_attr_get_search_ctx(ni, NULL);
	if (!ctx)
		return -1;

	while (!ntfs_attr_lookup(AT_FILE_NAME, AT_UNNAMED, 0, CASE_SENSITIVE,
			0, NULL, 0, ctx)) {
		/* We know this will always be resident. */
		fn = (FILE_NAME_ATTR*)((u8*)ctx->attr +
				le16_to_cpu(ctx->attr->value_offset));

		if (fn->file_name_type != FILE_NAME_DOS)
			namecount++;
		if ((fn->file_name_type & FILE_NAME_DOS)
		    && (MREF_LE(fn->parent_directory) == dnum)) {
				/*
				 * Found a DOS or WIN32+DOS name for the entry
				 * copy name, after truncation for safety
				 */
			outsize = fn->file_name_length;
/* TODO : reject if name is too long ? */
			if (outsize > MAX_DOS_NAME_LENGTH)
				outsize = MAX_DOS_NAME_LENGTH;
			memcpy(dosname,fn->file_name,outsize*sizeof(ntfschar));
		}
	}
	ntfs_attr_put_search_ctx(ctx);
	if ((outsize > 0) && (namecount > 1)) {
		outsize = -1;
		errno = EMLINK; /* this error implies there is a dos name */
	}
	return (outsize);
}


/*
 *		Get a long name for a file in designated directory
 *
 *	Not allowed if there are several non-dos names (EMLINK)
 *
 *	Returns size if found
 *		0 if not found
 *		-1 if there was an error (described by errno)
 */

static int get_long_name(ntfs_inode *ni, u64 dnum, ntfschar *longname)
{
	size_t outsize = 0;
	int namecount = 0;
	FILE_NAME_ATTR *fn;
	ntfs_attr_search_ctx *ctx;

		/* find the name in the attributes */
	ctx = ntfs_attr_get_search_ctx(ni, NULL);
	if (!ctx)
		return -1;

		/* first search for WIN32 or DOS+WIN32 names */
	while (!ntfs_attr_lookup(AT_FILE_NAME, AT_UNNAMED, 0, CASE_SENSITIVE,
			0, NULL, 0, ctx)) {
		/* We know this will always be resident. */
		fn = (FILE_NAME_ATTR*)((u8*)ctx->attr +
				le16_to_cpu(ctx->attr->value_offset));

		if (fn->file_name_type != FILE_NAME_DOS)
			namecount++;
		if ((fn->file_name_type & FILE_NAME_WIN32)
		    && (MREF_LE(fn->parent_directory) == dnum)) {
				/*
				 * Found a WIN32 or WIN32+DOS name for the entry
				 * copy name
				 */
			outsize = fn->file_name_length;
			memcpy(longname,fn->file_name,outsize*sizeof(ntfschar));
		}
	}
	if (namecount > 1) {
		ntfs_attr_put_search_ctx(ctx);
		errno = EMLINK;
		return -1;
	}
		/* if not found search for POSIX names */
	if (!outsize) {
		ntfs_attr_reinit_search_ctx(ctx);
	while (!ntfs_attr_lookup(AT_FILE_NAME, AT_UNNAMED, 0, CASE_SENSITIVE,
			0, NULL, 0, ctx)) {
		/* We know this will always be resident. */
		fn = (FILE_NAME_ATTR*)((u8*)ctx->attr +
				le16_to_cpu(ctx->attr->value_offset));

		if ((fn->file_name_type == FILE_NAME_POSIX)
		    && (MREF_LE(fn->parent_directory) == dnum)) {
				/*
				 * Found a POSIX name for the entry
				 * copy name
				 */
			outsize = fn->file_name_length;
			memcpy(longname,fn->file_name,outsize*sizeof(ntfschar));
		}
	}
	}
	ntfs_attr_put_search_ctx(ctx);
	return (outsize);
}


/*
 *		Get the ntfs DOS name into an extended attribute
 */

int ntfs_get_ntfs_dos_name(ntfs_inode *ni, ntfs_inode *dir_ni,
			char *value, size_t size)
{
	int outsize = 0;
	char *outname = (char*)NULL;
	u64 dnum;
	int doslen;
	ntfschar dosname[MAX_DOS_NAME_LENGTH];

	dnum = dir_ni->mft_no;
	doslen = get_dos_name(ni, dnum, dosname);
	if (doslen > 0) {
			/*
			 * Found a DOS name for the entry, make
			 * uppercase and encode into the buffer
			 * if there is enough space
			 */
		ntfs_name_upcase(dosname, doslen,
				ni->vol->upcase, ni->vol->upcase_len);
		outsize = ntfs_ucstombs(dosname, doslen, &outname, 0);
		if (outsize < 0) {
			ntfs_log_error("Cannot represent dosname in current locale.\n");
			outsize = -errno;
		} else {
			if (value && (outsize <= (int)size))
				memcpy(value, outname, outsize);
			else
				if (size && (outsize > (int)size))
					outsize = -ERANGE;
			free(outname);
		}
	} else {
		if (doslen == 0)
			errno = ENODATA;
		outsize = -errno;
	}
	return (outsize);
}

/*
 *		Change the name space of an existing file or directory
 *
 *	Returns the old namespace if successful
 *		-1 if an error occurred (described by errno)
 */

static int set_namespace(ntfs_inode *ni, ntfs_inode *dir_ni,
			const ntfschar *name, int len,
			FILE_NAME_TYPE_FLAGS nametype)
{
	ntfs_attr_search_ctx *actx;
	ntfs_index_context *icx;
	FILE_NAME_ATTR *fnx;
	FILE_NAME_ATTR *fn = NULL;
	BOOL found;
	int lkup;
	int ret;

	ret = -1;
	actx = ntfs_attr_get_search_ctx(ni, NULL);
	if (actx) {
		found = FALSE;
		do {
			lkup = ntfs_attr_lookup(AT_FILE_NAME, AT_UNNAMED, 0,
	                        CASE_SENSITIVE, 0, NULL, 0, actx);
			if (!lkup) {
				fn = (FILE_NAME_ATTR*)((u8*)actx->attr +
				     le16_to_cpu(actx->attr->value_offset));
				found = (MREF_LE(fn->parent_directory)
						== dir_ni->mft_no)
					&& !memcmp(fn->file_name, name,
						len*sizeof(ntfschar));
			}
		} while (!lkup && !found);
		if (found) {
			icx = ntfs_index_ctx_get(dir_ni, NTFS_INDEX_I30, 4);
			if (icx) {
				lkup = ntfs_index_lookup((char*)fn, len, icx);
				if (!lkup && icx->data && icx->data_len) {
					fnx = (FILE_NAME_ATTR*)icx->data;
					ret = fn->file_name_type;
					fn->file_name_type = nametype;
					fnx->file_name_type = nametype;
					ntfs_inode_mark_dirty(ni);
					ntfs_index_entry_mark_dirty(icx);
				}
			ntfs_index_ctx_put(icx);
			}
		}
		ntfs_attr_put_search_ctx(actx);
	}
	return (ret);
}

/*
 *		Set a DOS name to a file and adjust name spaces
 *
 *	If the new names are collapsible (same uppercased chars) :
 *
 * - the existing DOS name or DOS+Win32 name is made Posix
 * - if it was a real DOS name, the existing long name is made DOS+Win32
 *        and the existing DOS name is deleted
 * - finally the existing long name is made DOS+Win32 unless already done
 *
 *	If the new names are not collapsible :
 *
 * - insert the short name as a DOS name
 * - delete the old long name or existing short name
 * - insert the new long name (as a Win32 or DOS+Win32 name)
 *
 * Deleting the old long name will not delete the file
 * provided the old name was in the Posix name space,
 * because the alternate name has been set before.
 *
 * The inodes of file and parent directory are always closed
 *
 * Returns 0 if successful
 *	   -1 if failed
 */

static int set_dos_name(ntfs_inode *ni, ntfs_inode *dir_ni,
			const ntfschar *shortname, int shortlen,
			const ntfschar *longname, int longlen,
			const ntfschar *deletename, int deletelen, BOOL existed)
{
	unsigned int linkcount;
	ntfs_volume *vol;
	BOOL collapsible;
	BOOL deleted;
	BOOL done;
	FILE_NAME_TYPE_FLAGS oldnametype;
	u64 dnum;
	u64 fnum;
	int res;

	res = -1;
	vol = ni->vol;
	dnum = dir_ni->mft_no;
	fnum = ni->mft_no;
				/* save initial link count */
	linkcount = le16_to_cpu(ni->mrec->link_count);

		/* check whether the same name may be used as DOS and WIN32 */
	collapsible = ntfs_collapsible_chars(ni->vol, shortname, shortlen,
						longname, longlen);
	if (collapsible) {
		deleted = FALSE;
		done = FALSE;
		if (existed) {
			oldnametype = set_namespace(ni, dir_ni, deletename,
					deletelen, FILE_NAME_POSIX);
			if (oldnametype == FILE_NAME_DOS) {
				if (set_namespace(ni, dir_ni, longname, longlen,
						FILE_NAME_WIN32_AND_DOS) >= 0) {
					if (!ntfs_delete(vol,
						(const char*)NULL, ni, dir_ni,
						deletename, deletelen))
						res = 0;
					deleted = TRUE;
				} else
					done = TRUE;
			}
		}
		if (!deleted) {
			if (!done && (set_namespace(ni, dir_ni,
					longname, longlen,
					FILE_NAME_WIN32_AND_DOS) >= 0))
				res = 0;
			ntfs_inode_update_times(ni, NTFS_UPDATE_CTIME);
			ntfs_inode_update_times(dir_ni, NTFS_UPDATE_MCTIME);
			if (ntfs_inode_close_in_dir(ni,dir_ni) && !res)
				res = -1;
			if (ntfs_inode_close(dir_ni) && !res)
				res = -1;
		}
	} else {
		if (!ntfs_link_i(ni, dir_ni, shortname, shortlen,
				FILE_NAME_DOS)
			/* make sure a new link was recorded */
		    && (le16_to_cpu(ni->mrec->link_count) > linkcount)) {
			/* delete the existing long name or short name */
// is it ok to not provide the path ?
			if (!ntfs_delete(vol, (char*)NULL, ni, dir_ni,
				 deletename, deletelen)) {
			/* delete closes the inodes, so have to open again */
				dir_ni = ntfs_inode_open(vol, dnum);
				if (dir_ni) {
					ni = ntfs_inode_open(vol, fnum);
					if (ni) {
						if (!ntfs_link_i(ni, dir_ni,
							longname, longlen,
							FILE_NAME_WIN32))
							res = 0;
						if (ntfs_inode_close_in_dir(ni,
							dir_ni)
						    && !res)
							res = -1;
					}
				if (ntfs_inode_close(dir_ni) && !res)
					res = -1;
				}
			}
		} else {
			ntfs_inode_close_in_dir(ni,dir_ni);
			ntfs_inode_close(dir_ni);
		}
	}
	return (res);
}


/*
 *		Set the ntfs DOS name into an extended attribute
 *
 *  The DOS name will be added as another file name attribute
 *  using the existing file name information from the original
 *  name or overwriting the DOS Name if one exists.
 *
 *  	The inode of the file is always closed
 */

int ntfs_set_ntfs_dos_name(ntfs_inode *ni, ntfs_inode *dir_ni,
			const char *value, size_t size,	int flags)
{
	int res = 0;
	int longlen = 0;
	int shortlen = 0;
	char newname[3*MAX_DOS_NAME_LENGTH + 1];
	ntfschar oldname[MAX_DOS_NAME_LENGTH];
	int oldlen;
	u64 dnum;
	BOOL closed = FALSE;
	ntfschar *shortname = NULL;
	ntfschar longname[NTFS_MAX_NAME_LEN];

		/* copy the string to insert a null char, and truncate */
	if (size > 3*MAX_DOS_NAME_LENGTH)
		size = 3*MAX_DOS_NAME_LENGTH;
	strncpy(newname, value, size);
		/* a long name may be truncated badly and be untranslatable */
	newname[size] = 0;
		/* convert the string to the NTFS wide chars, and truncate */
	shortlen = ntfs_mbstoucs(newname, &shortname);
	if (shortlen > MAX_DOS_NAME_LENGTH)
		shortlen = MAX_DOS_NAME_LENGTH;

	/* Make sure the short name has valid chars.
	 * Note: the short name cannot end with dot or space, but the
	 * corresponding long name can. */
	if ((shortlen < 0)
	    || ntfs_forbidden_names(ni->vol,shortname,shortlen,TRUE)) {
		ntfs_inode_close_in_dir(ni,dir_ni);
		ntfs_inode_close(dir_ni);
		res = -errno;
		return res;
	}
	dnum = dir_ni->mft_no;
	longlen = get_long_name(ni, dnum, longname);
	if (longlen > 0) {
		oldlen = get_dos_name(ni, dnum, oldname);
		if ((oldlen >= 0)
		    && !ntfs_forbidden_names(ni->vol, longname, longlen,
					     FALSE)) {
			if (oldlen > 0) {
				if (flags & XATTR_CREATE) {
					res = -1;
					errno = EEXIST;
				} else
					if ((shortlen == oldlen)
					    && !memcmp(shortname,oldname,
						     oldlen*sizeof(ntfschar)))
						/* already set, done */
						res = 0;
					else {
						res = set_dos_name(ni, dir_ni,
							shortname, shortlen,
							longname, longlen,
							oldname, oldlen, TRUE);
						closed = TRUE;
					}
			} else {
				if (flags & XATTR_REPLACE) {
					res = -1;
					errno = ENODATA;
				} else {
					res = set_dos_name(ni, dir_ni,
						shortname, shortlen,
						longname, longlen,
						longname, longlen, FALSE);
					closed = TRUE;
				}
			}
		} else
			res = -1;
	} else {
		res = -1;
		if (!longlen)
			errno = ENOENT;
	}
	free(shortname);
	if (!closed) {
		ntfs_inode_close_in_dir(ni,dir_ni);
		ntfs_inode_close(dir_ni);
	}
	return (res ? -1 : 0);
}

/*
 *		Delete the ntfs DOS name
 */

int ntfs_remove_ntfs_dos_name(ntfs_inode *ni, ntfs_inode *dir_ni)
{
	int res;
	int oldnametype;
	int longlen = 0;
	int shortlen;
	u64 dnum;
	ntfs_volume *vol;
	BOOL deleted = FALSE;
	ntfschar shortname[MAX_DOS_NAME_LENGTH];
	ntfschar longname[NTFS_MAX_NAME_LEN];

	res = -1;
	vol = ni->vol;
	dnum = dir_ni->mft_no;
	longlen = get_long_name(ni, dnum, longname);
	if (longlen > 0) {
		shortlen = get_dos_name(ni, dnum, shortname);
		if (shortlen >= 0) {
				/* migrate the long name as Posix */
			oldnametype = set_namespace(ni,dir_ni,longname,longlen,
					FILE_NAME_POSIX);
			switch (oldnametype) {
			case FILE_NAME_WIN32_AND_DOS :
				/* name was Win32+DOS : done */
				res = 0;
				break;
			case FILE_NAME_DOS :
				/* name was DOS, make it back to DOS */
				set_namespace(ni,dir_ni,longname,longlen,
						FILE_NAME_DOS);
				errno = ENOENT;
				break;
			case FILE_NAME_WIN32 :
				/* name was Win32, make it Posix and delete */
				if (set_namespace(ni,dir_ni,shortname,shortlen,
						FILE_NAME_POSIX) >= 0) {
					if (!ntfs_delete(vol,
							(const char*)NULL, ni,
							dir_ni, shortname,
							shortlen))
						res = 0;
					deleted = TRUE;
				} else {
					/*
					 * DOS name has been found, but cannot
					 * migrate to Posix : something bad
					 * has happened
					 */
					errno = EIO;
					ntfs_log_error("Could not change"
						" DOS name of inode %lld to Posix\n",
						(long long)ni->mft_no);
				}
				break;
			default :
				/* name was Posix or not found : error */
				errno = ENOENT;
				break;
			}
		}
	} else {
		if (!longlen)
			errno = ENOENT;
		res = -1;
	}
	if (!deleted) {
		ntfs_inode_close_in_dir(ni,dir_ni);
		ntfs_inode_close(dir_ni);
	}
	return (res);
}

/*
 *		Increment the count of subdirectories
 *		(excluding entries with a short name)
 */

static int nlink_increment(void *nlink_ptr,
			const ntfschar *name __attribute__((unused)),
			const int len __attribute__((unused)),
			const int type,
			const s64 pos __attribute__((unused)),
			const MFT_REF mref __attribute__((unused)),
			const unsigned int dt_type)
{
	if ((dt_type == NTFS_DT_DIR) && (type != FILE_NAME_DOS))
		(*((int*)nlink_ptr))++;
	return (0);
}

/*
 *		Compute the number of hard links according to Posix
 *	For a directory count the subdirectories whose name is not
 *		a short one, but count "." and ".."
 *	Otherwise count the names, excluding the short ones.
 *
 *	if there is an error, a null count is returned.
 */

int ntfs_dir_link_cnt(ntfs_inode *ni)
{
	ntfs_attr_search_ctx *actx;
	FILE_NAME_ATTR *fn;
	s64 pos;
	int err = 0;
	int nlink = 0;

	if (!ni) {
		ntfs_log_error("Invalid argument.\n");
		errno = EINVAL;
		goto err_out;
	}
	if (ni->nr_extents == -1)
		ni = ni->base_ni;
	if (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) {
		/*
		 * Directory : scan the directory and count
		 * subdirectories whose name is not DOS-only.
		 * The directory names are ignored, but "." and ".."
		 * are taken into account.
		 */
		pos = 0;
		err = ntfs_readdir(ni, &pos, &nlink, nlink_increment);
		if (err)
			nlink = 0;
	} else {
		/*
		 * Non-directory : search for FILE_NAME attributes,
		 * and count those which are not DOS-only ones.
		 */
		actx = ntfs_attr_get_search_ctx(ni, NULL);
		if (!actx)
			goto err_out;
		while (!(err = ntfs_attr_lookup(AT_FILE_NAME, AT_UNNAMED, 0,
					CASE_SENSITIVE, 0, NULL, 0, actx))) {
			fn = (FILE_NAME_ATTR*)((u8*)actx->attr +
					le16_to_cpu(actx->attr->value_offset));
			if (fn->file_name_type != FILE_NAME_DOS)
				nlink++;
		}
		if (err && (errno != ENOENT))
			nlink = 0;
		ntfs_attr_put_search_ctx(actx);
	}
	if (!nlink)
		ntfs_log_perror("Failed to compute nlink of inode %lld",
			(long long)ni->mft_no);
err_out :
	return (nlink);
}
