root/src/add-ons/kernel/file_systems/ntfs/libntfs/dir.c
/**
 * 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

/**
 * 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)
{
        VCN vcn;
        u64 mref = 0;
        s64 br;
        ntfs_volume *vol = dir_ni->vol;
        ntfs_attr_search_ctx *ctx;
        INDEX_ROOT *ir;
        INDEX_ENTRY *ie;
        INDEX_ALLOCATION *ia;
        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 -1;
        }

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

        /* 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 */
                if (ntfs_index_entry_inconsistent(ie, COLLATION_FILE_NAME,
                                dir_ni->mft_no)) {
                        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.
                 */
                mref = le64_to_cpu(ie->indexed_file);
                ntfs_attr_put_search_ctx(ctx);
                return mref;
        }
        /*
         * 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);
                if (mref)
                        return mref;
                ntfs_log_debug("Entry not found - between root entries.\n");
                errno = ENOENT;
                return -1;
        } /* Child node present, descend into it. */

        /* Open the index allocation attribute. */
        ia_na = ntfs_attr_open(dir_ni, AT_INDEX_ALLOCATION, 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((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 */
                if (ntfs_index_entry_inconsistent(ie, COLLATION_FILE_NAME,
                                dir_ni->mft_no)) {
                        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;
                mref = le64_to_cpu(ie->indexed_file);
                free(ia);
                ntfs_attr_close(ia_na);
                ntfs_attr_put_search_ctx(ctx);
                return mref;
        }
        /*
         * 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.
         */
        if (mref)
                return mref;
        ntfs_log_debug("Entry not found.\n");
        errno = ENOENT;
        return -1;
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 -1;
close_err_out:
        eo = errno;
        free(ia);
        ntfs_attr_close(ia_na);
        goto eo_put_err_out;
}

/*
 *              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);
                        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.
 */
#ifndef __HAIKU__
static
#endif
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 */
                if (ntfs_index_entry_inconsistent(ie, COLLATION_FILE_NAME,
                                dir_ni->mft_no)) {
                        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((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 */
                if (ntfs_index_entry_inconsistent(ie, COLLATION_FILE_NAME,
                                dir_ni->mft_no)) {
                        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);
}