root/src/add-ons/kernel/file_systems/ntfs/libntfs/security.c
/**
 * security.c - Handling security/ACLs in NTFS.  Originated from the Linux-NTFS project.
 *
 * Copyright (c) 2004 Anton Altaparmakov
 * Copyright (c) 2005-2006 Szabolcs Szakacsits
 * Copyright (c) 2006 Yura Pakhuchiy
 * Copyright (c) 2007-2015 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_STDIO_H
#include <stdio.h>
#endif
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#ifdef HAVE_STRING_H
#include <string.h>
#endif
#ifdef HAVE_ERRNO_H
#include <errno.h>
#endif
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif
#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif

#include <unistd.h>
#include <pwd.h>
#include <grp.h>

#ifdef __HAIKU__
#define getgrgid(a) NULL
#define getpwuid(a) NULL
#define getgrnam(x) NULL
#define getpwnam(x) NULL
#endif

#include "compat.h"
#include "param.h"
#include "types.h"
#include "layout.h"
#include "attrib.h"
#include "index.h"
#include "dir.h"
#include "bitmap.h"
#include "security.h"
#include "acls.h"
#include "cache.h"
#include "misc.h"
#include "xattrs.h"

/*
 *      JPA NTFS constants or structs
 *      should be moved to layout.h
 */

#define ALIGN_SDS_BLOCK 0x40000 /* Alignment for a $SDS block */
#define ALIGN_SDS_ENTRY 16 /* Alignment for a $SDS entry */
#define STUFFSZ 0x4000 /* unitary stuffing size for $SDS */
#define FIRST_SECURITY_ID 0x100 /* Lowest security id */

        /* Mask for attributes which can be forced */
#define FILE_ATTR_SETTABLE ( FILE_ATTR_READONLY         \
                                | FILE_ATTR_HIDDEN      \
                                | FILE_ATTR_SYSTEM      \
                                | FILE_ATTR_ARCHIVE     \
                                | FILE_ATTR_TEMPORARY   \
                                | FILE_ATTR_OFFLINE     \
                                | FILE_ATTR_NOT_CONTENT_INDEXED )

struct SII {            /* this is an image of an $SII index entry */
        le16 offs;
        le16 size;
        le32 fill1;
        le16 indexsz;
        le16 indexksz;
        le16 flags;
        le16 fill2;
        le32 keysecurid;

        /* did not find official description for the following */
        le32 hash;
        le32 securid;
        le32 dataoffsl; /* documented as badly aligned */
        le32 dataoffsh;
        le32 datasize;
} ;

struct SDH {            /* this is an image of an $SDH index entry */
        le16 offs;
        le16 size;
        le32 fill1;
        le16 indexsz;
        le16 indexksz;
        le16 flags;
        le16 fill2;
        le32 keyhash;
        le32 keysecurid;

        /* did not find official description for the following */
        le32 hash;
        le32 securid;
        le32 dataoffsl;
        le32 dataoffsh;
        le32 datasize;
        le32 fill3;
        } ;

/*
 *      A few useful constants
 */

static ntfschar sii_stream[] = { const_cpu_to_le16('$'),
                                 const_cpu_to_le16('S'),
                                 const_cpu_to_le16('I'),
                                 const_cpu_to_le16('I'),
                                 const_cpu_to_le16(0) };
static ntfschar sdh_stream[] = { const_cpu_to_le16('$'),
                                 const_cpu_to_le16('S'),
                                 const_cpu_to_le16('D'),
                                 const_cpu_to_le16('H'),
                                 const_cpu_to_le16(0) };

/*
 *              null SID (S-1-0-0)
 */

extern const SID *nullsid;

/*
 * The zero GUID.
 */

static const GUID __zero_guid = { const_cpu_to_le32(0), const_cpu_to_le16(0),
                const_cpu_to_le16(0), { 0, 0, 0, 0, 0, 0, 0, 0 } };
static const GUID *const zero_guid = &__zero_guid;

/**
 * ntfs_guid_is_zero - check if a GUID is zero
 * @guid:       [IN] guid to check
 *
 * Return TRUE if @guid is a valid pointer to a GUID and it is the zero GUID
 * and FALSE otherwise.
 */
BOOL ntfs_guid_is_zero(const GUID *guid)
{
        return (memcmp(guid, zero_guid, sizeof(*zero_guid)));
}

/**
 * ntfs_guid_to_mbs - convert a GUID to a multi byte string
 * @guid:       [IN]  guid to convert
 * @guid_str:   [OUT] string in which to return the GUID (optional)
 *
 * Convert the GUID pointed to by @guid to a multi byte string of the form
 * "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX".  Therefore, @guid_str (if not NULL)
 * needs to be able to store at least 37 bytes.
 *
 * If @guid_str is not NULL it will contain the converted GUID on return.  If
 * it is NULL a string will be allocated and this will be returned.  The caller
 * is responsible for free()ing the string in that case.
 *
 * On success return the converted string and on failure return NULL with errno
 * set to the error code.
 */
char *ntfs_guid_to_mbs(const GUID *guid, char *guid_str)
{
        char *_guid_str;
        int res;

        if (!guid) {
                errno = EINVAL;
                return NULL;
        }
        _guid_str = guid_str;
        if (!_guid_str) {
                _guid_str = (char*)ntfs_malloc(37);
                if (!_guid_str)
                        return _guid_str;
        }
        res = snprintf(_guid_str, 37,
                        "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",
                        (unsigned int)le32_to_cpu(guid->data1),
                        le16_to_cpu(guid->data2), le16_to_cpu(guid->data3),
                        guid->data4[0], guid->data4[1],
                        guid->data4[2], guid->data4[3], guid->data4[4],
                        guid->data4[5], guid->data4[6], guid->data4[7]);
        if (res == 36)
                return _guid_str;
        if (!guid_str)
                free(_guid_str);
        errno = EINVAL;
        return NULL;
}

/**
 * ntfs_sid_to_mbs_size - determine maximum size for the string of a SID
 * @sid:        [IN]  SID for which to determine the maximum string size
 *
 * Determine the maximum multi byte string size in bytes which is needed to
 * store the standard textual representation of the SID pointed to by @sid.
 * See ntfs_sid_to_mbs(), below.
 *
 * On success return the maximum number of bytes needed to store the multi byte
 * string and on failure return -1 with errno set to the error code.
 */
int ntfs_sid_to_mbs_size(const SID *sid)
{
        int size, i;

        if (!ntfs_valid_sid(sid)) {
                errno = EINVAL;
                return -1;
        }
        /* Start with "S-". */
        size = 2;
        /*
         * Add the SID_REVISION.  Hopefully the compiler will optimize this
         * away as SID_REVISION is a constant.
         */
        for (i = SID_REVISION; i > 0; i /= 10)
                size++;
        /* Add the "-". */
        size++;
        /*
         * Add the identifier authority.  If it needs to be in decimal, the
         * maximum is 2^32-1 = 4294967295 = 10 characters.  If it needs to be
         * in hexadecimal, then maximum is 0x665544332211 = 14 characters.
         */
        if (!sid->identifier_authority.high_part)
                size += 10;
        else
                size += 14;
        /*
         * Finally, add the sub authorities.  For each we have a "-" followed
         * by a decimal which can be up to 2^32-1 = 4294967295 = 10 characters.
         */
        size += (1 + 10) * sid->sub_authority_count;
        /* We need the zero byte at the end, too. */
        size++;
        return size * sizeof(char);
}

/**
 * ntfs_sid_to_mbs - convert a SID to a multi byte string
 * @sid:                [IN]  SID to convert
 * @sid_str:            [OUT] string in which to return the SID (optional)
 * @sid_str_size:       [IN]  size in bytes of @sid_str
 *
 * Convert the SID pointed to by @sid to its standard textual representation.
 * @sid_str (if not NULL) needs to be able to store at least
 * ntfs_sid_to_mbs_size() bytes.  @sid_str_size is the size in bytes of
 * @sid_str if @sid_str is not NULL.
 *
 * The standard textual representation of the SID is of the form:
 *      S-R-I-S-S...
 * Where:
 *    - The first "S" is the literal character 'S' identifying the following
 *      digits as a SID.
 *    - R is the revision level of the SID expressed as a sequence of digits
 *      in decimal.
 *    - I is the 48-bit identifier_authority, expressed as digits in decimal,
 *      if I < 2^32, or hexadecimal prefixed by "0x", if I >= 2^32.
 *    - S... is one or more sub_authority values, expressed as digits in
 *      decimal.
 *
 * If @sid_str is not NULL it will contain the converted SUID on return.  If it
 * is NULL a string will be allocated and this will be returned.  The caller is
 * responsible for free()ing the string in that case.
 *
 * On success return the converted string and on failure return NULL with errno
 * set to the error code.
 */
char *ntfs_sid_to_mbs(const SID *sid, char *sid_str, size_t sid_str_size)
{
        u64 u;
        le32 leauth;
        char *s;
        int i, j, cnt;

        /*
         * No need to check @sid if !@sid_str since ntfs_sid_to_mbs_size() will
         * check @sid, too.  8 is the minimum SID string size.
         */
        if (sid_str && (sid_str_size < 8 || !ntfs_valid_sid(sid))) {
                errno = EINVAL;
                return NULL;
        }
        /* Allocate string if not provided. */
        if (!sid_str) {
                cnt = ntfs_sid_to_mbs_size(sid);
                if (cnt < 0)
                        return NULL;
                s = (char*)ntfs_malloc(cnt);
                if (!s)
                        return s;
                sid_str = s;
                /* So we know we allocated it. */
                sid_str_size = 0;
        } else {
                s = sid_str;
                cnt = sid_str_size;
        }
        /* Start with "S-R-". */
        i = snprintf(s, cnt, "S-%hhu-", (unsigned char)sid->revision);
        if (i < 0 || i >= cnt)
                goto err_out;
        s += i;
        cnt -= i;
        /* Add the identifier authority. */
        for (u = i = 0, j = 40; i < 6; i++, j -= 8)
                u += (u64)sid->identifier_authority.value[i] << j;
        if (!sid->identifier_authority.high_part)
                i = snprintf(s, cnt, "%lu", (unsigned long)u);
        else
                i = snprintf(s, cnt, "0x%llx", (unsigned long long)u);
        if (i < 0 || i >= cnt)
                goto err_out;
        s += i;
        cnt -= i;
        /* Finally, add the sub authorities. */
        for (j = 0; j < sid->sub_authority_count; j++) {
                leauth = sid->sub_authority[j];
                i = snprintf(s, cnt, "-%u", (unsigned int)
                                le32_to_cpu(leauth));
                if (i < 0 || i >= cnt)
                        goto err_out;
                s += i;
                cnt -= i;
        }
        return sid_str;
err_out:
        if (i >= cnt)
                i = EMSGSIZE;
        else
                i = errno;
        if (!sid_str_size)
                free(sid_str);
        errno = i;
        return NULL;
}

/**
 * ntfs_generate_guid - generatates a random current guid.
 * @guid:       [OUT]   pointer to a GUID struct to hold the generated guid.
 *
 * perhaps not a very good random number generator though...
 */
void ntfs_generate_guid(GUID *guid)
{
        unsigned int i;
        u8 *p = (u8 *)guid;

        /* this is called at most once from mkntfs */
        srandom(time((time_t*)NULL) ^ (getpid() << 16));
        for (i = 0; i < sizeof(GUID); i++) {
                p[i] = (u8)(random() & 0xFF);
                if (i == 7)
                        p[7] = (p[7] & 0x0F) | 0x40;
                if (i == 8)
                        p[8] = (p[8] & 0x3F) | 0x80;
        }
}

/**
 * ntfs_security_hash - calculate the hash of a security descriptor
 * @sd:         self-relative security descriptor whose hash to calculate
 * @length:     size in bytes of the security descritor @sd
 *
 * Calculate the hash of the self-relative security descriptor @sd of length
 * @length bytes.
 *
 * This hash is used in the $Secure system file as the primary key for the $SDH
 * index and is also stored in the header of each security descriptor in the
 * $SDS data stream as well as in the index data of both the $SII and $SDH
 * indexes.  In all three cases it forms part of the SDS_ENTRY_HEADER
 * structure.
 *
 * Return the calculated security hash in little endian.
 */
le32 ntfs_security_hash(const SECURITY_DESCRIPTOR_RELATIVE *sd, const u32 len)
{
        const le32 *pos = (const le32*)sd;
        const le32 *end = pos + (len >> 2);
        u32 hash = 0;

        while (pos < end) {
                hash = le32_to_cpup(pos) + ntfs_rol32(hash, 3);
                pos++;
        }
        return cpu_to_le32(hash);
}

/*
 *      Get the first entry of current index block
 *      cut and pasted form ntfs_ie_get_first() in index.c
 */

static INDEX_ENTRY *ntfs_ie_get_first(INDEX_HEADER *ih)
{
        return (INDEX_ENTRY*)((u8*)ih + le32_to_cpu(ih->entries_offset));
}

/*
 *              Stuff a 256KB block into $SDS before writing descriptors
 *      into the block.
 *
 *      This prevents $SDS from being automatically declared as sparse
 *      when the second copy of the first security descriptor is written
 *      256KB further ahead.
 *
 *      Having $SDS declared as a sparse file is not wrong by itself
 *      and chkdsk leaves it as a sparse file. It does however complain
 *      and add a sparse flag (0x0200) into field file_attributes of
 *      STANDARD_INFORMATION of $Secure. This probably means that a
 *      sparse attribute (ATTR_IS_SPARSE) is only allowed in sparse
 *      files (FILE_ATTR_SPARSE_FILE).
 *
 *      Windows normally does not convert to sparse attribute or sparse
 *      file. Stuffing is just a way to get to the same result.
 */

static int entersecurity_stuff(ntfs_volume *vol, off_t offs)
{
        int res;
        int written;
        unsigned long total;
        char *stuff;

        res = 0;
        total = 0;
        stuff = (char*)ntfs_malloc(STUFFSZ);
        if (stuff) {
                memset(stuff, 0, STUFFSZ);
                do {
                        written = ntfs_attr_data_write(vol->secure_ni,
                                STREAM_SDS, 4, stuff, STUFFSZ, offs);
                        if (written == STUFFSZ) {
                                total += STUFFSZ;
                                offs += STUFFSZ;
                        } else {
                                errno = ENOSPC;
                                res = -1;
                        }
                } while (!res && (total < ALIGN_SDS_BLOCK));
                free(stuff);
        } else {
                errno = ENOMEM;
                res = -1;
        }
        return (res);
}

/*
 *              Enter a new security descriptor into $Secure (data only)
 *      it has to be written twice with an offset of 256KB
 *
 *      Should only be called by entersecurityattr() to ensure consistency
 *
 *      Returns zero if sucessful
 */

static int entersecurity_data(ntfs_volume *vol,
                        const SECURITY_DESCRIPTOR_RELATIVE *attr, s64 attrsz,
                        le32 hash, le32 keyid, off_t offs, int gap)
{
        int res;
        int written1;
        int written2;
        char *fullattr;
        int fullsz;
        SECURITY_DESCRIPTOR_HEADER *phsds;

        res = -1;
        fullsz = attrsz + gap + sizeof(SECURITY_DESCRIPTOR_HEADER);
        fullattr = (char*)ntfs_malloc(fullsz);
        if (fullattr) {
                        /*
                         * Clear the gap from previous descriptor
                         * this could be useful for appending the second
                         * copy to the end of file. When creating a new
                         * 256K block, the gap is cleared while writing
                         * the first copy
                         */
                if (gap)
                        memset(fullattr,0,gap);
                memcpy(&fullattr[gap + sizeof(SECURITY_DESCRIPTOR_HEADER)],
                                attr,attrsz);
                phsds = (SECURITY_DESCRIPTOR_HEADER*)&fullattr[gap];
                phsds->hash = hash;
                phsds->security_id = keyid;
                phsds->offset = cpu_to_le64(offs);
                phsds->length = cpu_to_le32(fullsz - gap);
                written1 = ntfs_attr_data_write(vol->secure_ni,
                        STREAM_SDS, 4, fullattr, fullsz,
                        offs - gap);
                written2 = ntfs_attr_data_write(vol->secure_ni,
                        STREAM_SDS, 4, fullattr, fullsz,
                        offs - gap + ALIGN_SDS_BLOCK);
                if ((written1 == fullsz)
                     && (written2 == written1)) {
                        /*
                         * Make sure the data size for $SDS marks the end
                         * of the last security attribute. Windows uses
                         * this to determine where the next attribute will
                         * be written, which causes issues if chkdsk had
                         * previously deleted the last entries without
                         * adjusting the size.
                         */
                        res = ntfs_attr_shrink_size(vol->secure_ni,STREAM_SDS,
                                4, offs - gap + ALIGN_SDS_BLOCK + fullsz);
                } else
                        errno = ENOSPC;
                free(fullattr);
        } else
                errno = ENOMEM;
        return (res);
}

/*
 *      Enter a new security descriptor in $Secure (indexes only)
 *
 *      Should only be called by entersecurityattr() to ensure consistency
 *
 *      Returns zero if sucessful
 */

static int entersecurity_indexes(ntfs_volume *vol, s64 attrsz,
                        le32 hash, le32 keyid, off_t offs)
{
        union {
                struct {
                        le32 dataoffsl;
                        le32 dataoffsh;
                } parts;
                le64 all;
        } realign;
        int res;
        ntfs_index_context *xsii;
        ntfs_index_context *xsdh;
        struct SII newsii;
        struct SDH newsdh;

        res = -1;
                                /* enter a new $SII record */

        xsii = vol->secure_xsii;
        ntfs_index_ctx_reinit(xsii);
        newsii.offs = const_cpu_to_le16(20);
        newsii.size = const_cpu_to_le16(sizeof(struct SII) - 20);
        newsii.fill1 = const_cpu_to_le32(0);
        newsii.indexsz = const_cpu_to_le16(sizeof(struct SII));
        newsii.indexksz = const_cpu_to_le16(sizeof(SII_INDEX_KEY));
        newsii.flags = const_cpu_to_le16(0);
        newsii.fill2 = const_cpu_to_le16(0);
        newsii.keysecurid = keyid;
        newsii.hash = hash;
        newsii.securid = keyid;
        realign.all = cpu_to_le64(offs);
        newsii.dataoffsh = realign.parts.dataoffsh;
        newsii.dataoffsl = realign.parts.dataoffsl;
        newsii.datasize = cpu_to_le32(attrsz
                         + sizeof(SECURITY_DESCRIPTOR_HEADER));
        if (!ntfs_ie_add(xsii,(INDEX_ENTRY*)&newsii)) {

                /* enter a new $SDH record */

                xsdh = vol->secure_xsdh;
                ntfs_index_ctx_reinit(xsdh);
                newsdh.offs = const_cpu_to_le16(24);
                newsdh.size = const_cpu_to_le16(
                        sizeof(SECURITY_DESCRIPTOR_HEADER));
                newsdh.fill1 = const_cpu_to_le32(0);
                newsdh.indexsz = const_cpu_to_le16(
                                sizeof(struct SDH));
                newsdh.indexksz = const_cpu_to_le16(
                                sizeof(SDH_INDEX_KEY));
                newsdh.flags = const_cpu_to_le16(0);
                newsdh.fill2 = const_cpu_to_le16(0);
                newsdh.keyhash = hash;
                newsdh.keysecurid = keyid;
                newsdh.hash = hash;
                newsdh.securid = keyid;
                newsdh.dataoffsh = realign.parts.dataoffsh;
                newsdh.dataoffsl = realign.parts.dataoffsl;
                newsdh.datasize = cpu_to_le32(attrsz
                         + sizeof(SECURITY_DESCRIPTOR_HEADER));
                           /* special filler value, Windows generally */
                           /* fills with 0x00490049, sometimes with zero */
                newsdh.fill3 = const_cpu_to_le32(0x00490049);
                if (!ntfs_ie_add(xsdh,(INDEX_ENTRY*)&newsdh))
                        res = 0;
        }
        return (res);
}

/*
 *      Enter a new security descriptor in $Secure (data and indexes)
 *      Returns id of entry, or zero if there is a problem.
 *      (should not be called for NTFS version < 3.0)
 *
 *      important : calls have to be serialized, however no locking is
 *      needed while fuse is not multithreaded
 */

static le32 entersecurityattr(ntfs_volume *vol,
                        const SECURITY_DESCRIPTOR_RELATIVE *attr, s64 attrsz,
                        le32 hash)
{
        union {
                struct {
                        le32 dataoffsl;
                        le32 dataoffsh;
                } parts;
                le64 all;
        } realign;
        le32 securid;
        le32 keyid;
        u32 newkey;
        off_t offs;
        int gap;
        int size;
        BOOL found;
        struct SII *psii;
        INDEX_ENTRY *entry;
        INDEX_ENTRY *next;
        ntfs_index_context *xsii;
        int retries;
        ntfs_attr *na;
        int olderrno;

        /* find the first available securid beyond the last key */
        /* in $Secure:$SII. This also determines the first */
        /* available location in $Secure:$SDS, as this stream */
        /* is always appended to and the id's are allocated */
        /* in sequence */

        securid = const_cpu_to_le32(0);
        xsii = vol->secure_xsii;
        ntfs_index_ctx_reinit(xsii);
        offs = size = 0;
        keyid = const_cpu_to_le32(-1);
        olderrno = errno;
        found = !ntfs_index_lookup((char*)&keyid,
                               sizeof(SII_INDEX_KEY), xsii);
        if (!found && (errno != ENOENT)) {
                ntfs_log_perror("Inconsistency in index $SII");
                psii = (struct SII*)NULL;
        } else {
                        /* restore errno to avoid misinterpretation */
                errno = olderrno;
                entry = xsii->entry;
                psii = (struct SII*)xsii->entry;
        }
        if (psii) {
                /*
                 * Get last entry in block, but must get first one
                 * one first, as we should already be beyond the
                 * last one. For some reason the search for the last
                 * entry sometimes does not return the last block...
                 * we assume this can only happen in root block
                 */
                if (xsii->is_in_root)
                        entry = ntfs_ie_get_first
                                ((INDEX_HEADER*)&xsii->ir->index);
                else
                        entry = ntfs_ie_get_first
                                ((INDEX_HEADER*)&xsii->ib->index);
                /*
                 * All index blocks should be at least half full
                 * so there always is a last entry but one,
                 * except when creating the first entry in index root.
                 * This was however found not to be true : chkdsk
                 * sometimes deletes all the (unused) keys in the last
                 * index block without rebalancing the tree.
                 * When this happens, a new search is restarted from
                 * the smallest key.
                 */
                keyid = const_cpu_to_le32(0);
                retries = 0;
                while (entry) {
                        next = ntfs_index_next(entry,xsii);
                        if (next) {
                                psii = (struct SII*)next;
                                        /* save last key and */
                                        /* available position */
                                keyid = psii->keysecurid;
                                realign.parts.dataoffsh
                                                 = psii->dataoffsh;
                                realign.parts.dataoffsl
                                                 = psii->dataoffsl;
                                offs = le64_to_cpu(realign.all);
                                size = le32_to_cpu(psii->datasize);
                        }
                        entry = next;
                        if (!entry && !keyid && !retries) {
                                /* search failed, retry from smallest key */
                                ntfs_index_ctx_reinit(xsii);
                                found = !ntfs_index_lookup((char*)&keyid,
                                               sizeof(SII_INDEX_KEY), xsii);
                                if (!found && (errno != ENOENT)) {
                                        ntfs_log_perror("Index $SII is broken");
                                        psii = (struct SII*)NULL;
                                } else {
                                                /* restore errno */
                                        errno = olderrno;
                                        entry = xsii->entry;
                                        psii = (struct SII*)entry;
                                }
                                if (psii
                                    && !(psii->flags & INDEX_ENTRY_END)) {
                                                /* save first key and */
                                                /* available position */
                                        keyid = psii->keysecurid;
                                        realign.parts.dataoffsh
                                                         = psii->dataoffsh;
                                        realign.parts.dataoffsl
                                                         = psii->dataoffsl;
                                        offs = le64_to_cpu(realign.all);
                                        size = le32_to_cpu(psii->datasize);
                                }
                                retries++;
                        }
                }
        }
        if (!keyid) {
                /*
                 * could not find any entry, before creating the first
                 * entry, make a double check by making sure size of $SII
                 * is less than needed for one entry
                 */
                securid = const_cpu_to_le32(0);
                na = ntfs_attr_open(vol->secure_ni,AT_INDEX_ROOT,sii_stream,4);
                if (na) {
                        if ((size_t)na->data_size < (sizeof(struct SII)
                                        + sizeof(INDEX_ENTRY_HEADER))) {
                                ntfs_log_error("Creating the first security_id\n");
                                securid = const_cpu_to_le32(FIRST_SECURITY_ID);
                        }
                        ntfs_attr_close(na);
                }
                if (!securid) {
                        ntfs_log_error("Error creating a security_id\n");
                        errno = EIO;
                }
        } else {
                newkey = le32_to_cpu(keyid) + 1;
                securid = cpu_to_le32(newkey);
        }
        /*
         * The security attr has to be written twice 256KB
         * apart. This implies that offsets like
         * 0x40000*odd_integer must be left available for
         * the second copy. So align to next block when
         * the last byte overflows on a wrong block.
         */

        if (securid) {
                gap = (-size) & (ALIGN_SDS_ENTRY - 1);
                offs += gap + size;
                if ((offs + attrsz + sizeof(SECURITY_DESCRIPTOR_HEADER) - 1)
                   & ALIGN_SDS_BLOCK) {
                        offs = ((offs + attrsz
                                 + sizeof(SECURITY_DESCRIPTOR_HEADER) - 1)
                                | (ALIGN_SDS_BLOCK - 1)) + 1;
                }
                if (!(offs & (ALIGN_SDS_BLOCK - 1)))
                        entersecurity_stuff(vol, offs);
                /*
                 * now write the security attr to storage :
                 * first data, then SII, then SDH
                 * If failure occurs while writing SDS, data will never
                 *    be accessed through indexes, and will be overwritten
                 *    by the next allocated descriptor
                 * If failure occurs while writing SII, the id has not
                 *    recorded and will be reallocated later
                 * If failure occurs while writing SDH, the space allocated
                 *    in SDS or SII will not be reused, an inconsistency
                 *    will persist with no significant consequence
                 */
                if (entersecurity_data(vol, attr, attrsz, hash, securid, offs, gap)
                    || entersecurity_indexes(vol, attrsz, hash, securid, offs))
                        securid = const_cpu_to_le32(0);
        }
                /* inode now is dirty, synchronize it all */
        ntfs_index_entry_mark_dirty(vol->secure_xsii);
        ntfs_index_ctx_reinit(vol->secure_xsii);
        ntfs_index_entry_mark_dirty(vol->secure_xsdh);
        ntfs_index_ctx_reinit(vol->secure_xsdh);
        NInoSetDirty(vol->secure_ni);
        if (ntfs_inode_sync(vol->secure_ni))
                ntfs_log_perror("Could not sync $Secure\n");
        return (securid);
}

/*
 *              Find a matching security descriptor in $Secure,
 *      if none, allocate a new id and write the descriptor to storage
 *      Returns id of entry, or zero if there is a problem.
 *
 *      important : calls have to be serialized, however no locking is
 *      needed while fuse is not multithreaded
 */

static le32 setsecurityattr(ntfs_volume *vol,
                        const SECURITY_DESCRIPTOR_RELATIVE *attr, s64 attrsz)
{
        struct SDH *psdh;       /* this is an image of index (le) */
        union {
                struct {
                        le32 dataoffsl;
                        le32 dataoffsh;
                } parts;
                le64 all;
        } realign;
        BOOL found;
        BOOL collision;
        size_t size;
        size_t rdsize;
        s64 offs;
        int res;
        ntfs_index_context *xsdh;
        char *oldattr;
        SDH_INDEX_KEY key;
        INDEX_ENTRY *entry;
        le32 securid;
        le32 hash;
        int olderrno;

        hash = ntfs_security_hash(attr,attrsz);
        oldattr = (char*)NULL;
        securid = const_cpu_to_le32(0);
        res = 0;
        xsdh = vol->secure_xsdh;
        if (vol->secure_ni && xsdh && !vol->secure_reentry++) {
                ntfs_index_ctx_reinit(xsdh);
                /*
                 * find the nearest key as (hash,0)
                 * (do not search for partial key : in case of collision,
                 * it could return a key which is not the first one which
                 * collides)
                 */
                key.hash = hash;
                key.security_id = const_cpu_to_le32(0);
                olderrno = errno;
                found = !ntfs_index_lookup((char*)&key,
                                 sizeof(SDH_INDEX_KEY), xsdh);
                if (!found && (errno != ENOENT))
                        ntfs_log_perror("Inconsistency in index $SDH");
                else {
                                /* restore errno to avoid misinterpretation */
                        errno = olderrno;
                        entry = xsdh->entry;
                        found = FALSE;
                        /*
                         * lookup() may return a node with no data,
                         * if so get next
                         */
                        if (entry->ie_flags & INDEX_ENTRY_END)
                                entry = ntfs_index_next(entry,xsdh);
                        do {
                                collision = FALSE;
                                psdh = (struct SDH*)entry;
                                if (psdh)
                                        size = (size_t) le32_to_cpu(psdh->datasize)
                                                 - sizeof(SECURITY_DESCRIPTOR_HEADER);
                                else size = 0;
                           /* if hash is not the same, the key is not present */
                                if (psdh && (size > 0)
                                   && (psdh->keyhash == hash)) {
                                           /* if hash is the same */
                                           /* check the whole record */
                                        realign.parts.dataoffsh = psdh->dataoffsh;
                                        realign.parts.dataoffsl = psdh->dataoffsl;
                                        offs = le64_to_cpu(realign.all)
                                                + sizeof(SECURITY_DESCRIPTOR_HEADER);
                                        oldattr = (char*)ntfs_malloc(size);
                                        if (oldattr) {
                                                rdsize = ntfs_attr_data_read(
                                                        vol->secure_ni,
                                                        STREAM_SDS, 4,
                                                        oldattr, size, offs);
                                                found = (rdsize == size)
                                                        && !memcmp(oldattr,attr,size);
                                                free(oldattr);
                                          /* if the records do not compare */
                                          /* (hash collision), try next one */
                                                if (!found) {
                                                        entry = ntfs_index_next(
                                                                entry,xsdh);
                                                        collision = TRUE;
                                                }
                                        } else
                                                res = ENOMEM;
                                }
                        } while (collision && entry);
                        if (found)
                                securid = psdh->keysecurid;
                        else {
                                if (res) {
                                        errno = res;
                                        securid = const_cpu_to_le32(0);
                                } else {
                                        /*
                                         * no matching key :
                                         * have to build a new one
                                         */
                                        securid = entersecurityattr(vol,
                                                attr, attrsz, hash);
                                }
                        }
                }
        }
        if (--vol->secure_reentry)
                ntfs_log_perror("Reentry error, check no multithreading\n");
        return (securid);
}


/*
 *              Update the security descriptor of a file
 *      Either as an attribute (complying with pre v3.x NTFS version)
 *      or, when possible, as an entry in $Secure (for NTFS v3.x)
 *
 *      returns 0 if success
 */

static int update_secur_descr(ntfs_volume *vol,
                                char *newattr, ntfs_inode *ni)
{
        int newattrsz;
        int written;
        int res;
        ntfs_attr *na;

        newattrsz = ntfs_attr_size(newattr);

#if !FORCE_FORMAT_v1x
        if ((vol->major_ver < 3) || !vol->secure_ni) {
#endif

                /* update for NTFS format v1.x */

                /* update the old security attribute */
                na = ntfs_attr_open(ni, AT_SECURITY_DESCRIPTOR, AT_UNNAMED, 0);
                if (na) {
                        /* resize attribute */
                        res = ntfs_attr_truncate(na, (s64) newattrsz);
                        /* overwrite value */
                        if (!res) {
                                written = (int)ntfs_attr_pwrite(na, (s64) 0,
                                         (s64) newattrsz, newattr);
                                if (written != newattrsz) {
                                        ntfs_log_error("Failed to update "
                                                "a v1.x security descriptor\n");
                                        errno = EIO;
                                        res = -1;
                                }
                        }

                        ntfs_attr_close(na);
                        /* if old security attribute was found, also */
                        /* truncate standard information attribute to v1.x */
                        /* this is needed when security data is wanted */
                        /* as v1.x though volume is formatted for v3.x */
                        na = ntfs_attr_open(ni, AT_STANDARD_INFORMATION,
                                AT_UNNAMED, 0);
                        if (na) {
                                clear_nino_flag(ni, v3_Extensions);
                        /*
                         * Truncating the record does not sweep extensions
                         * from copy in memory. Clear security_id to be safe
                         */
                                ni->security_id = const_cpu_to_le32(0);
                                res = ntfs_attr_truncate(na, (s64)48);
                                ntfs_attr_close(na);
                                clear_nino_flag(ni, v3_Extensions);
                        }
                } else {
                        /*
                         * insert the new security attribute if there
                         * were none
                         */
                        res = ntfs_attr_add(ni, AT_SECURITY_DESCRIPTOR,
                                            AT_UNNAMED, 0, (u8*)newattr,
                                            (s64) newattrsz);
                }
#if !FORCE_FORMAT_v1x
        } else {

                /* update for NTFS format v3.x */

                le32 securid;

                securid = setsecurityattr(vol,
                        (const SECURITY_DESCRIPTOR_RELATIVE*)newattr,
                        (s64)newattrsz);
                if (securid) {
                        na = ntfs_attr_open(ni, AT_STANDARD_INFORMATION,
                                AT_UNNAMED, 0);
                        if (na) {
                                res = 0;
                                if (!test_nino_flag(ni, v3_Extensions)) {
                        /* expand standard information attribute to v3.x */
                                        res = ntfs_attr_truncate(na,
                                         (s64)sizeof(STANDARD_INFORMATION));
                                        ni->owner_id = const_cpu_to_le32(0);
                                        ni->quota_charged = const_cpu_to_le64(0);
                                        ni->usn = const_cpu_to_le64(0);
                                        ntfs_attr_remove(ni,
                                                AT_SECURITY_DESCRIPTOR,
                                                AT_UNNAMED, 0);
                        }
                                set_nino_flag(ni, v3_Extensions);
                                ni->security_id = securid;
                                ntfs_attr_close(na);
                        } else {
                                ntfs_log_error("Failed to update "
                                        "standard informations\n");
                                errno = EIO;
                                res = -1;
                        }
                } else
                        res = -1;
        }
#endif

        /* mark node as dirty */
        NInoSetDirty(ni);
        return (res);
}

/*
 *              Upgrade the security descriptor of a file
 *      This is intended to allow graceful upgrades for files which
 *      were created in previous versions, with a security attributes
 *      and no security id.
 *
 *      It will allocate a security id and replace the individual
 *      security attribute by a reference to the global one
 *
 *      Special files are not upgraded (currently / and files in
 *      directories /$*)
 *
 *      Though most code is similar to update_secur_desc() it has
 *      been kept apart to facilitate the further processing of
 *      special cases or even to remove it if found dangerous.
 *
 *      returns 0 if success,
 *              1 if not upgradable. This is not an error.
 *              -1 if there is a problem
 */

static int upgrade_secur_desc(ntfs_volume *vol,
                                const char *attr, ntfs_inode *ni)
{
        int attrsz;
        int res;
        le32 securid;
        ntfs_attr *na;

                /*
                 * upgrade requires NTFS format v3.x
                 * also refuse upgrading for special files
                 * whose number is less than FILE_first_user
                 */

        if ((vol->major_ver >= 3)
            && (ni->mft_no >= FILE_first_user)) {
                attrsz = ntfs_attr_size(attr);
                securid = setsecurityattr(vol,
                        (const SECURITY_DESCRIPTOR_RELATIVE*)attr,
                        (s64)attrsz);
                if (securid) {
                        na = ntfs_attr_open(ni, AT_STANDARD_INFORMATION,
                                AT_UNNAMED, 0);
                        if (na) {
                        /* expand standard information attribute to v3.x */
                                res = ntfs_attr_truncate(na,
                                         (s64)sizeof(STANDARD_INFORMATION));
                                ni->owner_id = const_cpu_to_le32(0);
                                ni->quota_charged = const_cpu_to_le64(0);
                                ni->usn = const_cpu_to_le64(0);
                                ntfs_attr_remove(ni, AT_SECURITY_DESCRIPTOR,
                                                AT_UNNAMED, 0);
                                set_nino_flag(ni, v3_Extensions);
                                ni->security_id = securid;
                                ntfs_attr_close(na);
                        } else {
                                ntfs_log_error("Failed to upgrade "
                                        "standard informations\n");
                                errno = EIO;
                                res = -1;
                        }
                } else
                        res = -1;
                        /* mark node as dirty */
                NInoSetDirty(ni);
        } else
                res = 1;

        return (res);
}

/*
 *              Optional simplified checking of group membership
 *
 *      This only takes into account the groups defined in
 *      /etc/group at initialization time.
 *      It does not take into account the groups dynamically set by
 *      setgroups() nor the changes in /etc/group since initialization
 *
 *      This optional method could be useful if standard checking
 *      leads to a performance concern.
 *
 *      Should not be called for user root, however the group may be root
 *
 */

static BOOL staticgroupmember(struct SECURITY_CONTEXT *scx, uid_t uid, gid_t gid)
{
        BOOL ingroup;
        int grcnt;
        gid_t *groups;
        struct MAPPING *user;

        ingroup = FALSE;
        if (uid) {
                user = scx->mapping[MAPUSERS];
                while (user && ((uid_t)user->xid != uid))
                        user = user->next;
                if (user) {
                        groups = user->groups;
                        grcnt = user->grcnt;
                        while ((--grcnt >= 0) && (groups[grcnt] != gid)) { }
                        ingroup = (grcnt >= 0);
                }
        }
        return (ingroup);
}

#if defined(__sun) && defined (__SVR4)

/*
 *              Check whether current thread owner is member of file group
 *                              Solaris/OpenIndiana version
 *      Should not be called for user root, however the group may be root
 *
 * The group list is available in "/proc/$PID/cred"
 *
 */

static BOOL groupmember(struct SECURITY_CONTEXT *scx, uid_t uid, gid_t gid)
{
        typedef struct prcred {
                uid_t pr_euid;      /* effective user id */
                uid_t pr_ruid;      /* real user id */
                uid_t pr_suid;      /* saved user id (from exec) */
                gid_t pr_egid;      /* effective group id */
                gid_t pr_rgid;      /* real group id */
                gid_t pr_sgid;      /* saved group id (from exec) */
                int pr_ngroups;     /* number of supplementary groups */
                gid_t pr_groups[1]; /* array of supplementary groups */
        } prcred_t;
        enum { readset = 16 };

        prcred_t basecreds;
        gid_t groups[readset];
        char filename[64];
        int fd;
        int k;
        int cnt;
        gid_t *p;
        BOOL ismember;
        int got;
        pid_t tid;

        if (scx->vol->secure_flags & (1 << SECURITY_STATICGRPS))
                ismember = staticgroupmember(scx, uid, gid);
        else {
                ismember = FALSE; /* default return */
                tid = scx->tid;
                sprintf(filename,"/proc/%u/cred",tid);
                fd = open(filename,O_RDONLY);
                if (fd >= 0) {
                        got = read(fd, &basecreds, sizeof(prcred_t));
                        if (got == sizeof(prcred_t)) {
                                if (basecreds.pr_egid == gid)
                                        ismember = TRUE;
                                p = basecreds.pr_groups;
                                cnt = 1;
                                k = 0;
                                while (!ismember
                                    && (k < basecreds.pr_ngroups)
                                    && (cnt > 0)
                                    && (*p != gid)) {
                                        k++;
                                        cnt--;
                                        p++;
                                        if (cnt <= 0) {
                                                got = read(fd, groups,
                                                        readset*sizeof(gid_t));
                                                cnt = got/sizeof(gid_t);
                                                p = groups;
                                        }
                                }
                                if ((cnt > 0)
                                    && (k < basecreds.pr_ngroups))
                                        ismember = TRUE;
                        }
                close(fd);
                }
        }
        return (ismember);
}

#elif defined(__HAIKU__)

static BOOL groupmember(struct SECURITY_CONTEXT *scx, uid_t uid, gid_t gid)
{
        return TRUE;
}

#else /* defined(__sun) && defined (__SVR4) */

/*
 *              Check whether current thread owner is member of file group
 *                              Linux version
 *      Should not be called for user root, however the group may be root
 *
 * As indicated by Miklos Szeredi :
 *
 * The group list is available in
 *
 *   /proc/$PID/task/$TID/status
 *
 * and fuse supplies TID in get_fuse_context()->pid.  The only problem is
 * finding out PID, for which I have no good solution, except to iterate
 * through all processes.  This is rather slow, but may be speeded up
 * with caching and heuristics (for single threaded programs PID = TID).
 *
 * The following implementation gets the group list from
 *   /proc/$TID/task/$TID/status which apparently exists and
 * contains the same data.
 */

static BOOL groupmember(struct SECURITY_CONTEXT *scx, uid_t uid, gid_t gid)
{
        static char key[] = "\nGroups:";
        char buf[BUFSZ+1];
        char filename[64];
        enum { INKEY, INSEP, INNUM, INEND } state;
        int fd;
        char c;
        int matched;
        BOOL ismember;
        int got;
        char *p;
        gid_t grp;
        pid_t tid;

        if (scx->vol->secure_flags & (1 << SECURITY_STATICGRPS))
                ismember = staticgroupmember(scx, uid, gid);
        else {
                ismember = FALSE; /* default return */
                tid = scx->tid;
                sprintf(filename,"/proc/%u/task/%u/status",tid,tid);
                fd = open(filename,O_RDONLY);
                if (fd >= 0) {
                        got = read(fd, buf, BUFSZ);
                        buf[got] = 0;
                        state = INKEY;
                        matched = 0;
                        p = buf;
                        grp = 0;
                                /*
                                 *  A simple automaton to process lines like
                                 *  Groups: 14 500 513
                                 */
                        do {
                                c = *p++;
                                if (!c) {
                                        /* refill buffer */
                                        got = read(fd, buf, BUFSZ);
                                        buf[got] = 0;
                                        p = buf;
                                        c = *p++; /* 0 at end of file */
                                }
                                switch (state) {
                                case INKEY :
                                        if (key[matched] == c) {
                                                if (!key[++matched])
                                                        state = INSEP;
                                        } else
                                                if (key[0] == c)
                                                        matched = 1;
                                                else
                                                        matched = 0;
                                        break;
                                case INSEP :
                                        if ((c >= '0') && (c <= '9')) {
                                                grp = c - '0';
                                                state = INNUM;
                                        } else
                                                if ((c != ' ') && (c != '\t'))
                                                        state = INEND;
                                        break;
                                case INNUM :
                                        if ((c >= '0') && (c <= '9'))
                                                grp = grp*10 + c - '0';
                                        else {
                                                ismember = (grp == gid);
                                                if ((c != ' ') && (c != '\t'))
                                                        state = INEND;
                                                else
                                                        state = INSEP;
                                        }
                                default :
                                        break;
                                }
                        } while (!ismember && c && (state != INEND));
                close(fd);
                if (!c)
                        ntfs_log_error("No group record found in %s\n",filename);
                } else
                        ntfs_log_error("Could not open %s\n",filename);
        }
        return (ismember);
}

#endif /* defined(__sun) && defined (__SVR4) */

#if POSIXACLS

/*
 *              Extract the basic permissions from a Posix ACL
 *
 *      This is only to be used when Posix ACLs are compiled in,
 *      but not enabled in the mount options.
 *
 *      it replaces the permission mask by the group permissions.
 *      If special groups are mapped, they are also considered as world.
 */

static int ntfs_basic_perms(const struct SECURITY_CONTEXT *scx,
                        const struct POSIX_SECURITY *pxdesc)
{
        int k;
        int perms;
        const struct POSIX_ACE *pace;
        const struct MAPPING* group;

        k = 0;
        perms = pxdesc->mode;
        for (k=0; k < pxdesc->acccnt; k++) {
                pace = &pxdesc->acl.ace[k];
                if (pace->tag == POSIX_ACL_GROUP_OBJ)
                        perms = (perms & 07707)
                                | ((pace->perms & 7) << 3);
                else
                        if (pace->tag == POSIX_ACL_GROUP) {
                                group = scx->mapping[MAPGROUPS];
                                while (group && (group->xid != pace->id))
                                        group = group->next;
                                if (group && group->grcnt
                                    && (*(group->groups) == (gid_t)pace->id))
                                        perms |= pace->perms & 7;
                        }
        }
        return (perms);
}

#endif /* POSIXACLS */

/*
 *      Cacheing is done two-way :
 *      - from uid, gid and perm to securid (CACHED_SECURID)
 *      - from a securid to uid, gid and perm (CACHED_PERMISSIONS)
 *
 *      CACHED_SECURID data is kept in a most-recent-first list
 *      which should not be too long to be efficient. Its optimal
 *      size is depends on usage and is hard to determine.
 *
 *      CACHED_PERMISSIONS data is kept in a two-level indexed array. It
 *      is optimal at the expense of storage. Use of a most-recent-first
 *      list would save memory and provide similar performances for
 *      standard usage, but not for file servers with too many file
 *      owners
 *
 *      CACHED_PERMISSIONS_LEGACY is a special case for CACHED_PERMISSIONS
 *      for legacy directories which were not allocated a security_id
 *      it is organized in a most-recent-first list.
 *
 *      In main caches, data is never invalidated, as the meaning of
 *      a security_id only changes when user mapping is changed, which
 *      current implies remounting. However returned entries may be
 *      overwritten at next update, so data has to be copied elsewhere
 *      before another cache update is made.
 *      In legacy cache, data has to be invalidated when protection is
 *      changed.
 *
 *      Though the same data may be found in both list, they
 *      must be kept separately : the interpretation of ACL
 *      in both direction are approximations which could be non
 *      reciprocal for some configuration of the user mapping data
 *
 *      During the process of recompiling ntfs-3g from a tgz archive,
 *      security processing added 7.6% to the cpu time used by ntfs-3g
 *      and 30% if the cache is disabled.
 */

static struct PERMISSIONS_CACHE *create_caches(struct SECURITY_CONTEXT *scx,
                        u32 securindex)
{
        struct PERMISSIONS_CACHE *cache;
        unsigned int index1;
        unsigned int i;

        cache = (struct PERMISSIONS_CACHE*)NULL;
                /* create the first permissions blocks */
        index1 = securindex >> CACHE_PERMISSIONS_BITS;
        cache = (struct PERMISSIONS_CACHE*)
                ntfs_malloc(sizeof(struct PERMISSIONS_CACHE)
                      + index1*sizeof(struct CACHED_PERMISSIONS*));
        if (cache) {
                cache->head.last = index1;
                cache->head.p_reads = 0;
                cache->head.p_hits = 0;
                cache->head.p_writes = 0;
                *scx->pseccache = cache;
                for (i=0; i<=index1; i++)
                        cache->cachetable[i]
                           = (struct CACHED_PERMISSIONS*)NULL;
        }
        return (cache);
}

/*
 *              Free memory used by caches
 *      The only purpose is to facilitate the detection of memory leaks
 */

static void free_caches(struct SECURITY_CONTEXT *scx)
{
        unsigned int index1;
        struct PERMISSIONS_CACHE *pseccache;

        pseccache = *scx->pseccache;
        if (pseccache) {
                for (index1=0; index1<=pseccache->head.last; index1++)
                        if (pseccache->cachetable[index1]) {
#if POSIXACLS
                                struct CACHED_PERMISSIONS *cacheentry;
                                unsigned int index2;

                                for (index2=0; index2<(1<< CACHE_PERMISSIONS_BITS); index2++) {
                                        cacheentry = &pseccache->cachetable[index1][index2];
                                        if (cacheentry->valid
                                            && cacheentry->pxdesc)
                                                free(cacheentry->pxdesc);
                                        }
#endif
                                free(pseccache->cachetable[index1]);
                        }
                free(pseccache);
        }
}

static int compare(const struct CACHED_SECURID *cached,
                        const struct CACHED_SECURID *item)
{
#if POSIXACLS
        size_t csize;
        size_t isize;

                /* only compare data and sizes */
        csize = (cached->variable ?
                sizeof(struct POSIX_ACL)
                + (((struct POSIX_SECURITY*)cached->variable)->acccnt
                   + ((struct POSIX_SECURITY*)cached->variable)->defcnt)
                        *sizeof(struct POSIX_ACE) :
                0);
        isize = (item->variable ?
                sizeof(struct POSIX_ACL)
                + (((struct POSIX_SECURITY*)item->variable)->acccnt
                   + ((struct POSIX_SECURITY*)item->variable)->defcnt)
                        *sizeof(struct POSIX_ACE) :
                0);
        return ((cached->uid != item->uid)
                 || (cached->gid != item->gid)
                 || (cached->dmode != item->dmode)
                 || (csize != isize)
                 || (csize
                    && isize
                    && memcmp(&((struct POSIX_SECURITY*)cached->variable)->acl,
                       &((struct POSIX_SECURITY*)item->variable)->acl, csize)));
#else
        return ((cached->uid != item->uid)
                 || (cached->gid != item->gid)
                 || (cached->dmode != item->dmode));
#endif
}

static int leg_compare(const struct CACHED_PERMISSIONS_LEGACY *cached,
                        const struct CACHED_PERMISSIONS_LEGACY *item)
{
        return (cached->mft_no != item->mft_no);
}

/*
 *      Resize permission cache table
 *      do not call unless resizing is needed
 *
 *      If allocation fails, the cache size is not updated
 *      Lack of memory is not considered as an error, the cache is left
 *      consistent and errno is not set.
 */

static void resize_cache(struct SECURITY_CONTEXT *scx,
                        u32 securindex)
{
        struct PERMISSIONS_CACHE *oldcache;
        struct PERMISSIONS_CACHE *newcache;
        int newcnt;
        int oldcnt;
        unsigned int index1;
        unsigned int i;

        oldcache = *scx->pseccache;
        index1 = securindex >> CACHE_PERMISSIONS_BITS;
        newcnt = index1 + 1;
        if (newcnt <= ((CACHE_PERMISSIONS_SIZE
                        + (1 << CACHE_PERMISSIONS_BITS)
                        - 1) >> CACHE_PERMISSIONS_BITS)) {
                /* expand cache beyond current end, do not use realloc() */
                /* to avoid losing data when there is no more memory */
                oldcnt = oldcache->head.last + 1;
                newcache = (struct PERMISSIONS_CACHE*)
                        ntfs_malloc(
                            sizeof(struct PERMISSIONS_CACHE)
                              + (newcnt - 1)*sizeof(struct CACHED_PERMISSIONS*));
                if (newcache) {
                        memcpy(newcache,oldcache,
                            sizeof(struct PERMISSIONS_CACHE)
                              + (oldcnt - 1)*sizeof(struct CACHED_PERMISSIONS*));
                        free(oldcache);
                             /* mark new entries as not valid */
                        for (i=newcache->head.last+1; i<=index1; i++)
                                newcache->cachetable[i]
                                         = (struct CACHED_PERMISSIONS*)NULL;
                        newcache->head.last = index1;
                        *scx->pseccache = newcache;
                }
        }
}

/*
 *      Enter uid, gid and mode into cache, if possible
 *
 *      returns the updated or created cache entry,
 *      or NULL if not possible (typically if there is no
 *              security id associated)
 */

#if POSIXACLS
static struct CACHED_PERMISSIONS *enter_cache(struct SECURITY_CONTEXT *scx,
                ntfs_inode *ni, uid_t uid, gid_t gid,
                struct POSIX_SECURITY *pxdesc)
#else
static struct CACHED_PERMISSIONS *enter_cache(struct SECURITY_CONTEXT *scx,
                ntfs_inode *ni, uid_t uid, gid_t gid, mode_t mode)
#endif
{
        struct CACHED_PERMISSIONS *cacheentry;
        struct CACHED_PERMISSIONS *cacheblock;
        struct PERMISSIONS_CACHE *pcache;
        u32 securindex;
#if POSIXACLS
        int pxsize;
        struct POSIX_SECURITY *pxcached;
#endif
        unsigned int index1;
        unsigned int index2;
        int i;

        /* cacheing is only possible if a security_id has been defined */
        if (test_nino_flag(ni, v3_Extensions)
           && ni->security_id) {
                /*
                 *  Immediately test the most frequent situation
                 *  where the entry exists
                 */
                securindex = le32_to_cpu(ni->security_id);
                index1 = securindex >> CACHE_PERMISSIONS_BITS;
                index2 = securindex & ((1 << CACHE_PERMISSIONS_BITS) - 1);
                pcache = *scx->pseccache;
                if (pcache
                     && (pcache->head.last >= index1)
                     && pcache->cachetable[index1]) {
                        cacheentry = &pcache->cachetable[index1][index2];
                        cacheentry->uid = uid;
                        cacheentry->gid = gid;
#if POSIXACLS
                        if (cacheentry->valid && cacheentry->pxdesc)
                                free(cacheentry->pxdesc);
                        if (pxdesc) {
                                pxsize = sizeof(struct POSIX_SECURITY)
                                        + (pxdesc->acccnt + pxdesc->defcnt)*sizeof(struct POSIX_ACE);
                                pxcached = (struct POSIX_SECURITY*)malloc(pxsize);
                                if (pxcached) {
                                        memcpy(pxcached, pxdesc, pxsize);
                                        cacheentry->pxdesc = pxcached;
                                } else {
                                        cacheentry->valid = 0;
                                        cacheentry = (struct CACHED_PERMISSIONS*)NULL;
                                }
                                cacheentry->mode = pxdesc->mode & 07777;
                        } else
                                cacheentry->pxdesc = (struct POSIX_SECURITY*)NULL;
#else
                        cacheentry->mode = mode & 07777;
#endif
                        cacheentry->inh_fileid = const_cpu_to_le32(0);
                        cacheentry->inh_dirid = const_cpu_to_le32(0);
                        cacheentry->valid = 1;
                        pcache->head.p_writes++;
                } else {
                        if (!pcache) {
                                /* create the first cache block */
                                pcache = create_caches(scx, securindex);
                        } else {
                                if (index1 > pcache->head.last) {
                                        resize_cache(scx, securindex);
                                        pcache = *scx->pseccache;
                                }
                        }
                        /* allocate block, if cache table was allocated */
                        if (pcache && (index1 <= pcache->head.last)) {
                                cacheblock = (struct CACHED_PERMISSIONS*)
                                        malloc(sizeof(struct CACHED_PERMISSIONS)
                                                << CACHE_PERMISSIONS_BITS);
                                pcache->cachetable[index1] = cacheblock;
                                for (i=0; i<(1 << CACHE_PERMISSIONS_BITS); i++)
                                        cacheblock[i].valid = 0;
                                cacheentry = &cacheblock[index2];
                                if (cacheentry) {
                                        cacheentry->uid = uid;
                                        cacheentry->gid = gid;
#if POSIXACLS
                                        if (pxdesc) {
                                                pxsize = sizeof(struct POSIX_SECURITY)
                                                        + (pxdesc->acccnt + pxdesc->defcnt)*sizeof(struct POSIX_ACE);
                                                pxcached = (struct POSIX_SECURITY*)malloc(pxsize);
                                                if (pxcached) {
                                                        memcpy(pxcached, pxdesc, pxsize);
                                                        cacheentry->pxdesc = pxcached;
                                                } else {
                                                        cacheentry->valid = 0;
                                                        cacheentry = (struct CACHED_PERMISSIONS*)NULL;
                                                }
                                                cacheentry->mode = pxdesc->mode & 07777;
                                        } else
                                                cacheentry->pxdesc = (struct POSIX_SECURITY*)NULL;
#else
                                        cacheentry->mode = mode & 07777;
#endif
                                        cacheentry->inh_fileid = const_cpu_to_le32(0);
                                        cacheentry->inh_dirid = const_cpu_to_le32(0);
                                        cacheentry->valid = 1;
                                        pcache->head.p_writes++;
                                }
                        } else
                                cacheentry = (struct CACHED_PERMISSIONS*)NULL;
                }
        } else {
                cacheentry = (struct CACHED_PERMISSIONS*)NULL;
#if CACHE_LEGACY_SIZE
                if (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) {
                        struct CACHED_PERMISSIONS_LEGACY wanted;
                        struct CACHED_PERMISSIONS_LEGACY *legacy;

                        wanted.perm.uid = uid;
                        wanted.perm.gid = gid;
#if POSIXACLS
                        wanted.perm.mode = pxdesc->mode & 07777;
                        wanted.perm.inh_fileid = const_cpu_to_le32(0);
                        wanted.perm.inh_dirid = const_cpu_to_le32(0);
                        wanted.mft_no = ni->mft_no;
                        wanted.variable = (void*)pxdesc;
                        wanted.varsize = sizeof(struct POSIX_SECURITY)
                                        + (pxdesc->acccnt + pxdesc->defcnt)*sizeof(struct POSIX_ACE);
#else
                        wanted.perm.mode = mode & 07777;
                        wanted.perm.inh_fileid = const_cpu_to_le32(0);
                        wanted.perm.inh_dirid = const_cpu_to_le32(0);
                        wanted.mft_no = ni->mft_no;
                        wanted.variable = (void*)NULL;
                        wanted.varsize = 0;
#endif
                        legacy = (struct CACHED_PERMISSIONS_LEGACY*)ntfs_enter_cache(
                                scx->vol->legacy_cache, GENERIC(&wanted),
                                (cache_compare)leg_compare);
                        if (legacy) {
                                cacheentry = &legacy->perm;
#if POSIXACLS
                                /*
                                 * give direct access to the cached pxdesc
                                 * in the permissions structure
                                 */
                                cacheentry->pxdesc = legacy->variable;
#endif
                        }
                }
#endif
        }
        return (cacheentry);
}

/*
 *      Fetch owner, group and permission of a file, if cached
 *
 *      Beware : do not use the returned entry after a cache update :
 *      the cache may be relocated making the returned entry meaningless
 *
 *      returns the cache entry, or NULL if not available
 */

static struct CACHED_PERMISSIONS *fetch_cache(struct SECURITY_CONTEXT *scx,
                ntfs_inode *ni)
{
        struct CACHED_PERMISSIONS *cacheentry;
        struct PERMISSIONS_CACHE *pcache;
        u32 securindex;
        unsigned int index1;
        unsigned int index2;

        /* cacheing is only possible if a security_id has been defined */
        cacheentry = (struct CACHED_PERMISSIONS*)NULL;
        if (test_nino_flag(ni, v3_Extensions)
           && (ni->security_id)) {
                securindex = le32_to_cpu(ni->security_id);
                index1 = securindex >> CACHE_PERMISSIONS_BITS;
                index2 = securindex & ((1 << CACHE_PERMISSIONS_BITS) - 1);
                pcache = *scx->pseccache;
                if (pcache
                     && (pcache->head.last >= index1)
                     && pcache->cachetable[index1]) {
                        cacheentry = &pcache->cachetable[index1][index2];
                        /* reject if entry is not valid */
                        if (!cacheentry->valid)
                                cacheentry = (struct CACHED_PERMISSIONS*)NULL;
                        else
                                pcache->head.p_hits++;
                if (pcache)
                        pcache->head.p_reads++;
                }
        }
#if CACHE_LEGACY_SIZE
        else {
                cacheentry = (struct CACHED_PERMISSIONS*)NULL;
                if (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) {
                        struct CACHED_PERMISSIONS_LEGACY wanted;
                        struct CACHED_PERMISSIONS_LEGACY *legacy;

                        wanted.mft_no = ni->mft_no;
                        wanted.variable = (void*)NULL;
                        wanted.varsize = 0;
                        legacy = (struct CACHED_PERMISSIONS_LEGACY*)ntfs_fetch_cache(
                                scx->vol->legacy_cache, GENERIC(&wanted),
                                (cache_compare)leg_compare);
                        if (legacy) cacheentry = &legacy->perm;
                }
        }
#endif
#if POSIXACLS
        if (cacheentry && !cacheentry->pxdesc) {
                ntfs_log_error("No Posix descriptor in cache\n");
                cacheentry = (struct CACHED_PERMISSIONS*)NULL;
        }
#endif
        return (cacheentry);
}

/*
 *      Retrieve a security attribute from $Secure
 */

static char *retrievesecurityattr(ntfs_volume *vol, SII_INDEX_KEY id)
{
        struct SII *psii;
        union {
                struct {
                        le32 dataoffsl;
                        le32 dataoffsh;
                } parts;
                le64 all;
        } realign;
        int found;
        size_t size;
        size_t rdsize;
        s64 offs;
        ntfs_inode *ni;
        ntfs_index_context *xsii;
        char *securattr;

        securattr = (char*)NULL;
        ni = vol->secure_ni;
        xsii = vol->secure_xsii;
        if (ni && xsii) {
                ntfs_index_ctx_reinit(xsii);
                found =
                    !ntfs_index_lookup((char*)&id,
                                       sizeof(SII_INDEX_KEY), xsii);
                if (found) {
                        psii = (struct SII*)xsii->entry;
                        size =
                            (size_t) le32_to_cpu(psii->datasize)
                                 - sizeof(SECURITY_DESCRIPTOR_HEADER);
                        /* work around bad alignment problem */
                        realign.parts.dataoffsh = psii->dataoffsh;
                        realign.parts.dataoffsl = psii->dataoffsl;
                        offs = le64_to_cpu(realign.all)
                                + sizeof(SECURITY_DESCRIPTOR_HEADER);

                        securattr = (char*)ntfs_malloc(size);
                        if (securattr) {
                                rdsize = ntfs_attr_data_read(
                                        ni, STREAM_SDS, 4,
                                        securattr, size, offs);
                                if ((rdsize != size)
                                        || !ntfs_valid_descr(securattr,
                                                rdsize)) {
                                        /* error to be logged by caller */
                                        free(securattr);
                                        securattr = (char*)NULL;
                                }
                        }
                } else
                        if (errno != ENOENT)
                                ntfs_log_perror("Inconsistency in index $SII");
        }
        if (!securattr) {
                ntfs_log_error("Failed to retrieve a security descriptor\n");
                errno = EIO;
        }
        return (securattr);
}

/*
 *              Get the security descriptor associated to a file
 *
 *      Either :
 *         - read the security descriptor attribute (v1.x format)
 *         - or find the descriptor in $Secure:$SDS (v3.x format)
 *
 *      in both case, sanity checks are done on the attribute and
 *      the descriptor can be assumed safe
 *
 *      The returned descriptor is dynamically allocated and has to be freed
 */

static char *getsecurityattr(ntfs_volume *vol, ntfs_inode *ni)
{
        SII_INDEX_KEY securid;
        char *securattr;
        s64 readallsz;

                /*
                 * Warning : in some situations, after fixing by chkdsk,
                 * v3_Extensions are marked present (long standard informations)
                 * with a default security descriptor inserted in an
                 * attribute
                 */
        if (test_nino_flag(ni, v3_Extensions)
            && vol->secure_ni && ni->security_id) {
                        /* get v3.x descriptor in $Secure */
                securid.security_id = ni->security_id;
                securattr = retrievesecurityattr(vol,securid);
                if (!securattr)
                        ntfs_log_error("Bad security descriptor for 0x%lx\n",
                                        (long)le32_to_cpu(ni->security_id));
        } else {
                        /* get v1.x security attribute */
                readallsz = 0;
                securattr = ntfs_attr_readall(ni, AT_SECURITY_DESCRIPTOR,
                                AT_UNNAMED, 0, &readallsz);
                if (securattr && !ntfs_valid_descr(securattr, readallsz)) {
                        ntfs_log_error("Bad security descriptor for inode %lld\n",
                                (long long)ni->mft_no);
                        free(securattr);
                        securattr = (char*)NULL;
                }
        }
        if (!securattr) {
                        /*
                         * in some situations, there is no security
                         * descriptor, and chkdsk does not detect or fix
                         * anything. This could be a normal situation.
                         * When this happens, simulate a descriptor with
                         * minimum rights, so that a real descriptor can
                         * be created by chown or chmod
                         */
                ntfs_log_error("No security descriptor found for inode %lld\n",
                                (long long)ni->mft_no);
                securattr = ntfs_build_descr(0, 0, adminsid, adminsid);
        }
        return (securattr);
}

#if POSIXACLS

/*
 *              Determine which access types to a file are allowed
 *      according to the relation of current process to the file
 *
 *      When Posix ACLs are compiled in but not enabled in the mount
 *      options POSIX_ACL_USER, POSIX_ACL_GROUP and POSIX_ACL_MASK
 *      are ignored.
 */

static int access_check_posix(struct SECURITY_CONTEXT *scx,
                        struct POSIX_SECURITY *pxdesc, mode_t request,
                        uid_t uid, gid_t gid)
{
        struct POSIX_ACE *pxace;
        int userperms;
        int groupperms;
        int mask;
        BOOL somegroup;
        BOOL needgroups;
        BOOL noacl;
        mode_t perms;
        int i;

        noacl = !(scx->vol->secure_flags & (1 << SECURITY_ACL));
        if (noacl)
                perms = ntfs_basic_perms(scx, pxdesc);
        else
                perms = pxdesc->mode;
                                        /* owner and root access */
        if (!scx->uid || (uid == scx->uid)) {
                if (!scx->uid) {
                                        /* root access if owner or other execution */
                        if (perms & 0101)
                                perms |= 01777;
                        else {
                                        /* root access if some group execution */
                                groupperms = 0;
                                mask = 7;
                                for (i=pxdesc->acccnt-1; i>=0 ; i--) {
                                        pxace = &pxdesc->acl.ace[i];
                                        switch (pxace->tag) {
                                        case POSIX_ACL_USER_OBJ :
                                        case POSIX_ACL_GROUP_OBJ :
                                                groupperms |= pxace->perms;
                                                break;
                                        case POSIX_ACL_GROUP :
                                                if (!noacl)
                                                        groupperms
                                                            |= pxace->perms;
                                                break;
                                        case POSIX_ACL_MASK :
                                                if (!noacl)
                                                        mask = pxace->perms & 7;
                                                break;
                                        default :
                                                break;
                                        }
                                }
                                perms = (groupperms & mask & 1) | 6;
                        }
                } else
                        perms &= 07700;
        } else {
                                /*
                                 * analyze designated users, get mask
                                 * and identify whether we need to check
                                 * the group memberships. The groups are
                                 * not needed when all groups have the
                                 * same permissions as other for the
                                 * requested modes.
                                 */
                userperms = -1;
                groupperms = -1;
                needgroups = FALSE;
                mask = 7;
                for (i=pxdesc->acccnt-1; i>=0 ; i--) {
                        pxace = &pxdesc->acl.ace[i];
                        switch (pxace->tag) {
                        case POSIX_ACL_USER :
                                if (!noacl
                                    && ((uid_t)pxace->id == scx->uid))
                                        userperms = pxace->perms;
                                break;
                        case POSIX_ACL_MASK :
                                if (!noacl)
                                        mask = pxace->perms & 7;
                                break;
                        case POSIX_ACL_GROUP_OBJ :
                                if (((pxace->perms & mask) ^ perms)
                                    & (request >> 6) & 7)
                                        needgroups = TRUE;
                                break;
                        case POSIX_ACL_GROUP :
                                if (!noacl
                                    && (((pxace->perms & mask) ^ perms)
                                            & (request >> 6) & 7))
                                        needgroups = TRUE;
                                break;
                        default :
                                break;
                        }
                }
                                        /* designated users */
                if (userperms >= 0)
                        perms = (perms & 07000) + (userperms & mask);
                else if (!needgroups)
                                perms &= 07007;
                else {
                                        /* owning group */
                        if (!(~(perms >> 3) & request & mask)
                            && ((gid == scx->gid)
                                || groupmember(scx, scx->uid, gid)))
                                perms &= 07070;
                        else if (!noacl) {
                                        /* other groups */
                                groupperms = -1;
                                somegroup = FALSE;
                                for (i=pxdesc->acccnt-1; i>=0 ; i--) {
                                        pxace = &pxdesc->acl.ace[i];
                                        if ((pxace->tag == POSIX_ACL_GROUP)
                                            && groupmember(scx, scx->uid, pxace->id)) {
                                                if (!(~pxace->perms & request & mask))
                                                        groupperms = pxace->perms;
                                                somegroup = TRUE;
                                        }
                                }
                                if (groupperms >= 0)
                                        perms = (perms & 07000) + (groupperms & mask);
                                else
                                        if (somegroup)
                                                perms = 0;
                                        else
                                                perms &= 07007;
                        } else
                                perms &= 07007;
                }
        }
        return (perms);
}

/*
 *              Get permissions to access a file
 *      Takes into account the relation of user to file (owner, group, ...)
 *      Do no use as mode of the file
 *      Do no call if default_permissions is set
 *
 *      returns -1 if there is a problem
 */

static int ntfs_get_perm(struct SECURITY_CONTEXT *scx,
                 ntfs_inode * ni, mode_t request)
{
        const SECURITY_DESCRIPTOR_RELATIVE *phead;
        const struct CACHED_PERMISSIONS *cached;
        char *securattr;
        const SID *usid;        /* owner of file/directory */
        const SID *gsid;        /* group of file/directory */
        uid_t uid;
        gid_t gid;
        int perm;
        BOOL isdir;
        struct POSIX_SECURITY *pxdesc;

        if (!scx->mapping[MAPUSERS])
                perm = 07777;
        else {
                /* check whether available in cache */
                cached = fetch_cache(scx,ni);
                if (cached) {
                        uid = cached->uid;
                        gid = cached->gid;
                        perm = access_check_posix(scx,cached->pxdesc,request,uid,gid);
                } else {
                        perm = 0;       /* default to no permission */
                        isdir = (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY)
                                != const_cpu_to_le16(0);
                        securattr = getsecurityattr(scx->vol, ni);
                        if (securattr) {
                                phead = (const SECURITY_DESCRIPTOR_RELATIVE*)
                                        securattr;
                                gsid = (const SID*)&
                                           securattr[le32_to_cpu(phead->group)];
                                gid = ntfs_find_group(scx->mapping[MAPGROUPS],gsid);
#if OWNERFROMACL
                                usid = ntfs_acl_owner(securattr);
                                pxdesc = ntfs_build_permissions_posix(scx->mapping,securattr,
                                                 usid, gsid, isdir);
                                if (pxdesc)
                                        perm = pxdesc->mode & 07777;
                                else
                                        perm = -1;
                                uid = ntfs_find_user(scx->mapping[MAPUSERS],usid);
#else
                                usid = (const SID*)&
                                            securattr[le32_to_cpu(phead->owner)];
                                pxdesc = ntfs_build_permissions_posix(scx,securattr,
                                                 usid, gsid, isdir);
                                if (pxdesc)
                                        perm = pxdesc->mode & 07777;
                                else
                                        perm = -1;
                                if (!perm && ntfs_same_sid(usid, adminsid)) {
                                        uid = find_tenant(scx, securattr);
                                        if (uid)
                                                perm = 0700;
                                } else
                                        uid = ntfs_find_user(scx->mapping[MAPUSERS],usid);
#endif
                                /*
                                 *  Create a security id if there were none
                                 * and upgrade option is selected
                                 */
                                if (!test_nino_flag(ni, v3_Extensions)
                                   && (perm >= 0)
                                   && (scx->vol->secure_flags
                                     & (1 << SECURITY_ADDSECURIDS))) {
                                        upgrade_secur_desc(scx->vol,
                                                securattr, ni);
                                        /*
                                         * fetch owner and group for cacheing
                                         * if there is a securid
                                         */
                                }
                                if (test_nino_flag(ni, v3_Extensions)
                                    && (perm >= 0)) {
                                        enter_cache(scx, ni, uid,
                                                        gid, pxdesc);
                                }
                                if (pxdesc) {
                                        perm = access_check_posix(scx,pxdesc,request,uid,gid);
                                        free(pxdesc);
                                }
                                free(securattr);
                        } else {
                                perm = -1;
                                uid = gid = 0;
                        }
                }
        }
        return (perm);
}

/*
 *              Get a Posix ACL
 *
 *      returns size or -errno if there is a problem
 *      if size was too small, no copy is done and errno is not set,
 *      the caller is expected to issue a new call
 */

int ntfs_get_posix_acl(struct SECURITY_CONTEXT *scx, ntfs_inode *ni,
                        const char *name, char *value, size_t size)
{
        const SECURITY_DESCRIPTOR_RELATIVE *phead;
        struct POSIX_SECURITY *pxdesc;
        const struct CACHED_PERMISSIONS *cached;
        char *securattr;
        const SID *usid;        /* owner of file/directory */
        const SID *gsid;        /* group of file/directory */
        uid_t uid;
        gid_t gid;
        BOOL isdir;
        size_t outsize;

        outsize = 0;    /* default to error */
        if (!scx->mapping[MAPUSERS])
                errno = ENOTSUP;
        else {
                        /* check whether available in cache */
                cached = fetch_cache(scx,ni);
                if (cached)
                        pxdesc = cached->pxdesc;
                else {
                        securattr = getsecurityattr(scx->vol, ni);
                        isdir = (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY)
                                != const_cpu_to_le16(0);
                        if (securattr) {
                                phead =
                                    (const SECURITY_DESCRIPTOR_RELATIVE*)
                                                securattr;
                                gsid = (const SID*)&
                                          securattr[le32_to_cpu(phead->group)];
#if OWNERFROMACL
                                usid = ntfs_acl_owner(securattr);
#else
                                usid = (const SID*)&
                                          securattr[le32_to_cpu(phead->owner)];
#endif
                                pxdesc = ntfs_build_permissions_posix(scx->mapping,securattr,
                                          usid, gsid, isdir);

                                        /*
                                         * fetch owner and group for cacheing
                                         */
                                if (pxdesc) {
                                /*
                                 *  Create a security id if there were none
                                 * and upgrade option is selected
                                 */
                                        if (!test_nino_flag(ni, v3_Extensions)
                                           && (scx->vol->secure_flags
                                             & (1 << SECURITY_ADDSECURIDS))) {
                                                upgrade_secur_desc(scx->vol,
                                                         securattr, ni);
                                        }
#if OWNERFROMACL
                                        uid = ntfs_find_user(scx->mapping[MAPUSERS],usid);
#else
                                        if (!(pxdesc->mode & 07777)
                                            && ntfs_same_sid(usid, adminsid)) {
                                                uid = find_tenant(scx,
                                                                securattr);
                                        } else
                                                uid = ntfs_find_user(scx->mapping[MAPUSERS],usid);
#endif
                                        gid = ntfs_find_group(scx->mapping[MAPGROUPS],gsid);
                                        if (pxdesc->tagsset & POSIX_ACL_EXTENSIONS)
                                        enter_cache(scx, ni, uid,
                                                        gid, pxdesc);
                                }
                                free(securattr);
                        } else
                                pxdesc = (struct POSIX_SECURITY*)NULL;
                }

                if (pxdesc) {
                        if (ntfs_valid_posix(pxdesc)) {
                                if (!strcmp(name,"system.posix_acl_default")) {
                                        if (ni->mrec->flags
                                                    & MFT_RECORD_IS_DIRECTORY)
                                                outsize = sizeof(struct POSIX_ACL)
                                                        + pxdesc->defcnt*sizeof(struct POSIX_ACE);
                                        else {
                                        /*
                                         * getting default ACL from plain file :
                                         * return EACCES if size > 0 as
                                         * indicated in the man, but return ok
                                         * if size == 0, so that ls does not
                                         * display an error
                                         */
                                                if (size > 0) {
                                                        outsize = 0;
                                                        errno = EACCES;
                                                } else
                                                        outsize = sizeof(struct POSIX_ACL);
                                        }
                                        if (outsize && (outsize <= size)) {
                                                memcpy(value,&pxdesc->acl,sizeof(struct POSIX_ACL));
                                                memcpy(&value[sizeof(struct POSIX_ACL)],
                                                        &pxdesc->acl.ace[pxdesc->firstdef],
                                                        outsize-sizeof(struct POSIX_ACL));
                                        }
                                } else {
                                        outsize = sizeof(struct POSIX_ACL)
                                                + pxdesc->acccnt*sizeof(struct POSIX_ACE);
                                        if (outsize <= size)
                                                memcpy(value,&pxdesc->acl,outsize);
                                }
                        } else {
                                outsize = 0;
                                errno = EIO;
                                ntfs_log_error("Invalid Posix ACL built\n");
                        }
                        if (!cached)
                                free(pxdesc);
                } else
                        outsize = 0;
        }
        return (outsize ? (int)outsize : -errno);
}

#else /* POSIXACLS */


/*
 *              Get permissions to access a file
 *      Takes into account the relation of user to file (owner, group, ...)
 *      Do no use as mode of the file
 *
 *      returns -1 if there is a problem
 */

static int ntfs_get_perm(struct SECURITY_CONTEXT *scx,
                ntfs_inode *ni, mode_t request)
{
        const SECURITY_DESCRIPTOR_RELATIVE *phead;
        const struct CACHED_PERMISSIONS *cached;
        char *securattr;
        const SID *usid;        /* owner of file/directory */
        const SID *gsid;        /* group of file/directory */
        BOOL isdir;
        uid_t uid;
        gid_t gid;
        int perm;

        if (!scx->mapping[MAPUSERS] || (!scx->uid && !(request & S_IEXEC)))
                perm = 07777;
        else {
                /* check whether available in cache */
                cached = fetch_cache(scx,ni);
                if (cached) {
                        perm = cached->mode;
                        uid = cached->uid;
                        gid = cached->gid;
                } else {
                        perm = 0;       /* default to no permission */
                        isdir = (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY)
                                != const_cpu_to_le16(0);
                        securattr = getsecurityattr(scx->vol, ni);
                        if (securattr) {
                                phead = (const SECURITY_DESCRIPTOR_RELATIVE*)
                                        securattr;
                                gsid = (const SID*)&
                                           securattr[le32_to_cpu(phead->group)];
                                gid = ntfs_find_group(scx->mapping[MAPGROUPS],gsid);
#if OWNERFROMACL
                                usid = ntfs_acl_owner(securattr);
                                perm = ntfs_build_permissions(securattr,
                                                 usid, gsid, isdir);
                                uid = ntfs_find_user(scx->mapping[MAPUSERS],usid);
#else
                                usid = (const SID*)&
                                            securattr[le32_to_cpu(phead->owner)];
                                perm = ntfs_build_permissions(securattr,
                                                 usid, gsid, isdir);
                                if (!perm && ntfs_same_sid(usid, adminsid)) {
                                        uid = find_tenant(scx, securattr);
                                        if (uid)
                                                perm = 0700;
                                } else
                                        uid = ntfs_find_user(scx->mapping[MAPUSERS],usid);
#endif
                                /*
                                 *  Create a security id if there were none
                                 * and upgrade option is selected
                                 */
                                if (!test_nino_flag(ni, v3_Extensions)
                                   && (perm >= 0)
                                   && (scx->vol->secure_flags
                                     & (1 << SECURITY_ADDSECURIDS))) {
                                        upgrade_secur_desc(scx->vol,
                                                securattr, ni);
                                        /*
                                         * fetch owner and group for cacheing
                                         * if there is a securid
                                         */
                                }
                                if (test_nino_flag(ni, v3_Extensions)
                                    && (perm >= 0)) {
                                        enter_cache(scx, ni, uid,
                                                        gid, perm);
                                }
                                free(securattr);
                        } else {
                                perm = -1;
                                uid = gid = 0;
                        }
                }
                if (perm >= 0) {
                        if (!scx->uid) {
                                /* root access and execution */
                                if (perm & 0111)
                                        perm |= 01777;
                                else
                                        perm = 0;
                        } else
                                if (uid == scx->uid)
                                        perm &= 07700;
                                else
                                /*
                                 * avoid checking group membership
                                 * when the requested perms for group
                                 * are the same as perms for other
                                 */
                                        if ((gid == scx->gid)
                                          || ((((perm >> 3) ^ perm)
                                                & (request >> 6) & 7)
                                            && groupmember(scx, scx->uid, gid)))
                                                perm &= 07070;
                                        else
                                                perm &= 07007;
                }
        }
        return (perm);
}

#endif /* POSIXACLS */

/*
 *              Get an NTFS ACL
 *
 *      Returns size or -errno if there is a problem
 *      if size was too small, no copy is done and errno is not set,
 *      the caller is expected to issue a new call
 */

int ntfs_get_ntfs_acl(struct SECURITY_CONTEXT *scx, ntfs_inode *ni,
                        char *value, size_t size)
{
        char *securattr;
        size_t outsize;

        outsize = 0;    /* default to no data and no error */
        securattr = getsecurityattr(scx->vol, ni);
        if (securattr) {
                outsize = ntfs_attr_size(securattr);
                if (outsize <= size) {
                        memcpy(value,securattr,outsize);
                }
                free(securattr);
        }
        return (outsize ? (int)outsize : -errno);
}

/*
 *              Get owner, group and permissions in an stat structure
 *      returns permissions, or -1 if there is a problem
 */

int ntfs_get_owner_mode(struct SECURITY_CONTEXT *scx,
                ntfs_inode * ni, struct stat *stbuf)
{
        const SECURITY_DESCRIPTOR_RELATIVE *phead;
        char *securattr;
        const SID *usid;        /* owner of file/directory */
        const SID *gsid;        /* group of file/directory */
        const struct CACHED_PERMISSIONS *cached;
        int perm;
        BOOL isdir;
#if POSIXACLS
        struct POSIX_SECURITY *pxdesc;
#endif

        if (!scx->mapping[MAPUSERS])
                perm = 07777;
        else {
                        /* check whether available in cache */
                cached = fetch_cache(scx,ni);
                if (cached) {
#if POSIXACLS
                        if (!(scx->vol->secure_flags & (1 << SECURITY_ACL))
                            && cached->pxdesc)
                                perm = ntfs_basic_perms(scx,cached->pxdesc);
                        else
#endif
                                perm = cached->mode;
                        stbuf->st_uid = cached->uid;
                        stbuf->st_gid = cached->gid;
                        stbuf->st_mode = (stbuf->st_mode & ~07777) + perm;
                } else {
                        perm = -1;      /* default to error */
                        isdir = (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY)
                                != const_cpu_to_le16(0);
                        securattr = getsecurityattr(scx->vol, ni);
                        if (securattr) {
                                phead =
                                    (const SECURITY_DESCRIPTOR_RELATIVE*)
                                                securattr;
                                gsid = (const SID*)&
                                          securattr[le32_to_cpu(phead->group)];
#if OWNERFROMACL
                                usid = ntfs_acl_owner(securattr);
#else
                                usid = (const SID*)&
                                          securattr[le32_to_cpu(phead->owner)];
#endif
#if POSIXACLS
                                pxdesc = ntfs_build_permissions_posix(
                                                scx->mapping, securattr,
                                        usid, gsid, isdir);
                                if (pxdesc) {
                                        if (!(scx->vol->secure_flags
                                            & (1 << SECURITY_ACL)))
                                                perm = ntfs_basic_perms(scx,
                                                                pxdesc);
                                        else
                                                perm = pxdesc->mode & 07777;
                                } else
                                        perm = -1;
#else
                                perm = ntfs_build_permissions(securattr,
                                          usid, gsid, isdir);
#endif
                                        /*
                                         * fetch owner and group for cacheing
                                         */
                                if (perm >= 0) {
                                /*
                                 *  Create a security id if there were none
                                 * and upgrade option is selected
                                 */
                                        if (!test_nino_flag(ni, v3_Extensions)
                                           && (scx->vol->secure_flags
                                             & (1 << SECURITY_ADDSECURIDS))) {
                                                upgrade_secur_desc(scx->vol,
                                                         securattr, ni);
                                        }
#if OWNERFROMACL
                                        stbuf->st_uid = ntfs_find_user(scx->mapping[MAPUSERS],usid);
#else
                                        if (!perm && ntfs_same_sid(usid, adminsid)) {
                                                stbuf->st_uid =
                                                        find_tenant(scx,
                                                                securattr);
                                                if (stbuf->st_uid)
                                                        perm = 0700;
                                        } else
                                                stbuf->st_uid = ntfs_find_user(scx->mapping[MAPUSERS],usid);
#endif
                                        stbuf->st_gid = ntfs_find_group(scx->mapping[MAPGROUPS],gsid);
                                        stbuf->st_mode =
                                            (stbuf->st_mode & ~07777) + perm;
#if POSIXACLS
                                        enter_cache(scx, ni, stbuf->st_uid,
                                                stbuf->st_gid, pxdesc);
                                        free(pxdesc);
#else
                                        enter_cache(scx, ni, stbuf->st_uid,
                                                stbuf->st_gid, perm);
#endif
                                }
                                free(securattr);
                        }
                }
        }
        return (perm);
}

#if POSIXACLS

/*
 *              Get the base for a Posix inheritance and
 *      build an inherited Posix descriptor
 */

static struct POSIX_SECURITY *inherit_posix(struct SECURITY_CONTEXT *scx,
                        ntfs_inode *dir_ni, mode_t mode, BOOL isdir)
{
        const struct CACHED_PERMISSIONS *cached;
        const SECURITY_DESCRIPTOR_RELATIVE *phead;
        struct POSIX_SECURITY *pxdesc;
        struct POSIX_SECURITY *pydesc;
        char *securattr;
        const SID *usid;
        const SID *gsid;
        uid_t uid;
        gid_t gid;

        pydesc = (struct POSIX_SECURITY*)NULL;
                /* check whether parent directory is available in cache */
        cached = fetch_cache(scx,dir_ni);
        if (cached) {
                uid = cached->uid;
                gid = cached->gid;
                pxdesc = cached->pxdesc;
                if (pxdesc) {
                        if (scx->vol->secure_flags & (1 << SECURITY_ACL))
                                pydesc = ntfs_build_inherited_posix(pxdesc,
                                        mode, scx->umask, isdir);
                        else
                                pydesc = ntfs_build_basic_posix(pxdesc,
                                        mode, scx->umask, isdir);
                }
        } else {
                securattr = getsecurityattr(scx->vol, dir_ni);
                if (securattr) {
                        phead = (const SECURITY_DESCRIPTOR_RELATIVE*)
                                securattr;
                        gsid = (const SID*)&
                                   securattr[le32_to_cpu(phead->group)];
                        gid = ntfs_find_group(scx->mapping[MAPGROUPS],gsid);
#if OWNERFROMACL
                        usid = ntfs_acl_owner(securattr);
                        pxdesc = ntfs_build_permissions_posix(scx->mapping,securattr,
                                                 usid, gsid, TRUE);
                        uid = ntfs_find_user(scx->mapping[MAPUSERS],usid);
#else
                        usid = (const SID*)&
                                    securattr[le32_to_cpu(phead->owner)];
                        pxdesc = ntfs_build_permissions_posix(scx->mapping,securattr,
                                                 usid, gsid, TRUE);
                        if (pxdesc && ntfs_same_sid(usid, adminsid)) {
                                uid = find_tenant(scx, securattr);
                        } else
                                uid = ntfs_find_user(scx->mapping[MAPUSERS],usid);
#endif
                        if (pxdesc) {
                                /*
                                 *  Create a security id if there were none
                                 * and upgrade option is selected
                                 */
                                if (!test_nino_flag(dir_ni, v3_Extensions)
                                   && (scx->vol->secure_flags
                                     & (1 << SECURITY_ADDSECURIDS))) {
                                        upgrade_secur_desc(scx->vol,
                                                securattr, dir_ni);
                                        /*
                                         * fetch owner and group for cacheing
                                         * if there is a securid
                                         */
                                }
                                if (test_nino_flag(dir_ni, v3_Extensions)) {
                                        enter_cache(scx, dir_ni, uid,
                                                        gid, pxdesc);
                                }
                                if (scx->vol->secure_flags
                                                        & (1 << SECURITY_ACL))
                                        pydesc = ntfs_build_inherited_posix(
                                                pxdesc, mode,
                                                scx->umask, isdir);
                                else
                                        pydesc = ntfs_build_basic_posix(
                                                pxdesc, mode,
                                                scx->umask, isdir);
                                free(pxdesc);
                        }
                        free(securattr);
                }
        }
        return (pydesc);
}

/*
 *              Allocate a security_id for a file being created
 *
 *      Returns zero if not possible (NTFS v3.x required)
 */

le32 ntfs_alloc_securid(struct SECURITY_CONTEXT *scx,
                uid_t uid, gid_t gid, ntfs_inode *dir_ni,
                mode_t mode, BOOL isdir)
{
#if !FORCE_FORMAT_v1x
        const struct CACHED_SECURID *cached;
        struct CACHED_SECURID wanted;
        struct POSIX_SECURITY *pxdesc;
        char *newattr;
        int newattrsz;
        const SID *usid;
        const SID *gsid;
        BIGSID defusid;
        BIGSID defgsid;
        le32 securid;
#endif

        securid = const_cpu_to_le32(0);

#if !FORCE_FORMAT_v1x

        pxdesc = inherit_posix(scx, dir_ni, mode, isdir);
        if (pxdesc) {
                /* check whether target securid is known in cache */

                wanted.uid = uid;
                wanted.gid = gid;
                wanted.dmode = pxdesc->mode & mode & 07777;
                if (isdir) wanted.dmode |= 0x10000;
                wanted.variable = (void*)pxdesc;
                wanted.varsize = sizeof(struct POSIX_SECURITY)
                                + (pxdesc->acccnt + pxdesc->defcnt)*sizeof(struct POSIX_ACE);
                cached = (const struct CACHED_SECURID*)ntfs_fetch_cache(
                                scx->vol->securid_cache, GENERIC(&wanted),
                                (cache_compare)compare);
                        /* quite simple, if we are lucky */
                if (cached)
                        securid = cached->securid;

                        /* not in cache : make sure we can create ids */

                if (!cached && (scx->vol->major_ver >= 3)) {
                        usid = ntfs_find_usid(scx->mapping[MAPUSERS],uid,(SID*)&defusid);
                        gsid = ntfs_find_gsid(scx->mapping[MAPGROUPS],gid,(SID*)&defgsid);
                        if (!usid || !gsid) {
                                ntfs_log_error("File created by an unmapped user/group %d/%d\n",
                                                (int)uid, (int)gid);
                                usid = gsid = adminsid;
                        }
                        newattr = ntfs_build_descr_posix(scx->mapping, pxdesc,
                                        isdir, usid, gsid);
                        if (newattr) {
                                newattrsz = ntfs_attr_size(newattr);
                                securid = setsecurityattr(scx->vol,
                                        (const SECURITY_DESCRIPTOR_RELATIVE*)newattr,
                                        newattrsz);
                                if (securid) {
                                        /* update cache, for subsequent use */
                                        wanted.securid = securid;
                                        ntfs_enter_cache(scx->vol->securid_cache,
                                                        GENERIC(&wanted),
                                                        (cache_compare)compare);
                                }
                                free(newattr);
                        } else {
                                /*
                                 * could not build new security attribute
                                 * errno set by ntfs_build_descr()
                                 */
                        }
                }
        free(pxdesc);
        }
#endif
        return (securid);
}

/*
 *              Apply Posix inheritance to a newly created file
 *      (for NTFS 1.x only : no securid)
 */

int ntfs_set_inherited_posix(struct SECURITY_CONTEXT *scx,
                ntfs_inode *ni, uid_t uid, gid_t gid,
                ntfs_inode *dir_ni, mode_t mode)
{
        struct POSIX_SECURITY *pxdesc;
        char *newattr;
        const SID *usid;
        const SID *gsid;
        BIGSID defusid;
        BIGSID defgsid;
        BOOL isdir;
        int res;

        res = -1;
        isdir = (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) != const_cpu_to_le16(0);
        pxdesc = inherit_posix(scx, dir_ni, mode, isdir);
        if (pxdesc) {
                usid = ntfs_find_usid(scx->mapping[MAPUSERS],uid,(SID*)&defusid);
                gsid = ntfs_find_gsid(scx->mapping[MAPGROUPS],gid,(SID*)&defgsid);
                if (!usid || !gsid) {
                        ntfs_log_error("File created by an unmapped user/group %d/%d\n",
                                        (int)uid, (int)gid);
                        usid = gsid = adminsid;
                }
                newattr = ntfs_build_descr_posix(scx->mapping, pxdesc,
                                        isdir, usid, gsid);
                if (newattr) {
                                /* Adjust Windows read-only flag */
                        res = update_secur_descr(scx->vol, newattr, ni);
                        if (!res && !isdir) {
                                if (mode & S_IWUSR)
                                        ni->flags &= ~FILE_ATTR_READONLY;
                                else
                                        ni->flags |= FILE_ATTR_READONLY;
                        }
#if CACHE_LEGACY_SIZE
                        /* also invalidate legacy cache */
                        if (isdir && !ni->security_id) {
                                struct CACHED_PERMISSIONS_LEGACY legacy;

                                legacy.mft_no = ni->mft_no;
                                legacy.variable = pxdesc;
                                legacy.varsize = sizeof(struct POSIX_SECURITY)
                                        + (pxdesc->acccnt + pxdesc->defcnt)*sizeof(struct POSIX_ACE);
                                ntfs_invalidate_cache(scx->vol->legacy_cache,
                                                GENERIC(&legacy),
                                                (cache_compare)leg_compare,0);
                        }
#endif
                        free(newattr);

                } else {
                        /*
                         * could not build new security attribute
                         * errno set by ntfs_build_descr()
                         */
                }
        }
        return (res);
}

#else

le32 ntfs_alloc_securid(struct SECURITY_CONTEXT *scx,
                uid_t uid, gid_t gid, mode_t mode, BOOL isdir)
{
#if !FORCE_FORMAT_v1x
        const struct CACHED_SECURID *cached;
        struct CACHED_SECURID wanted;
        char *newattr;
        int newattrsz;
        const SID *usid;
        const SID *gsid;
        BIGSID defusid;
        BIGSID defgsid;
        le32 securid;
#endif

        securid = const_cpu_to_le32(0);

#if !FORCE_FORMAT_v1x
                /* check whether target securid is known in cache */

        wanted.uid = uid;
        wanted.gid = gid;
        wanted.dmode = mode & 07777;
        if (isdir) wanted.dmode |= 0x10000;
        wanted.variable = (void*)NULL;
        wanted.varsize = 0;
        cached = (const struct CACHED_SECURID*)ntfs_fetch_cache(
                        scx->vol->securid_cache, GENERIC(&wanted),
                        (cache_compare)compare);
                /* quite simple, if we are lucky */
        if (cached)
                securid = cached->securid;

                /* not in cache : make sure we can create ids */

        if (!cached && (scx->vol->major_ver >= 3)) {
                usid = ntfs_find_usid(scx->mapping[MAPUSERS],uid,(SID*)&defusid);
                gsid = ntfs_find_gsid(scx->mapping[MAPGROUPS],gid,(SID*)&defgsid);
                if (!usid || !gsid) {
                        ntfs_log_error("File created by an unmapped user/group %d/%d\n",
                                        (int)uid, (int)gid);
                        usid = gsid = adminsid;
                }
                newattr = ntfs_build_descr(mode, isdir, usid, gsid);
                if (newattr) {
                        newattrsz = ntfs_attr_size(newattr);
                        securid = setsecurityattr(scx->vol,
                                (const SECURITY_DESCRIPTOR_RELATIVE*)newattr,
                                newattrsz);
                        if (securid) {
                                /* update cache, for subsequent use */
                                wanted.securid = securid;
                                ntfs_enter_cache(scx->vol->securid_cache,
                                                GENERIC(&wanted),
                                                (cache_compare)compare);
                        }
                        free(newattr);
                } else {
                        /*
                         * could not build new security attribute
                         * errno set by ntfs_build_descr()
                         */
                }
        }
#endif
        return (securid);
}

#endif

/*
 *              Update ownership and mode of a file, reusing an existing
 *      security descriptor when possible
 *
 *      Returns zero if successful
 */

#if POSIXACLS
int ntfs_set_owner_mode(struct SECURITY_CONTEXT *scx, ntfs_inode *ni,
                uid_t uid, gid_t gid, mode_t mode,
                struct POSIX_SECURITY *pxdesc)
#else
int ntfs_set_owner_mode(struct SECURITY_CONTEXT *scx, ntfs_inode *ni,
                uid_t uid, gid_t gid, mode_t mode)
#endif
{
        int res;
        const struct CACHED_SECURID *cached;
        struct CACHED_SECURID wanted;
        char *newattr;
        const SID *usid;
        const SID *gsid;
        BIGSID defusid;
        BIGSID defgsid;
        BOOL isdir;

        res = 0;

                /* check whether target securid is known in cache */

        isdir = (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) != const_cpu_to_le16(0);
        wanted.uid = uid;
        wanted.gid = gid;
        wanted.dmode = mode & 07777;
        if (isdir) wanted.dmode |= 0x10000;
#if POSIXACLS
        wanted.variable = (void*)pxdesc;
        if (pxdesc)
                wanted.varsize = sizeof(struct POSIX_SECURITY)
                        + (pxdesc->acccnt + pxdesc->defcnt)*sizeof(struct POSIX_ACE);
        else
                wanted.varsize = 0;
#else
        wanted.variable = (void*)NULL;
        wanted.varsize = 0;
#endif
        if (test_nino_flag(ni, v3_Extensions)) {
                cached = (const struct CACHED_SECURID*)ntfs_fetch_cache(
                                scx->vol->securid_cache, GENERIC(&wanted),
                                (cache_compare)compare);
                        /* quite simple, if we are lucky */
                if (cached) {
                        ni->security_id = cached->securid;
                        NInoSetDirty(ni);
                                /* adjust Windows read-only flag */
                        if (!isdir) {
                                if (mode & S_IWUSR)
                                        ni->flags &= ~FILE_ATTR_READONLY;
                                else
                                        ni->flags |= FILE_ATTR_READONLY;
                                NInoFileNameSetDirty(ni);
                        }
                }
        } else cached = (struct CACHED_SECURID*)NULL;

        if (!cached) {
                        /*
                         * Do not use usid and gsid from former attributes,
                         * but recompute them to get repeatable results
                         * which can be kept in cache.
                         */
                usid = ntfs_find_usid(scx->mapping[MAPUSERS],uid,(SID*)&defusid);
                gsid = ntfs_find_gsid(scx->mapping[MAPGROUPS],gid,(SID*)&defgsid);
                if (!usid || !gsid) {
                        ntfs_log_error("File made owned by an unmapped user/group %d/%d\n",
                                uid, gid);
                        usid = gsid = adminsid;
                }
#if POSIXACLS
                if (pxdesc)
                        newattr = ntfs_build_descr_posix(scx->mapping, pxdesc,
                                         isdir, usid, gsid);
                else
                        newattr = ntfs_build_descr(mode,
                                         isdir, usid, gsid);
#else
                newattr = ntfs_build_descr(mode,
                                         isdir, usid, gsid);
#endif
                if (newattr) {
                        res = update_secur_descr(scx->vol, newattr, ni);
                        if (!res) {
                                /* adjust Windows read-only flag */
                                if (!isdir) {
                                        if (mode & S_IWUSR)
                                                ni->flags &= ~FILE_ATTR_READONLY;
                                        else
                                                ni->flags |= FILE_ATTR_READONLY;
                                        NInoFileNameSetDirty(ni);
                                }
                                /* update cache, for subsequent use */
                                if (test_nino_flag(ni, v3_Extensions)) {
                                        wanted.securid = ni->security_id;
                                        ntfs_enter_cache(scx->vol->securid_cache,
                                                        GENERIC(&wanted),
                                                        (cache_compare)compare);
                                }
#if CACHE_LEGACY_SIZE
                                /* also invalidate legacy cache */
                                if (isdir && !ni->security_id) {
                                        struct CACHED_PERMISSIONS_LEGACY legacy;

                                        legacy.mft_no = ni->mft_no;
#if POSIXACLS
                                        legacy.variable = wanted.variable;
                                        legacy.varsize = wanted.varsize;
#else
                                        legacy.variable = (void*)NULL;
                                        legacy.varsize = 0;
#endif
                                        ntfs_invalidate_cache(scx->vol->legacy_cache,
                                                GENERIC(&legacy),
                                                (cache_compare)leg_compare,0);
                                }
#endif
                        }
                        free(newattr);
                } else {
                        /*
                         * could not build new security attribute
                         * errno set by ntfs_build_descr()
                         */
                        res = -1;
                }
        }
        return (res);
}

/*
 *              Check whether user has ownership rights on a file
 *
 *      Returns TRUE if allowed
 *              if not, errno tells why
 */

BOOL ntfs_allowed_as_owner(struct SECURITY_CONTEXT *scx, ntfs_inode *ni)
{
        const struct CACHED_PERMISSIONS *cached;
        char *oldattr;
        const SID *usid;
        uid_t processuid;
        uid_t uid;
        BOOL gotowner;
        int allowed;

        processuid = scx->uid;
/* TODO : use CAP_FOWNER process capability */
        /*
         * Always allow for root
         * Also always allow if no mapping has been defined
         */
        if (!scx->mapping[MAPUSERS] || !processuid)
                allowed = TRUE;
        else {
                gotowner = FALSE; /* default */
                /* get the owner, either from cache or from old attribute  */
                cached = fetch_cache(scx, ni);
                if (cached) {
                        uid = cached->uid;
                        gotowner = TRUE;
                } else {
                        oldattr = getsecurityattr(scx->vol, ni);
                        if (oldattr) {
#if OWNERFROMACL
                                usid = ntfs_acl_owner(oldattr);
#else
                                const SECURITY_DESCRIPTOR_RELATIVE *phead;

                                phead = (const SECURITY_DESCRIPTOR_RELATIVE*)
                                                                oldattr;
                                usid = (const SID*)&oldattr
                                                [le32_to_cpu(phead->owner)];
#endif
                                uid = ntfs_find_user(scx->mapping[MAPUSERS],
                                                usid);
                                gotowner = TRUE;
                                free(oldattr);
                        }
                }
/* TODO : use CAP_FOWNER process capability */
                if (gotowner
                    && (!processuid || (processuid == uid)))
                        allowed = TRUE;
                else {
                        allowed = FALSE;
                        errno = EPERM;
                }
        }
        return (allowed);
}


#if POSIXACLS

/*
 *              Set a new access or default Posix ACL to a file
 *              (or remove ACL if no input data)
 *      Validity of input data is checked after merging
 *
 *      Returns 0, or -1 if there is a problem which errno describes
 */

int ntfs_set_posix_acl(struct SECURITY_CONTEXT *scx, ntfs_inode *ni,
                        const char *name, const char *value, size_t size,
                        int flags)
{
        const SECURITY_DESCRIPTOR_RELATIVE *phead;
        const struct CACHED_PERMISSIONS *cached;
        char *oldattr;
        uid_t processuid;
        const SID *usid;
        const SID *gsid;
        uid_t uid;
        uid_t gid;
        int res;
        BOOL isdir;
        BOOL deflt;
        BOOL exist;
        int count;
        struct POSIX_SECURITY *oldpxdesc;
        struct POSIX_SECURITY *newpxdesc;

        /* get the current pxsec, either from cache or from old attribute  */
        res = -1;
        deflt = !strcmp(name,"system.posix_acl_default");
        if (size)
                count = (size - sizeof(struct POSIX_ACL)) / sizeof(struct POSIX_ACE);
        else
                count = 0;
        isdir = (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) != const_cpu_to_le16(0);
        newpxdesc = (struct POSIX_SECURITY*)NULL;
        if ((!value
                || (((const struct POSIX_ACL*)value)->version == POSIX_VERSION))
            && (!deflt || isdir || (!size && !value))) {
                cached = fetch_cache(scx, ni);
                if (cached) {
                        uid = cached->uid;
                        gid = cached->gid;
                        oldpxdesc = cached->pxdesc;
                        if (oldpxdesc) {
                                newpxdesc = ntfs_replace_acl(oldpxdesc,
                                                (const struct POSIX_ACL*)value,count,deflt);
                                }
                } else {
                        oldattr = getsecurityattr(scx->vol, ni);
                        if (oldattr) {
                                phead = (const SECURITY_DESCRIPTOR_RELATIVE*)oldattr;
#if OWNERFROMACL
                                usid = ntfs_acl_owner(oldattr);
#else
                                usid = (const SID*)&oldattr[le32_to_cpu(phead->owner)];
#endif
                                gsid = (const SID*)&oldattr[le32_to_cpu(phead->group)];
                                uid = ntfs_find_user(scx->mapping[MAPUSERS],usid);
                                gid = ntfs_find_group(scx->mapping[MAPGROUPS],gsid);
                                oldpxdesc = ntfs_build_permissions_posix(scx->mapping,
                                        oldattr, usid, gsid, isdir);
                                if (oldpxdesc) {
                                        if (deflt)
                                                exist = oldpxdesc->defcnt > 0;
                                        else
                                                exist = oldpxdesc->acccnt > 3;
                                        if ((exist && (flags & XATTR_CREATE))
                                          || (!exist && (flags & XATTR_REPLACE))) {
                                                errno = (exist ? EEXIST : ENODATA);
                                        } else {
                                                newpxdesc = ntfs_replace_acl(oldpxdesc,
                                                        (const struct POSIX_ACL*)value,count,deflt);
                                        }
                                        free(oldpxdesc);
                                }
                                free(oldattr);
                        }
                }
        } else
                errno = EINVAL;

        if (newpxdesc) {
                processuid = scx->uid;
/* TODO : use CAP_FOWNER process capability */
                if (!processuid || (uid == processuid)) {
                                /*
                                 * clear setgid if file group does
                                 * not match process group
                                 */
                        if (processuid && (gid != scx->gid)
                            && !groupmember(scx, scx->uid, gid)) {
                                newpxdesc->mode &= ~S_ISGID;
                        }
                        res = ntfs_set_owner_mode(scx, ni, uid, gid,
                                newpxdesc->mode, newpxdesc);
                } else
                        errno = EPERM;
                free(newpxdesc);
        }
        return (res ? -1 : 0);
}

/*
 *              Remove a default Posix ACL from a file
 *
 *      Returns 0, or -1 if there is a problem which errno describes
 */

int ntfs_remove_posix_acl(struct SECURITY_CONTEXT *scx, ntfs_inode *ni,
                        const char *name)
{
        return (ntfs_set_posix_acl(scx, ni, name,
                        (const char*)NULL, 0, 0));
}

#endif

/*
 *              Set a new NTFS ACL to a file
 *
 *      Returns 0, or -1 if there is a problem
 */

int ntfs_set_ntfs_acl(struct SECURITY_CONTEXT *scx, ntfs_inode *ni,
                        const char *value, size_t size, int flags)
{
        char *attr;
        int res;

        res = -1;
        if ((size > 0)
           && !(flags & XATTR_CREATE)
           && ntfs_valid_descr(value,size)
           && (ntfs_attr_size(value) == size)) {
                        /* need copying in order to write */
                attr = (char*)ntfs_malloc(size);
                if (attr) {
                        memcpy(attr,value,size);
                        res = update_secur_descr(scx->vol, attr, ni);
                        /*
                         * No need to invalidate standard caches :
                         * the relation between a securid and
                         * the associated protection is unchanged,
                         * only the relation between a file and
                         * its securid and protection is changed.
                         */
#if CACHE_LEGACY_SIZE
                        /*
                         * we must however invalidate the legacy
                         * cache, which is based on inode numbers.
                         * For safety, invalidate even if updating
                         * failed.
                         */
                        if ((ni->mrec->flags & MFT_RECORD_IS_DIRECTORY)
                           && !ni->security_id) {
                                struct CACHED_PERMISSIONS_LEGACY legacy;

                                legacy.mft_no = ni->mft_no;
                                legacy.variable = (char*)NULL;
                                legacy.varsize = 0;
                                ntfs_invalidate_cache(scx->vol->legacy_cache,
                                        GENERIC(&legacy),
                                        (cache_compare)leg_compare,0);
                        }
#endif
                        free(attr);
                } else
                        errno = ENOMEM;
        } else
                errno = EINVAL;
        return (res ? -1 : 0);
}


/*
 *              Set new permissions to a file
 *      Checks user mapping has been defined before request for setting
 *
 *      rejected if request is not originated by owner or root
 *
 *      returns 0 on success
 *              -1 on failure, with errno = EIO
 */

int ntfs_set_mode(struct SECURITY_CONTEXT *scx, ntfs_inode *ni, mode_t mode)
{
        const SECURITY_DESCRIPTOR_RELATIVE *phead;
        const struct CACHED_PERMISSIONS *cached;
        char *oldattr;
        const SID *usid;
        const SID *gsid;
        uid_t processuid;
        uid_t uid;
        uid_t gid;
        int res;
#if POSIXACLS
        BOOL isdir;
        int pxsize;
        const struct POSIX_SECURITY *oldpxdesc;
        struct POSIX_SECURITY *newpxdesc = (struct POSIX_SECURITY*)NULL;
#endif

        /* get the current owner, either from cache or from old attribute  */
        res = 0;
        cached = fetch_cache(scx, ni);
        if (cached) {
                uid = cached->uid;
                gid = cached->gid;
#if POSIXACLS
                oldpxdesc = cached->pxdesc;
                if (oldpxdesc) {
                                /* must copy before merging */
                        pxsize = sizeof(struct POSIX_SECURITY)
                                + (oldpxdesc->acccnt + oldpxdesc->defcnt)*sizeof(struct POSIX_ACE);
                        newpxdesc = (struct POSIX_SECURITY*)malloc(pxsize);
                        if (newpxdesc) {
                                memcpy(newpxdesc, oldpxdesc, pxsize);
                                if (ntfs_merge_mode_posix(newpxdesc, mode))
                                        res = -1;
                        } else
                                res = -1;
                } else
                        newpxdesc = (struct POSIX_SECURITY*)NULL;
#endif
        } else {
                oldattr = getsecurityattr(scx->vol, ni);
                if (oldattr) {
                        phead = (const SECURITY_DESCRIPTOR_RELATIVE*)oldattr;
#if OWNERFROMACL
                        usid = ntfs_acl_owner(oldattr);
#else
                        usid = (const SID*)&oldattr[le32_to_cpu(phead->owner)];
#endif
                        gsid = (const SID*)&oldattr[le32_to_cpu(phead->group)];
                        uid = ntfs_find_user(scx->mapping[MAPUSERS],usid);
                        gid = ntfs_find_group(scx->mapping[MAPGROUPS],gsid);
#if POSIXACLS
                        isdir = (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) != const_cpu_to_le16(0);
                        newpxdesc = ntfs_build_permissions_posix(scx->mapping,
                                oldattr, usid, gsid, isdir);
                        if (!newpxdesc || ntfs_merge_mode_posix(newpxdesc, mode))
                                res = -1;
#endif
                        free(oldattr);
                } else
                        res = -1;
        }

        if (!res) {
                processuid = scx->uid;
/* TODO : use CAP_FOWNER process capability */
                if (!processuid || (uid == processuid)) {
                                /*
                                 * clear setgid if file group does
                                 * not match process group
                                 */
                        if (processuid && (gid != scx->gid)
                            && !groupmember(scx, scx->uid, gid))
                                mode &= ~S_ISGID;
#if POSIXACLS
                        if (newpxdesc) {
                                newpxdesc->mode = mode;
                                res = ntfs_set_owner_mode(scx, ni, uid, gid,
                                        mode, newpxdesc);
                        } else
                                res = ntfs_set_owner_mode(scx, ni, uid, gid,
                                        mode, newpxdesc);
#else
                        res = ntfs_set_owner_mode(scx, ni, uid, gid, mode);
#endif
                } else {
                        errno = EPERM;
                        res = -1;       /* neither owner nor root */
                }
        } else {
                /*
                 * Should not happen : a default descriptor is generated
                 * by getsecurityattr() when there are none
                 */
                ntfs_log_error("File has no security descriptor\n");
                res = -1;
                errno = EIO;
        }
#if POSIXACLS
        if (newpxdesc) free(newpxdesc);
#endif
        return (res ? -1 : 0);
}

/*
 *      Create a default security descriptor for files whose descriptor
 *      cannot be inherited
 */

int ntfs_sd_add_everyone(ntfs_inode *ni)
{
        /* JPA SECURITY_DESCRIPTOR_ATTR *sd; */
        SECURITY_DESCRIPTOR_RELATIVE *sd;
        ACL *acl;
        ACCESS_ALLOWED_ACE *ace;
        SID *sid;
        int ret, sd_len;

        /* Create SECURITY_DESCRIPTOR attribute (everyone has full access). */
        /*
         * Calculate security descriptor length. We have 2 sub-authorities in
         * owner and group SIDs, but structure SID contain only one, so add
         * 4 bytes to every SID.
         */
        sd_len = sizeof(SECURITY_DESCRIPTOR_ATTR) + 2 * (sizeof(SID) + 4) +
                sizeof(ACL) + sizeof(ACCESS_ALLOWED_ACE);
        sd = (SECURITY_DESCRIPTOR_RELATIVE*)ntfs_calloc(sd_len);
        if (!sd)
                return -1;

        sd->revision = SECURITY_DESCRIPTOR_REVISION;
        sd->control = SE_DACL_PRESENT | SE_SELF_RELATIVE;

        sid = (SID*)((u8*)sd + sizeof(SECURITY_DESCRIPTOR_ATTR));
        sid->revision = SID_REVISION;
        sid->sub_authority_count = 2;
        sid->sub_authority[0] = const_cpu_to_le32(SECURITY_BUILTIN_DOMAIN_RID);
        sid->sub_authority[1] = const_cpu_to_le32(DOMAIN_ALIAS_RID_ADMINS);
        sid->identifier_authority.value[5] = 5;
        sd->owner = cpu_to_le32((u8*)sid - (u8*)sd);

        sid = (SID*)((u8*)sid + sizeof(SID) + 4);
        sid->revision = SID_REVISION;
        sid->sub_authority_count = 2;
        sid->sub_authority[0] = const_cpu_to_le32(SECURITY_BUILTIN_DOMAIN_RID);
        sid->sub_authority[1] = const_cpu_to_le32(DOMAIN_ALIAS_RID_ADMINS);
        sid->identifier_authority.value[5] = 5;
        sd->group = cpu_to_le32((u8*)sid - (u8*)sd);

        acl = (ACL*)((u8*)sid + sizeof(SID) + 4);
        acl->revision = ACL_REVISION;
        acl->size = const_cpu_to_le16(sizeof(ACL) + sizeof(ACCESS_ALLOWED_ACE));
        acl->ace_count = const_cpu_to_le16(1);
        sd->dacl = cpu_to_le32((u8*)acl - (u8*)sd);

        ace = (ACCESS_ALLOWED_ACE*)((u8*)acl + sizeof(ACL));
        ace->type = ACCESS_ALLOWED_ACE_TYPE;
        ace->flags = OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE;
        ace->size = const_cpu_to_le16(sizeof(ACCESS_ALLOWED_ACE));
        ace->mask = const_cpu_to_le32(0x1f01ff); /* FIXME */
        ace->sid.revision = SID_REVISION;
        ace->sid.sub_authority_count = 1;
        ace->sid.sub_authority[0] = const_cpu_to_le32(0);
        ace->sid.identifier_authority.value[5] = 1;

        ret = ntfs_attr_add(ni, AT_SECURITY_DESCRIPTOR, AT_UNNAMED, 0, (u8*)sd,
                            sd_len);
        if (ret)
                ntfs_log_perror("Failed to add initial SECURITY_DESCRIPTOR");

        free(sd);
        return ret;
}

/*
 *              Check whether user can access a file in a specific way
 *
 *      Returns 1 if access is allowed, including user is root or no
 *                user mapping defined
 *              2 if sticky and accesstype is S_IWRITE + S_IEXEC + S_ISVTX
 *              0 and sets errno if there is a problem or if access
 *                is not allowed
 *
 *      This is used for Posix ACL and checking creation of DOS file names
 */

int ntfs_allowed_access(struct SECURITY_CONTEXT *scx,
                ntfs_inode *ni,
                int accesstype) /* access type required (S_Ixxx values) */
{
        int perm;
        int res;
        int allow;
        struct stat stbuf;

        /*
         * Always allow for root unless execution is requested.
         * (was checked by fuse until kernel 2.6.29)
         * Also always allow if no mapping has been defined
         */
        if (!scx->mapping[MAPUSERS]
            || (!scx->uid
                && (!(accesstype & S_IEXEC)
                    || (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY))))
                allow = 1;
        else {
                perm = ntfs_get_perm(scx, ni, accesstype);
                if (perm >= 0) {
                        res = EACCES;
                        switch (accesstype) {
                        case S_IEXEC:
                                allow = (perm & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0;
                                break;
                        case S_IWRITE:
                                allow = (perm & (S_IWUSR | S_IWGRP | S_IWOTH)) != 0;
                                break;
                        case S_IWRITE + S_IEXEC:
                                allow = ((perm & (S_IWUSR | S_IWGRP | S_IWOTH)) != 0)
                                    && ((perm & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0);
                                break;
                        case S_IREAD:
                                allow = (perm & (S_IRUSR | S_IRGRP | S_IROTH)) != 0;
                                break;
                        case S_IREAD + S_IEXEC:
                                allow = ((perm & (S_IRUSR | S_IRGRP | S_IROTH)) != 0)
                                    && ((perm & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0);
                                break;
                        case S_IREAD + S_IWRITE:
                                allow = ((perm & (S_IRUSR | S_IRGRP | S_IROTH)) != 0)
                                    && ((perm & (S_IWUSR | S_IWGRP | S_IWOTH)) != 0);
                                break;
                        case S_IWRITE + S_IEXEC + S_ISVTX:
                                if (perm & S_ISVTX) {
                                        if ((ntfs_get_owner_mode(scx,ni,&stbuf) >= 0)
                                            && (stbuf.st_uid == scx->uid))
                                                allow = 1;
                                        else
                                                allow = 2;
                                } else
                                        allow = ((perm & (S_IWUSR | S_IWGRP | S_IWOTH)) != 0)
                                            && ((perm & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0);
                                break;
                        case S_IREAD + S_IWRITE + S_IEXEC:
                                allow = ((perm & (S_IRUSR | S_IRGRP | S_IROTH)) != 0)
                                    && ((perm & (S_IWUSR | S_IWGRP | S_IWOTH)) != 0)
                                    && ((perm & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0);
                                break;
                        default :
                                res = EINVAL;
                                allow = 0;
                                break;
                        }
                        if (!allow)
                                errno = res;
                } else
                        allow = 0;
        }
        return (allow);
}

/*
 *              Check whether user can create a file (or directory)
 *
 *      Returns TRUE if access is allowed,
 *      Also returns the gid and dsetgid applicable to the created file
 */

int ntfs_allowed_create(struct SECURITY_CONTEXT *scx,
                ntfs_inode *dir_ni, gid_t *pgid, mode_t *pdsetgid)
{
        int perm;
        int res;
        int allow;
        struct stat stbuf;

        /*
         * Always allow for root.
         * Also always allow if no mapping has been defined
         */
        if (!scx->mapping[MAPUSERS])
                perm = 0777;
        else
                perm = ntfs_get_perm(scx, dir_ni, S_IWRITE + S_IEXEC);
        if (!scx->mapping[MAPUSERS]
            || !scx->uid) {
                allow = 1;
        } else {
                perm = ntfs_get_perm(scx, dir_ni, S_IWRITE + S_IEXEC);
                if (perm >= 0) {
                        res = EACCES;
                        allow = ((perm & (S_IWUSR | S_IWGRP | S_IWOTH)) != 0)
                                    && ((perm & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0);
                        if (!allow)
                                errno = res;
                } else
                        allow = 0;
        }
        *pgid = scx->gid;
        *pdsetgid = 0;
                /* return directory group if S_ISGID is set */
        if (allow && (perm & S_ISGID)) {
                if (ntfs_get_owner_mode(scx, dir_ni, &stbuf) >= 0) {
                        *pdsetgid = stbuf.st_mode & S_ISGID;
                        if (perm & S_ISGID)
                                *pgid = stbuf.st_gid;
                }
        }
        return (allow);
}

#if 0 /* not needed any more */

/*
 *              Check whether user can access the parent directory
 *      of a file in a specific way
 *
 *      Returns true if access is allowed, including user is root and
 *              no user mapping defined
 *
 *      Sets errno if there is a problem or if not allowed
 *
 *      This is used for Posix ACL and checking creation of DOS file names
 */

BOOL old_ntfs_allowed_dir_access(struct SECURITY_CONTEXT *scx,
                const char *path, int accesstype)
{
        int allow;
        char *dirpath;
        char *name;
        ntfs_inode *ni;
        ntfs_inode *dir_ni;
        struct stat stbuf;

        allow = 0;
        dirpath = strdup(path);
        if (dirpath) {
                /* the root of file system is seen as a parent of itself */
                /* is that correct ? */
                name = strrchr(dirpath, '/');
                *name = 0;
                dir_ni = ntfs_pathname_to_inode(scx->vol, NULL, dirpath);
                if (dir_ni) {
                        allow = ntfs_allowed_access(scx,
                                 dir_ni, accesstype);
                        ntfs_inode_close(dir_ni);
                                /*
                                 * for an not-owned sticky directory, have to
                                 * check whether file itself is owned
                                 */
                        if ((accesstype == (S_IWRITE + S_IEXEC + S_ISVTX))
                           && (allow == 2)) {
                                ni = ntfs_pathname_to_inode(scx->vol, NULL,
                                         path);
                                allow = FALSE;
                                if (ni) {
                                        allow = (ntfs_get_owner_mode(scx,ni,&stbuf) >= 0)
                                                && (stbuf.st_uid == scx->uid);
                                ntfs_inode_close(ni);
                                }
                        }
                }
                free(dirpath);
        }
        return (allow);         /* errno is set if not allowed */
}

#endif

/*
 *              Define a new owner/group to a file
 *
 *      returns zero if successful
 */

int ntfs_set_owner(struct SECURITY_CONTEXT *scx, ntfs_inode *ni,
                        uid_t uid, gid_t gid)
{
        const SECURITY_DESCRIPTOR_RELATIVE *phead;
        const struct CACHED_PERMISSIONS *cached;
        char *oldattr;
        const SID *usid;
        const SID *gsid;
        uid_t fileuid;
        uid_t filegid;
        mode_t mode;
        int perm;
        BOOL isdir;
        int res;
#if POSIXACLS
        struct POSIX_SECURITY *pxdesc;
        BOOL pxdescbuilt = FALSE;
#endif

        res = 0;
        /* get the current owner and mode from cache or security attributes */
        oldattr = (char*)NULL;
        cached = fetch_cache(scx,ni);
        if (cached) {
                fileuid = cached->uid;
                filegid = cached->gid;
                mode = cached->mode;
#if POSIXACLS
                pxdesc = cached->pxdesc;
                if (!pxdesc)
                        res = -1;
#endif
        } else {
                fileuid = 0;
                filegid = 0;
                mode = 0;
                oldattr = getsecurityattr(scx->vol, ni);
                if (oldattr) {
                        isdir = (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY)
                                != const_cpu_to_le16(0);
                        phead = (const SECURITY_DESCRIPTOR_RELATIVE*)
                                oldattr;
                        gsid = (const SID*)
                                &oldattr[le32_to_cpu(phead->group)];
#if OWNERFROMACL
                        usid = ntfs_acl_owner(oldattr);
#else
                        usid = (const SID*)
                                &oldattr[le32_to_cpu(phead->owner)];
#endif
#if POSIXACLS
                        pxdesc = ntfs_build_permissions_posix(scx->mapping, oldattr,
                                        usid, gsid, isdir);
                        if (pxdesc) {
                                pxdescbuilt = TRUE;
                                fileuid = ntfs_find_user(scx->mapping[MAPUSERS],usid);
                                filegid = ntfs_find_group(scx->mapping[MAPGROUPS],gsid);
                                mode = perm = pxdesc->mode;
                        } else
                                res = -1;
#else
                        mode = perm = ntfs_build_permissions(oldattr,
                                         usid, gsid, isdir);
                        if (perm >= 0) {
                                fileuid = ntfs_find_user(scx->mapping[MAPUSERS],usid);
                                filegid = ntfs_find_group(scx->mapping[MAPGROUPS],gsid);
                        } else
                                res = -1;
#endif
                        free(oldattr);
                } else
                        res = -1;
        }
        if (!res) {
                /* check requested by root */
                /* or chgrp requested by owner to an owned group */
                if (!scx->uid
                   || ((((int)uid < 0) || (uid == fileuid))
                      && ((gid == scx->gid) || groupmember(scx, scx->uid, gid))
                      && (fileuid == scx->uid))) {
                        /* replace by the new usid and gsid */
                        /* or reuse old gid and sid for cacheing */
                        if ((int)uid < 0)
                                uid = fileuid;
                        if ((int)gid < 0)
                                gid = filegid;
#if !defined(__sun) || !defined (__SVR4)
                        /* clear setuid and setgid if owner has changed */
                        /* unless request originated by root */
                        if (uid && (fileuid != uid))
                                mode &= 01777;
#endif
#if POSIXACLS
                        res = ntfs_set_owner_mode(scx, ni, uid, gid,
                                mode, pxdesc);
#else
                        res = ntfs_set_owner_mode(scx, ni, uid, gid, mode);
#endif
                } else {
                        res = -1;       /* neither owner nor root */
                        errno = EPERM;
                }
#if POSIXACLS
                if (pxdescbuilt)
                        free(pxdesc);
#endif
        } else {
                /*
                 * Should not happen : a default descriptor is generated
                 * by getsecurityattr() when there are none
                 */
                ntfs_log_error("File has no security descriptor\n");
                res = -1;
                errno = EIO;
        }
        return (res ? -1 : 0);
}

/*
 *              Define new owner/group and mode to a file
 *
 *      returns zero if successful
 */

int ntfs_set_ownmod(struct SECURITY_CONTEXT *scx, ntfs_inode *ni,
                        uid_t uid, gid_t gid, const mode_t mode)
{
        const struct CACHED_PERMISSIONS *cached;
        char *oldattr;
        uid_t fileuid;
        uid_t filegid;
        int res;
#if POSIXACLS
        const SECURITY_DESCRIPTOR_RELATIVE *phead;
        const SID *usid;
        const SID *gsid;
        BOOL isdir;
        const struct POSIX_SECURITY *oldpxdesc;
        struct POSIX_SECURITY *newpxdesc = (struct POSIX_SECURITY*)NULL;
        int pxsize;
#endif

        res = 0;
        /* get the current owner and mode from cache or security attributes */
        oldattr = (char*)NULL;
        cached = fetch_cache(scx,ni);
        if (cached) {
                fileuid = cached->uid;
                filegid = cached->gid;
#if POSIXACLS
                oldpxdesc = cached->pxdesc;
                if (oldpxdesc) {
                                /* must copy before merging */
                        pxsize = sizeof(struct POSIX_SECURITY)
                                + (oldpxdesc->acccnt + oldpxdesc->defcnt)*sizeof(struct POSIX_ACE);
                        newpxdesc = (struct POSIX_SECURITY*)malloc(pxsize);
                        if (newpxdesc) {
                                memcpy(newpxdesc, oldpxdesc, pxsize);
                                if (ntfs_merge_mode_posix(newpxdesc, mode))
                                        res = -1;
                        } else
                                res = -1;
                }
#endif
        } else {
                fileuid = 0;
                filegid = 0;
                oldattr = getsecurityattr(scx->vol, ni);
                if (oldattr) {
#if POSIXACLS
                        isdir = (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY)
                                != const_cpu_to_le16(0);
                        phead = (const SECURITY_DESCRIPTOR_RELATIVE*)
                                oldattr;
                        gsid = (const SID*)
                                &oldattr[le32_to_cpu(phead->group)];
#if OWNERFROMACL
                        usid = ntfs_acl_owner(oldattr);
#else
                        usid = (const SID*)
                                &oldattr[le32_to_cpu(phead->owner)];
#endif
                        newpxdesc = ntfs_build_permissions_posix(scx->mapping, oldattr,
                                        usid, gsid, isdir);
                        if (!newpxdesc || ntfs_merge_mode_posix(newpxdesc, mode))
                                res = -1;
                        else {
                                fileuid = ntfs_find_user(scx->mapping[MAPUSERS],usid);
                                filegid = ntfs_find_group(scx->mapping[MAPGROUPS],gsid);
                        }
#endif
                        free(oldattr);
                } else
                        res = -1;
        }
        if (!res) {
                /* check requested by root */
                /* or chgrp requested by owner to an owned group */
                if (!scx->uid
                   || ((((int)uid < 0) || (uid == fileuid))
                      && ((gid == scx->gid) || groupmember(scx, scx->uid, gid))
                      && (fileuid == scx->uid))) {
                        /* replace by the new usid and gsid */
                        /* or reuse old gid and sid for cacheing */
                        if ((int)uid < 0)
                                uid = fileuid;
                        if ((int)gid < 0)
                                gid = filegid;
#if POSIXACLS
                        res = ntfs_set_owner_mode(scx, ni, uid, gid,
                                mode, newpxdesc);
#else
                        res = ntfs_set_owner_mode(scx, ni, uid, gid, mode);
#endif
                } else {
                        res = -1;       /* neither owner nor root */
                        errno = EPERM;
                }
        } else {
                /*
                 * Should not happen : a default descriptor is generated
                 * by getsecurityattr() when there are none
                 */
                ntfs_log_error("File has no security descriptor\n");
                res = -1;
                errno = EIO;
        }
#if POSIXACLS
        free(newpxdesc);
#endif
        return (res ? -1 : 0);
}

/*
 *              Build a security id for a descriptor inherited from
 *      parent directory the Windows way
 */

static le32 build_inherited_id(struct SECURITY_CONTEXT *scx,
                        const char *parentattr, BOOL fordir)
{
        const SECURITY_DESCRIPTOR_RELATIVE *pphead;
        const ACL *ppacl;
        const SID *usid;
        const SID *gsid;
        BIGSID defusid;
        BIGSID defgsid;
        int offpacl;
        int offgroup;
        SECURITY_DESCRIPTOR_RELATIVE *pnhead;
        ACL *pnacl;
        int parentattrsz;
        char *newattr;
        int newattrsz;
        int aclsz;
        int usidsz;
        int gsidsz;
        int pos;
        le32 securid;

        parentattrsz = ntfs_attr_size(parentattr);
        pphead = (const SECURITY_DESCRIPTOR_RELATIVE*)parentattr;
        if (scx->mapping[MAPUSERS]) {
                usid = ntfs_find_usid(scx->mapping[MAPUSERS], scx->uid, (SID*)&defusid);
                gsid = ntfs_find_gsid(scx->mapping[MAPGROUPS], scx->gid, (SID*)&defgsid);
#if OWNERFROMACL
                        /* Get approximation of parent owner when cannot map */
                if (!gsid)
                        gsid = adminsid;
                if (!usid) {
                        usid = ntfs_acl_owner(parentattr);
                        if (!ntfs_is_user_sid(gsid))
                                gsid = usid;
                }
#else
                        /* Define owner as root when cannot map */
                if (!usid)
                        usid = adminsid;
                if (!gsid)
                        gsid = adminsid;
#endif
        } else {
                /*
                 * If there is no user mapping and this is not a root
                 * user, we have to get owner and group from somewhere,
                 * and the parent directory has to contribute.
                 * Windows never has to do that, because it can always
                 * rely on a user mapping
                 */
                if (!scx->uid)
                        usid = adminsid;
                else {
#if OWNERFROMACL
                        usid = ntfs_acl_owner(parentattr);
#else
                        int offowner;

                        offowner = le32_to_cpu(pphead->owner);
                        usid = (const SID*)&parentattr[offowner];
#endif
                }
                if (!scx->gid)
                        gsid = adminsid;
                else {
                        offgroup = le32_to_cpu(pphead->group);
                        gsid = (const SID*)&parentattr[offgroup];
                }
        }
                /*
                 * new attribute is smaller than parent's
                 * except for differences in SIDs which appear in
                 * owner, group and possible grants and denials in
                 * generic creator-owner and creator-group ACEs.
                 * For directories, an ACE may be duplicated for
                 * access and inheritance, so we double the count.
                 */
        usidsz = ntfs_sid_size(usid);
        gsidsz = ntfs_sid_size(gsid);
        newattrsz = parentattrsz + 3*usidsz + 3*gsidsz;
        if (fordir)
                newattrsz *= 2;
        newattr = (char*)ntfs_malloc(newattrsz);
        if (newattr) {
                pnhead = (SECURITY_DESCRIPTOR_RELATIVE*)newattr;
                pnhead->revision = SECURITY_DESCRIPTOR_REVISION;
                pnhead->alignment = 0;
                pnhead->control = (pphead->control
                        & (SE_DACL_AUTO_INHERITED | SE_SACL_AUTO_INHERITED))
                                | SE_SELF_RELATIVE;
                pos = sizeof(SECURITY_DESCRIPTOR_RELATIVE);
                        /*
                         * locate and inherit DACL
                         * do not test SE_DACL_PRESENT (wrong for "DR Watson")
                         */
                pnhead->dacl = const_cpu_to_le32(0);
                if (pphead->dacl) {
                        offpacl = le32_to_cpu(pphead->dacl);
                        ppacl = (const ACL*)&parentattr[offpacl];
                        pnacl = (ACL*)&newattr[pos];
                        aclsz = ntfs_inherit_acl(ppacl, pnacl, usid, gsid,
                                fordir, pphead->control
                                        & SE_DACL_AUTO_INHERITED);
                        if (aclsz) {
                                pnhead->dacl = cpu_to_le32(pos);
                                pos += aclsz;
                                pnhead->control |= SE_DACL_PRESENT;
                        }
                }
                        /*
                         * locate and inherit SACL
                         */
                pnhead->sacl = const_cpu_to_le32(0);
                if (pphead->sacl) {
                        offpacl = le32_to_cpu(pphead->sacl);
                        ppacl = (const ACL*)&parentattr[offpacl];
                        pnacl = (ACL*)&newattr[pos];
                        aclsz = ntfs_inherit_acl(ppacl, pnacl, usid, gsid,
                                fordir, pphead->control
                                        & SE_SACL_AUTO_INHERITED);
                        if (aclsz) {
                                pnhead->sacl = cpu_to_le32(pos);
                                pos += aclsz;
                                pnhead->control |= SE_SACL_PRESENT;
                        }
                }
                        /*
                         * inherit or redefine owner
                         */
                memcpy(&newattr[pos],usid,usidsz);
                pnhead->owner = cpu_to_le32(pos);
                pos += usidsz;
                        /*
                         * inherit or redefine group
                         */
                memcpy(&newattr[pos],gsid,gsidsz);
                pnhead->group = cpu_to_le32(pos);
                pos += gsidsz;
                securid = setsecurityattr(scx->vol,
                        (SECURITY_DESCRIPTOR_RELATIVE*)newattr, pos);
                free(newattr);
        } else
                securid = const_cpu_to_le32(0);
        return (securid);
}

/*
 *              Get an inherited security id
 *
 *      For Windows compatibility, the normal initial permission setting
 *      may be inherited from the parent directory instead of being
 *      defined by the creation arguments.
 *
 *      The following creates an inherited id for that purpose.
 *
 *      Note : the owner and group of parent directory are also
 *      inherited (which is not the case on Windows) if no user mapping
 *      is defined.
 *
 *      Returns the inherited id, or zero if not possible (eg on NTFS 1.x)
 */

le32 ntfs_inherited_id(struct SECURITY_CONTEXT *scx,
                        ntfs_inode *dir_ni, BOOL fordir)
{
        struct CACHED_PERMISSIONS *cached;
        char *parentattr;
        le32 securid;

        securid = const_cpu_to_le32(0);
        cached = (struct CACHED_PERMISSIONS*)NULL;
                /*
                 * Try to get inherited id from cache, possible when
                 * the current process owns the parent directory
                 */
        if (test_nino_flag(dir_ni, v3_Extensions)
                        && dir_ni->security_id) {
                cached = fetch_cache(scx, dir_ni);
                if (cached
                    && (cached->uid == scx->uid) && (cached->gid == scx->gid))
                        securid = (fordir ? cached->inh_dirid
                                        : cached->inh_fileid);
        }
                /*
                 * Not cached or not available in cache, compute it all
                 * Note : if parent directory has no id, it is not cacheable
                 */
        if (!securid) {
                parentattr = getsecurityattr(scx->vol, dir_ni);
                if (parentattr) {
                        securid = build_inherited_id(scx,
                                                parentattr, fordir);
                        free(parentattr);
                        /*
                         * Store the result into cache for further use
                         * if the current process owns the parent directory
                         */
                        if (securid) {
                                cached = fetch_cache(scx, dir_ni);
                                if (cached
                                    && (cached->uid == scx->uid)
                                    && (cached->gid == scx->gid)) {
                                        if (fordir)
                                                cached->inh_dirid = securid;
                                        else
                                                cached->inh_fileid = securid;
                                }
                        }
                }
        }
        return (securid);
}

/*
 *              Link a group to a member of group
 *
 *      Returns 0 if OK, -1 (and errno set) if error
 */

static int link_single_group(struct MAPPING *usermapping, struct passwd *user,
                        gid_t gid)
{
        struct group *group;
        char **grmem;
        int grcnt;
        gid_t *groups;
        int res;

        res = 0;
        group = getgrgid(gid);
        if (group && group->gr_mem) {
                grcnt = usermapping->grcnt;
                groups = usermapping->groups;
                grmem = group->gr_mem;
                while (*grmem && strcmp(user->pw_name, *grmem))
                        grmem++;
                if (*grmem) {
                        if (!grcnt)
                                groups = (gid_t*)malloc(sizeof(gid_t));
                        else
                                groups = (gid_t*)realloc(groups,
                                        (grcnt+1)*sizeof(gid_t));
                        if (groups)
                                groups[grcnt++] = gid;
                        else {
                                res = -1;
                                errno = ENOMEM;
                        }
                }
                usermapping->grcnt = grcnt;
                usermapping->groups = groups;
        }
        return (res);
}


/*
 *              Statically link group to users
 *      This is based on groups defined in /etc/group and does not take
 *      the groups dynamically set by setgroups() nor any changes in
 *      /etc/group into account
 *
 *      Only mapped groups and root group are linked to mapped users
 *
 *      Returns 0 if OK, -1 (and errno set) if error
 *
 */

static int link_group_members(struct SECURITY_CONTEXT *scx)
{
        struct MAPPING *usermapping;
        struct MAPPING *groupmapping;
        struct passwd *user;
        int res;

        res = 0;
        for (usermapping=scx->mapping[MAPUSERS]; usermapping && !res;
                        usermapping=usermapping->next) {
                usermapping->grcnt = 0;
                usermapping->groups = (gid_t*)NULL;
                user = getpwuid(usermapping->xid);
                if (user && user->pw_name) {
                        for (groupmapping=scx->mapping[MAPGROUPS];
                                        groupmapping && !res;
                                        groupmapping=groupmapping->next) {
                                if (link_single_group(usermapping, user,
                                    groupmapping->xid))
                                        res = -1;
                                }
                        if (!res && link_single_group(usermapping,
                                         user, (gid_t)0))
                                res = -1;
                }
        }
        return (res);
}

/*
 *              Apply default single user mapping
 *      returns zero if successful
 */

static int ntfs_do_default_mapping(struct SECURITY_CONTEXT *scx,
                         uid_t uid, gid_t gid, const SID *usid)
{
        struct MAPPING *usermapping;
        struct MAPPING *groupmapping;
        SID *sid;
        int sidsz;
        int res;

        res = -1;
        sidsz = ntfs_sid_size(usid);
        sid = (SID*)ntfs_malloc(sidsz);
        if (sid) {
                memcpy(sid,usid,sidsz);
                usermapping = (struct MAPPING*)ntfs_malloc(sizeof(struct MAPPING));
                if (usermapping) {
                        groupmapping = (struct MAPPING*)ntfs_malloc(sizeof(struct MAPPING));
                        if (groupmapping) {
                                usermapping->sid = sid;
                                usermapping->xid = uid;
                                usermapping->next = (struct MAPPING*)NULL;
                                groupmapping->sid = sid;
                                groupmapping->xid = gid;
                                groupmapping->next = (struct MAPPING*)NULL;
                                scx->mapping[MAPUSERS] = usermapping;
                                scx->mapping[MAPGROUPS] = groupmapping;
                                res = 0;
                        }
                }
        }
        return (res);
}

/*
 *              Make sure there are no ambiguous mapping
 *      Ambiguous mapping may lead to undesired configurations and
 *      we had rather be safe until the consequences are understood
 */

#if 0 /* not activated for now */

static BOOL check_mapping(const struct MAPPING *usermapping,
                const struct MAPPING *groupmapping)
{
        const struct MAPPING *mapping1;
        const struct MAPPING *mapping2;
        BOOL ambiguous;

        ambiguous = FALSE;
        for (mapping1=usermapping; mapping1; mapping1=mapping1->next)
                for (mapping2=mapping1->next; mapping2; mapping1=mapping2->next)
                        if (ntfs_same_sid(mapping1->sid,mapping2->sid)) {
                                if (mapping1->xid != mapping2->xid)
                                        ambiguous = TRUE;
                        } else {
                                if (mapping1->xid == mapping2->xid)
                                        ambiguous = TRUE;
                        }
        for (mapping1=groupmapping; mapping1; mapping1=mapping1->next)
                for (mapping2=mapping1->next; mapping2; mapping1=mapping2->next)
                        if (ntfs_same_sid(mapping1->sid,mapping2->sid)) {
                                if (mapping1->xid != mapping2->xid)
                                        ambiguous = TRUE;
                        } else {
                                if (mapping1->xid == mapping2->xid)
                                        ambiguous = TRUE;
                        }
        return (ambiguous);
}

#endif

#if 0 /* not used any more */

/*
 *              Try and apply default single user mapping
 *      returns zero if successful
 */

static int ntfs_default_mapping(struct SECURITY_CONTEXT *scx)
{
        const SECURITY_DESCRIPTOR_RELATIVE *phead;
        ntfs_inode *ni;
        char *securattr;
        const SID *usid;
        int res;

        res = -1;
        ni = ntfs_pathname_to_inode(scx->vol, NULL, "/.");
        if (ni) {
                securattr = getsecurityattr(scx->vol, ni);
                if (securattr) {
                        phead = (const SECURITY_DESCRIPTOR_RELATIVE*)securattr;
                        usid = (SID*)&securattr[le32_to_cpu(phead->owner)];
                        if (ntfs_is_user_sid(usid))
                                res = ntfs_do_default_mapping(scx,
                                                scx->uid, scx->gid, usid);
                        free(securattr);
                }
                ntfs_inode_close(ni);
        }
        return (res);
}

#endif

/*
 *              Basic read from a user mapping file on another volume
 */

static int basicread(void *fileid, char *buf, size_t size, off_t offs __attribute__((unused)))
{
        return (read(*(int*)fileid, buf, size));
}


/*
 *              Read from a user mapping file on current NTFS partition
 */

static int localread(void *fileid, char *buf, size_t size, off_t offs)
{
        return (ntfs_attr_data_read((ntfs_inode*)fileid,
                        AT_UNNAMED, 0, buf, size, offs));
}

/*
 *              Build the user mapping
 *      - according to a mapping file if defined (or default present),
 *      - or try default single user mapping if possible
 *
 *      The mapping is specific to a mounted device
 *      No locking done, mounting assumed non multithreaded
 *
 *      returns zero if mapping is successful
 *      (failure should not be interpreted as an error)
 */

int ntfs_build_mapping(struct SECURITY_CONTEXT *scx, const char *usermap_path,
                        BOOL allowdef)
{
        struct MAPLIST *item;
        struct MAPLIST *firstitem;
        struct MAPPING *usermapping;
        struct MAPPING *groupmapping;
        ntfs_inode *ni;
        int fd;
        static struct {
                u8 revision;
                u8 levels;
                be16 highbase;
                be32 lowbase;
                le32 level1;
                le32 level2;
                le32 level3;
                le32 level4;
                le32 level5;
        } defmap = {
                1, 5, const_cpu_to_be16(0), const_cpu_to_be32(5),
                const_cpu_to_le32(21),
                const_cpu_to_le32(DEFSECAUTH1), const_cpu_to_le32(DEFSECAUTH2),
                const_cpu_to_le32(DEFSECAUTH3), const_cpu_to_le32(DEFSECBASE)
        } ;

        /* be sure not to map anything until done */
        scx->mapping[MAPUSERS] = (struct MAPPING*)NULL;
        scx->mapping[MAPGROUPS] = (struct MAPPING*)NULL;

        if (!usermap_path) usermap_path = MAPPINGFILE;
        if (usermap_path[0] == '/') {
                fd = open(usermap_path,O_RDONLY);
                if (fd > 0) {
                        firstitem = ntfs_read_mapping(basicread, (void*)&fd);
                        close(fd);
                } else
                        firstitem = (struct MAPLIST*)NULL;
        } else {
                ni = ntfs_pathname_to_inode(scx->vol, NULL, usermap_path);
                if (ni) {
                        firstitem = ntfs_read_mapping(localread, ni);
                        ntfs_inode_close(ni);
                } else
                        firstitem = (struct MAPLIST*)NULL;
        }


        if (firstitem) {
                usermapping = ntfs_do_user_mapping(firstitem);
                groupmapping = ntfs_do_group_mapping(firstitem);
                if (usermapping && groupmapping) {
                        scx->mapping[MAPUSERS] = usermapping;
                        scx->mapping[MAPGROUPS] = groupmapping;
                } else
                        ntfs_log_error("There were no valid user or no valid group\n");
                /* now we can free the memory copy of input text */
                /* and rely on internal representation */
                while (firstitem) {
                        item = firstitem->next;
                        free(firstitem);
                        firstitem = item;
                }
        } else {
                        /* no mapping file, try a default mapping */
                if (allowdef) {
                        if (!ntfs_do_default_mapping(scx,
                                        0, 0, (const SID*)&defmap))
                                ntfs_log_info("Using default user mapping\n");
                }
        }
        return (!scx->mapping[MAPUSERS] || link_group_members(scx));
}


/*
 *              Get the ntfs attribute into an extended attribute
 *      The attribute is returned according to cpu endianness
 */

int ntfs_get_ntfs_attrib(ntfs_inode *ni, char *value, size_t size)
{
        u32 attrib;
        size_t outsize;

        outsize = 0;    /* default to no data and no error */
        if (ni) {
                attrib = le32_to_cpu(ni->flags);
                if (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY)
                        attrib |= const_le32_to_cpu(FILE_ATTR_DIRECTORY);
                else
                        attrib &= ~const_le32_to_cpu(FILE_ATTR_DIRECTORY);
                if (!attrib)
                        attrib |= const_le32_to_cpu(FILE_ATTR_NORMAL);
                outsize = sizeof(FILE_ATTR_FLAGS);
                if (size >= outsize) {
                        if (value)
                                memcpy(value,&attrib,outsize);
                        else
                                errno = EINVAL;
                }
        }
        return (outsize ? (int)outsize : -errno);
}

/*
 *              Return the ntfs attribute into an extended attribute
 *      The attribute is expected according to cpu endianness
 *
 *      Returns 0, or -1 if there is a problem
 */

int ntfs_set_ntfs_attrib(ntfs_inode *ni,
                        const char *value, size_t size, int flags)
{
        u32 attrib;
        le32 settable;
        ATTR_FLAGS dirflags;
        int res;

        res = -1;
        if (ni && value && (size >= sizeof(FILE_ATTR_FLAGS))) {
                if (!(flags & XATTR_CREATE)) {
                        /* copy to avoid alignment problems */
                        memcpy(&attrib,value,sizeof(FILE_ATTR_FLAGS));
                        settable = FILE_ATTR_SETTABLE;
                        res = 0;
                        if (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) {
                                /*
                                 * Accept changing compression for a directory
                                 * and set index root accordingly
                                 */
                                settable |= FILE_ATTR_COMPRESSED;
                                if ((ni->flags ^ cpu_to_le32(attrib))
                                             & FILE_ATTR_COMPRESSED) {
                                        if (ni->flags & FILE_ATTR_COMPRESSED)
                                                dirflags = const_cpu_to_le16(0);
                                        else
                                                dirflags = ATTR_IS_COMPRESSED;
                                        res = ntfs_attr_set_flags(ni,
                                                AT_INDEX_ROOT,
                                                NTFS_INDEX_I30, 4,
                                                dirflags,
                                                ATTR_COMPRESSION_MASK);
                                }
                        }
                        if (!res) {
                                ni->flags = (ni->flags & ~settable)
                                         | (cpu_to_le32(attrib) & settable);
                                NInoFileNameSetDirty(ni);
                                NInoSetDirty(ni);
                        }
                } else
                        errno = EEXIST;
        } else
                errno = EINVAL;
        return (res ? -1 : 0);
}


/*
 *      Open the volume's security descriptor index ($Secure)
 *
 *      returns  0 if it succeeds
 *              -1 with errno set if it fails and the volume is NTFS v3.0+
 */
int ntfs_open_secure(ntfs_volume *vol)
{
        ntfs_inode *ni;
        ntfs_index_context *sii;
        ntfs_index_context *sdh;

        if (vol->secure_ni) /* Already open? */
                return 0;

        ni = ntfs_pathname_to_inode(vol, NULL, "$Secure");
        if (!ni)
                goto err;

        if (ni->mft_no != FILE_Secure) {
                ntfs_log_error("$Secure does not have expected inode number!");
                errno = EINVAL;
                goto err_close_ni;
        }

        /* Allocate the needed index contexts. */
        sii = ntfs_index_ctx_get(ni, sii_stream, 4);
        if (!sii)
                goto err_close_ni;

        sdh = ntfs_index_ctx_get(ni, sdh_stream, 4);
        if (!sdh)
                goto err_close_sii;

        vol->secure_xsdh = sdh;
        vol->secure_xsii = sii;
        vol->secure_ni = ni;
        return 0;

err_close_sii:
        ntfs_index_ctx_put(sii);
err_close_ni:
        ntfs_inode_close(ni);
err:
        /* Failing on NTFS pre-v3.0 is expected. */
        if (vol->major_ver < 3)
                return 0;
        ntfs_log_perror("Failed to open $Secure");
        return -1;
}

/*
 *      Close the volume's security descriptor index ($Secure)
 *
 *      returns  0 if it succeeds
 *              -1 with errno set if it fails
 */
int ntfs_close_secure(ntfs_volume *vol)
{
        int res = 0;

        if (vol->secure_ni) {
                ntfs_index_ctx_put(vol->secure_xsdh);
                ntfs_index_ctx_put(vol->secure_xsii);
                res = ntfs_inode_close(vol->secure_ni);
                vol->secure_ni = NULL;
        }
        return res;
}

/*
 *              Destroy a security context
 *      Allocated memory is freed to facilitate the detection of memory leaks
 */
void ntfs_destroy_security_context(struct SECURITY_CONTEXT *scx)
{
        ntfs_free_mapping(scx->mapping);
        free_caches(scx);
}

/*
 *              API for direct access to security descriptors
 *      based on Win32 API
 */


/*
 *              Selective feeding of a security descriptor into user buffer
 *
 *      Returns TRUE if successful
 */

static BOOL feedsecurityattr(const char *attr, u32 selection,
                char *buf, u32 buflen, u32 *psize)
{
        const SECURITY_DESCRIPTOR_RELATIVE *phead;
        SECURITY_DESCRIPTOR_RELATIVE *pnhead;
        const ACL *pdacl;
        const ACL *psacl;
        const SID *pusid;
        const SID *pgsid;
        unsigned int offdacl;
        unsigned int offsacl;
        unsigned int offowner;
        unsigned int offgroup;
        unsigned int daclsz;
        unsigned int saclsz;
        unsigned int usidsz;
        unsigned int gsidsz;
        unsigned int size; /* size of requested attributes */
        BOOL ok;
        unsigned int pos;
        unsigned int avail;
        le16 control;

        avail = 0;
        control = SE_SELF_RELATIVE;
        phead = (const SECURITY_DESCRIPTOR_RELATIVE*)attr;
        size = sizeof(SECURITY_DESCRIPTOR_RELATIVE);

                /* locate DACL if requested and available */
        if (phead->dacl && (selection & DACL_SECURITY_INFORMATION)) {
                offdacl = le32_to_cpu(phead->dacl);
                pdacl = (const ACL*)&attr[offdacl];
                daclsz = le16_to_cpu(pdacl->size);
                size += daclsz;
                avail |= DACL_SECURITY_INFORMATION;
        } else
                offdacl = daclsz = 0;

                /* locate owner if requested and available */
        offowner = le32_to_cpu(phead->owner);
        if (offowner && (selection & OWNER_SECURITY_INFORMATION)) {
                        /* find end of USID */
                pusid = (const SID*)&attr[offowner];
                usidsz = ntfs_sid_size(pusid);
                size += usidsz;
                avail |= OWNER_SECURITY_INFORMATION;
        } else
                offowner = usidsz = 0;

                /* locate group if requested and available */
        offgroup = le32_to_cpu(phead->group);
        if (offgroup && (selection & GROUP_SECURITY_INFORMATION)) {
                        /* find end of GSID */
                pgsid = (const SID*)&attr[offgroup];
                gsidsz = ntfs_sid_size(pgsid);
                size += gsidsz;
                avail |= GROUP_SECURITY_INFORMATION;
        } else
                offgroup = gsidsz = 0;

                /* locate SACL if requested and available */
        if (phead->sacl && (selection & SACL_SECURITY_INFORMATION)) {
                        /* find end of SACL */
                offsacl = le32_to_cpu(phead->sacl);
                psacl = (const ACL*)&attr[offsacl];
                saclsz = le16_to_cpu(psacl->size);
                size += saclsz;
                avail |= SACL_SECURITY_INFORMATION;
        } else
                offsacl = saclsz = 0;

                /*
                 * Check having enough size in destination buffer
                 * (required size is returned nevertheless so that
                 * the request can be reissued with adequate size)
                 */
        if (size > buflen) {
                *psize = size;
                errno = EINVAL;
                ok = FALSE;
        } else {
                if (selection & OWNER_SECURITY_INFORMATION)
                        control |= phead->control & SE_OWNER_DEFAULTED;
                if (selection & GROUP_SECURITY_INFORMATION)
                        control |= phead->control & SE_GROUP_DEFAULTED;
                if (selection & DACL_SECURITY_INFORMATION)
                        control |= phead->control
                                        & (SE_DACL_PRESENT
                                           | SE_DACL_DEFAULTED
                                           | SE_DACL_AUTO_INHERITED
                                           | SE_DACL_PROTECTED);
                if (selection & SACL_SECURITY_INFORMATION)
                        control |= phead->control
                                        & (SE_SACL_PRESENT
                                           | SE_SACL_DEFAULTED
                                           | SE_SACL_AUTO_INHERITED
                                           | SE_SACL_PROTECTED);
                /*
                 * copy header and feed new flags, even if no detailed data
                 */
                memcpy(buf,attr,sizeof(SECURITY_DESCRIPTOR_RELATIVE));
                pnhead = (SECURITY_DESCRIPTOR_RELATIVE*)buf;
                pnhead->control = control;
                pos = sizeof(SECURITY_DESCRIPTOR_RELATIVE);

                /* copy DACL if requested and available */
                if (selection & avail & DACL_SECURITY_INFORMATION) {
                        pnhead->dacl = cpu_to_le32(pos);
                        memcpy(&buf[pos],&attr[offdacl],daclsz);
                        pos += daclsz;
                } else
                        pnhead->dacl = const_cpu_to_le32(0);

                /* copy SACL if requested and available */
                if (selection & avail & SACL_SECURITY_INFORMATION) {
                        pnhead->sacl = cpu_to_le32(pos);
                        memcpy(&buf[pos],&attr[offsacl],saclsz);
                        pos += saclsz;
                } else
                        pnhead->sacl = const_cpu_to_le32(0);

                /* copy owner if requested and available */
                if (selection & avail & OWNER_SECURITY_INFORMATION) {
                        pnhead->owner = cpu_to_le32(pos);
                        memcpy(&buf[pos],&attr[offowner],usidsz);
                        pos += usidsz;
                } else
                        pnhead->owner = const_cpu_to_le32(0);

                /* copy group if requested and available */
                if (selection & avail & GROUP_SECURITY_INFORMATION) {
                        pnhead->group = cpu_to_le32(pos);
                        memcpy(&buf[pos],&attr[offgroup],gsidsz);
                        pos += gsidsz;
                } else
                        pnhead->group = const_cpu_to_le32(0);
                if (pos != size)
                        ntfs_log_error("Error in security descriptor size\n");
                *psize = size;
                ok = TRUE;
        }

        return (ok);
}

/*
 *              Merge a new security descriptor into the old one
 *      and assign to designated file
 *
 *      Returns TRUE if successful
 */

static BOOL mergesecurityattr(ntfs_volume *vol, const char *oldattr,
                const char *newattr, u32 selection, ntfs_inode *ni)
{
        const SECURITY_DESCRIPTOR_RELATIVE *oldhead;
        const SECURITY_DESCRIPTOR_RELATIVE *newhead;
        SECURITY_DESCRIPTOR_RELATIVE *targhead;
        const ACL *pdacl;
        const ACL *psacl;
        const SID *powner;
        const SID *pgroup;
        int offdacl;
        int offsacl;
        int offowner;
        int offgroup;
        unsigned int size;
        le16 control;
        char *target;
        int pos;
        int oldattrsz;
        int newattrsz;
        BOOL ok;

        ok = FALSE; /* default return */
        oldhead = (const SECURITY_DESCRIPTOR_RELATIVE*)oldattr;
        newhead = (const SECURITY_DESCRIPTOR_RELATIVE*)newattr;
        oldattrsz = ntfs_attr_size(oldattr);
        newattrsz = ntfs_attr_size(newattr);
        target = (char*)ntfs_malloc(oldattrsz + newattrsz);
        if (target) {
                targhead = (SECURITY_DESCRIPTOR_RELATIVE*)target;
                pos = sizeof(SECURITY_DESCRIPTOR_RELATIVE);
                control = SE_SELF_RELATIVE;
                        /*
                         * copy new DACL if selected
                         * or keep old DACL if any
                         */
                if ((selection & DACL_SECURITY_INFORMATION) ?
                                newhead->dacl : oldhead->dacl) {
                        if (selection & DACL_SECURITY_INFORMATION) {
                                offdacl = le32_to_cpu(newhead->dacl);
                                pdacl = (const ACL*)&newattr[offdacl];
                        } else {
                                offdacl = le32_to_cpu(oldhead->dacl);
                                pdacl = (const ACL*)&oldattr[offdacl];
                        }
                        size = le16_to_cpu(pdacl->size);
                        memcpy(&target[pos], pdacl, size);
                        targhead->dacl = cpu_to_le32(pos);
                        pos += size;
                } else
                        targhead->dacl = const_cpu_to_le32(0);
                if (selection & DACL_SECURITY_INFORMATION) {
                        control |= newhead->control
                                        & (SE_DACL_PRESENT
                                           | SE_DACL_DEFAULTED
                                           | SE_DACL_PROTECTED);
                        if (newhead->control & SE_DACL_AUTO_INHERIT_REQ)
                                control |= SE_DACL_AUTO_INHERITED;
                } else
                        control |= oldhead->control
                                        & (SE_DACL_PRESENT
                                           | SE_DACL_DEFAULTED
                                           | SE_DACL_AUTO_INHERITED
                                           | SE_DACL_PROTECTED);
                        /*
                         * copy new SACL if selected
                         * or keep old SACL if any
                         */
                if ((selection & SACL_SECURITY_INFORMATION) ?
                                newhead->sacl : oldhead->sacl) {
                        if (selection & SACL_SECURITY_INFORMATION) {
                                offsacl = le32_to_cpu(newhead->sacl);
                                psacl = (const ACL*)&newattr[offsacl];
                        } else {
                                offsacl = le32_to_cpu(oldhead->sacl);
                                psacl = (const ACL*)&oldattr[offsacl];
                        }
                        size = le16_to_cpu(psacl->size);
                        memcpy(&target[pos], psacl, size);
                        targhead->sacl = cpu_to_le32(pos);
                        pos += size;
                } else
                        targhead->sacl = const_cpu_to_le32(0);
                if (selection & SACL_SECURITY_INFORMATION) {
                        control |= newhead->control
                                        & (SE_SACL_PRESENT
                                           | SE_SACL_DEFAULTED
                                           | SE_SACL_PROTECTED);
                        if (newhead->control & SE_SACL_AUTO_INHERIT_REQ)
                                control |= SE_SACL_AUTO_INHERITED;
                } else
                        control |= oldhead->control
                                        & (SE_SACL_PRESENT
                                           | SE_SACL_DEFAULTED
                                           | SE_SACL_AUTO_INHERITED
                                           | SE_SACL_PROTECTED);
                        /*
                         * copy new OWNER if selected
                         * or keep old OWNER if any
                         */
                if ((selection & OWNER_SECURITY_INFORMATION) ?
                                newhead->owner : oldhead->owner) {
                        if (selection & OWNER_SECURITY_INFORMATION) {
                                offowner = le32_to_cpu(newhead->owner);
                                powner = (const SID*)&newattr[offowner];
                        } else {
                                offowner = le32_to_cpu(oldhead->owner);
                                powner = (const SID*)&oldattr[offowner];
                        }
                        size = ntfs_sid_size(powner);
                        memcpy(&target[pos], powner, size);
                        targhead->owner = cpu_to_le32(pos);
                        pos += size;
                } else
                        targhead->owner = const_cpu_to_le32(0);
                if (selection & OWNER_SECURITY_INFORMATION)
                        control |= newhead->control & SE_OWNER_DEFAULTED;
                else
                        control |= oldhead->control & SE_OWNER_DEFAULTED;
                        /*
                         * copy new GROUP if selected
                         * or keep old GROUP if any
                         */
                if ((selection & GROUP_SECURITY_INFORMATION) ?
                                newhead->group : oldhead->group) {
                        if (selection & GROUP_SECURITY_INFORMATION) {
                                offgroup = le32_to_cpu(newhead->group);
                                pgroup = (const SID*)&newattr[offgroup];
                                control |= newhead->control
                                                 & SE_GROUP_DEFAULTED;
                        } else {
                                offgroup = le32_to_cpu(oldhead->group);
                                pgroup = (const SID*)&oldattr[offgroup];
                                control |= oldhead->control
                                                 & SE_GROUP_DEFAULTED;
                        }
                        size = ntfs_sid_size(pgroup);
                        memcpy(&target[pos], pgroup, size);
                        targhead->group = cpu_to_le32(pos);
                        pos += size;
                } else
                        targhead->group = const_cpu_to_le32(0);
                if (selection & GROUP_SECURITY_INFORMATION)
                        control |= newhead->control & SE_GROUP_DEFAULTED;
                else
                        control |= oldhead->control & SE_GROUP_DEFAULTED;
                targhead->revision = SECURITY_DESCRIPTOR_REVISION;
                targhead->alignment = 0;
                targhead->control = control;
                ok = !update_secur_descr(vol, target, ni);
                free(target);
        }
        return (ok);
}

/*
 *              Return the security descriptor of a file
 *      This is intended to be similar to GetFileSecurity() from Win32
 *      in order to facilitate the development of portable tools
 *
 *      returns zero if unsuccessful (following Win32 conventions)
 *              -1 if no securid
 *              the securid if any
 *
 *  The Win32 API is :
 *
 *  BOOL WINAPI GetFileSecurity(
 *    __in          LPCTSTR lpFileName,
 *    __in          SECURITY_INFORMATION RequestedInformation,
 *    __out_opt     PSECURITY_DESCRIPTOR pSecurityDescriptor,
 *    __in          DWORD nLength,
 *    __out         LPDWORD lpnLengthNeeded
 *  );
 *
 */

int ntfs_get_file_security(struct SECURITY_API *scapi,
                const char *path, u32 selection,
                char *buf, u32 buflen, u32 *psize)
{
        ntfs_inode *ni;
        char *attr;
        int res;

        res = 0; /* default return */
        if (scapi && (scapi->magic == MAGIC_API)) {
                ni = ntfs_pathname_to_inode(scapi->security.vol, NULL, path);
                if (ni) {
                        attr = getsecurityattr(scapi->security.vol, ni);
                        if (attr) {
                                if (feedsecurityattr(attr,selection,
                                                buf,buflen,psize)) {
                                        if (test_nino_flag(ni, v3_Extensions)
                                            && ni->security_id)
                                                res = le32_to_cpu(
                                                        ni->security_id);
                                        else
                                                res = -1;
                                }
                                free(attr);
                        }
                        ntfs_inode_close(ni);
                } else
                        errno = ENOENT;
                if (!res) *psize = 0;
        } else
                errno = EINVAL; /* do not clear *psize */
        return (res);
}


/*
 *              Set the security descriptor of a file or directory
 *      This is intended to be similar to SetFileSecurity() from Win32
 *      in order to facilitate the development of portable tools
 *
 *      returns zero if unsuccessful (following Win32 conventions)
 *              -1 if no securid
 *              the securid if any
 *
 *  The Win32 API is :
 *
 *  BOOL WINAPI SetFileSecurity(
 *    __in          LPCTSTR lpFileName,
 *    __in          SECURITY_INFORMATION SecurityInformation,
 *    __in          PSECURITY_DESCRIPTOR pSecurityDescriptor
 *  );
 */

int ntfs_set_file_security(struct SECURITY_API *scapi,
                const char *path, u32 selection, const char *attr)
{
        const SECURITY_DESCRIPTOR_RELATIVE *phead;
        ntfs_inode *ni;
        int attrsz;
        BOOL missing;
        char *oldattr;
        int res;

        res = 0; /* default return */
        if (scapi && (scapi->magic == MAGIC_API) && attr) {
                phead = (const SECURITY_DESCRIPTOR_RELATIVE*)attr;
                attrsz = ntfs_attr_size(attr);
                /* if selected, owner and group must be present or defaulted */
                missing = ((selection & OWNER_SECURITY_INFORMATION)
                                && !phead->owner
                                && !(phead->control & SE_OWNER_DEFAULTED))
                        || ((selection & GROUP_SECURITY_INFORMATION)
                                && !phead->group
                                && !(phead->control & SE_GROUP_DEFAULTED));
                if (!missing
                    && (phead->control & SE_SELF_RELATIVE)
                    && ntfs_valid_descr(attr, attrsz)) {
                        ni = ntfs_pathname_to_inode(scapi->security.vol,
                                NULL, path);
                        if (ni) {
                                oldattr = getsecurityattr(scapi->security.vol,
                                                ni);
                                if (oldattr) {
                                        if (mergesecurityattr(
                                                scapi->security.vol,
                                                oldattr, attr,
                                                selection, ni)) {
                                                if (test_nino_flag(ni,
                                                            v3_Extensions))
                                                        res = le32_to_cpu(
                                                            ni->security_id);
                                                else
                                                        res = -1;
                                        }
                                        free(oldattr);
                                }
                                ntfs_inode_close(ni);
                        }
                } else
                        errno = EINVAL;
        } else
                errno = EINVAL;
        return (res);
}


/*
 *              Return the attributes of a file
 *      This is intended to be similar to GetFileAttributes() from Win32
 *      in order to facilitate the development of portable tools
 *
 *      returns -1 if unsuccessful (Win32 : INVALID_FILE_ATTRIBUTES)
 *
 *  The Win32 API is :
 *
 *  DWORD WINAPI GetFileAttributes(
 *   __in  LPCTSTR lpFileName
 *  );
 */

int ntfs_get_file_attributes(struct SECURITY_API *scapi, const char *path)
{
        ntfs_inode *ni;
        s32 attrib;

        attrib = -1; /* default return */
        if (scapi && (scapi->magic == MAGIC_API) && path) {
                ni = ntfs_pathname_to_inode(scapi->security.vol, NULL, path);
                if (ni) {
                        attrib = le32_to_cpu(ni->flags);
                        if (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY)
                                attrib |= const_le32_to_cpu(FILE_ATTR_DIRECTORY);
                        else
                                attrib &= ~const_le32_to_cpu(FILE_ATTR_DIRECTORY);
                        if (!attrib)
                                attrib |= const_le32_to_cpu(FILE_ATTR_NORMAL);

                        ntfs_inode_close(ni);
                } else
                        errno = ENOENT;
        } else
                errno = EINVAL; /* do not clear *psize */
        return (attrib);
}


/*
 *              Set attributes to a file or directory
 *      This is intended to be similar to SetFileAttributes() from Win32
 *      in order to facilitate the development of portable tools
 *
 *      Only a few flags can be set (same list as Win32)
 *
 *      returns zero if unsuccessful (following Win32 conventions)
 *              nonzero if successful
 *
 *  The Win32 API is :
 *
 *  BOOL WINAPI SetFileAttributes(
 *    __in  LPCTSTR lpFileName,
 *    __in  DWORD dwFileAttributes
 *  );
 */

BOOL ntfs_set_file_attributes(struct SECURITY_API *scapi,
                const char *path, s32 attrib)
{
        ntfs_inode *ni;
        le32 settable;
        ATTR_FLAGS dirflags;
        int res;

        res = 0; /* default return */
        if (scapi && (scapi->magic == MAGIC_API) && path) {
                ni = ntfs_pathname_to_inode(scapi->security.vol, NULL, path);
                if (ni) {
                        settable = FILE_ATTR_SETTABLE;
                        if (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) {
                                /*
                                 * Accept changing compression for a directory
                                 * and set index root accordingly
                                 */
                                settable |= FILE_ATTR_COMPRESSED;
                                if ((ni->flags ^ cpu_to_le32(attrib))
                                             & FILE_ATTR_COMPRESSED) {
                                        if (ni->flags & FILE_ATTR_COMPRESSED)
                                                dirflags = const_cpu_to_le16(0);
                                        else
                                                dirflags = ATTR_IS_COMPRESSED;
                                        res = ntfs_attr_set_flags(ni,
                                                AT_INDEX_ROOT,
                                                NTFS_INDEX_I30, 4,
                                                dirflags,
                                                ATTR_COMPRESSION_MASK);
                                }
                        }
                        if (!res) {
                                ni->flags = (ni->flags & ~settable)
                                         | (cpu_to_le32(attrib) & settable);
                                NInoSetDirty(ni);
                                NInoFileNameSetDirty(ni);
                        }
                        if (!ntfs_inode_close(ni))
                                res = -1;
                } else
                        errno = ENOENT;
        }
        return (res);
}


BOOL ntfs_read_directory(struct SECURITY_API *scapi,
                const char *path, ntfs_filldir_t callback, void *context)
{
        ntfs_inode *ni;
        BOOL ok;
        s64 pos;

        ok = FALSE; /* default return */
        if (scapi && (scapi->magic == MAGIC_API) && callback) {
                ni = ntfs_pathname_to_inode(scapi->security.vol, NULL, path);
                if (ni) {
                        if (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) {
                                pos = 0;
                                ntfs_readdir(ni,&pos,context,callback);
                                ok = !ntfs_inode_close(ni);
                        } else {
                                ntfs_inode_close(ni);
                                errno = ENOTDIR;
                        }
                } else
                        errno = ENOENT;
        } else
                errno = EINVAL; /* do not clear *psize */
        return (ok);
}

/*
 *              read $SDS (for auditing security data)
 *
 *      Returns the number or read bytes, or -1 if there is an error
 */

int ntfs_read_sds(struct SECURITY_API *scapi,
                char *buf, u32 size, u32 offset)
{
        int got;

        got = -1; /* default return */
        if (scapi && (scapi->magic == MAGIC_API)) {
                if (scapi->security.vol->secure_ni)
                        got = ntfs_attr_data_read(scapi->security.vol->secure_ni,
                                STREAM_SDS, 4, buf, size, offset);
                else
                        errno = EOPNOTSUPP;
        } else
                errno = EINVAL;
        return (got);
}

/*
 *              read $SII (for auditing security data)
 *
 *      Returns next entry, or NULL if there is an error
 */

INDEX_ENTRY *ntfs_read_sii(struct SECURITY_API *scapi,
                INDEX_ENTRY *entry)
{
        SII_INDEX_KEY key;
        INDEX_ENTRY *ret;
        BOOL found;
        ntfs_index_context *xsii;

        ret = (INDEX_ENTRY*)NULL; /* default return */
        if (scapi && (scapi->magic == MAGIC_API)) {
                xsii = scapi->security.vol->secure_xsii;
                if (xsii) {
                        if (!entry) {
                                key.security_id = const_cpu_to_le32(0);
                                found = !ntfs_index_lookup((char*)&key,
                                                sizeof(SII_INDEX_KEY), xsii);
                                /* not supposed to find */
                                if (!found && (errno == ENOENT))
                                        ret = xsii->entry;
                        } else
                                ret = ntfs_index_next(entry,xsii);
                        if (!ret)
                                errno = ENODATA;
                } else
                        errno = EOPNOTSUPP;
        } else
                errno = EINVAL;
        return (ret);
}

/*
 *              read $SDH (for auditing security data)
 *
 *      Returns next entry, or NULL if there is an error
 */

INDEX_ENTRY *ntfs_read_sdh(struct SECURITY_API *scapi,
                INDEX_ENTRY *entry)
{
        SDH_INDEX_KEY key;
        INDEX_ENTRY *ret;
        BOOL found;
        ntfs_index_context *xsdh;

        ret = (INDEX_ENTRY*)NULL; /* default return */
        if (scapi && (scapi->magic == MAGIC_API)) {
                xsdh = scapi->security.vol->secure_xsdh;
                if (xsdh) {
                        if (!entry) {
                                key.hash = const_cpu_to_le32(0);
                                key.security_id = const_cpu_to_le32(0);
                                found = !ntfs_index_lookup((char*)&key,
                                                sizeof(SDH_INDEX_KEY), xsdh);
                                /* not supposed to find */
                                if (!found && (errno == ENOENT))
                                        ret = xsdh->entry;
                        } else
                                ret = ntfs_index_next(entry,xsdh);
                        if (!ret)
                                errno = ENODATA;
                } else errno = ENOTSUP;
        } else
                errno = EINVAL;
        return (ret);
}

/*
 *              Get the mapped user SID
 *      A buffer of 40 bytes has to be supplied
 *
 *      returns the size of the SID, or zero and errno set if not found
 */

int ntfs_get_usid(struct SECURITY_API *scapi, uid_t uid, char *buf)
{
        const SID *usid;
        BIGSID defusid;
        int size;

        size = 0;
        if (scapi && (scapi->magic == MAGIC_API)) {
                usid = ntfs_find_usid(scapi->security.mapping[MAPUSERS], uid, (SID*)&defusid);
                if (usid) {
                        size = ntfs_sid_size(usid);
                        memcpy(buf,usid,size);
                } else
                        errno = ENODATA;
        } else
                errno = EINVAL;
        return (size);
}

/*
 *              Get the mapped group SID
 *      A buffer of 40 bytes has to be supplied
 *
 *      returns the size of the SID, or zero and errno set if not found
 */

int ntfs_get_gsid(struct SECURITY_API *scapi, gid_t gid, char *buf)
{
        const SID *gsid;
        BIGSID defgsid;
        int size;

        size = 0;
        if (scapi && (scapi->magic == MAGIC_API)) {
                gsid = ntfs_find_gsid(scapi->security.mapping[MAPGROUPS], gid, (SID*)&defgsid);
                if (gsid) {
                        size = ntfs_sid_size(gsid);
                        memcpy(buf,gsid,size);
                } else
                        errno = ENODATA;
        } else
                errno = EINVAL;
        return (size);
}

/*
 *              Get the user mapped to a SID
 *
 *      returns the uid, or -1 if not found
 */

int ntfs_get_user(struct SECURITY_API *scapi, const SID *usid)
{
        int uid;

        uid = -1;
        if (scapi && (scapi->magic == MAGIC_API) && ntfs_valid_sid(usid)) {
                if (ntfs_same_sid(usid,adminsid))
                        uid = 0;
                else {
                        uid = ntfs_find_user(scapi->security.mapping[MAPUSERS], usid);
                        if (!uid) {
                                uid = -1;
                                errno = ENODATA;
                        }
                }
        } else
                errno = EINVAL;
        return (uid);
}

/*
 *              Get the group mapped to a SID
 *
 *      returns the uid, or -1 if not found
 */

int ntfs_get_group(struct SECURITY_API *scapi, const SID *gsid)
{
        int gid;

        gid = -1;
        if (scapi && (scapi->magic == MAGIC_API) && ntfs_valid_sid(gsid)) {
                if (ntfs_same_sid(gsid,adminsid))
                        gid = 0;
                else {
                        gid = ntfs_find_group(scapi->security.mapping[MAPGROUPS], gsid);
                        if (!gid) {
                                gid = -1;
                                errno = ENODATA;
                        }
                }
        } else
                errno = EINVAL;
        return (gid);
}

/*
 *              Initializations before calling ntfs_get_file_security()
 *      ntfs_set_file_security() and ntfs_read_directory()
 *
 *      Only allowed for root
 *
 *      Returns an (obscured) struct SECURITY_API* needed for further calls
 *              NULL if not root (EPERM) or device is mounted (EBUSY)
 */

struct SECURITY_API *ntfs_initialize_file_security(const char *device,
                                unsigned long flags)
{
        ntfs_volume *vol;
        unsigned long mntflag;
        int mnt;
        struct SECURITY_API *scapi;
        struct SECURITY_CONTEXT *scx;

        scapi = (struct SECURITY_API*)NULL;
        mnt = ntfs_check_if_mounted(device, &mntflag);
        if (!mnt && !(mntflag & NTFS_MF_MOUNTED) && !getuid()) {
                vol = ntfs_mount(device, flags);
                if (vol) {
                        scapi = (struct SECURITY_API*)
                                ntfs_malloc(sizeof(struct SECURITY_API));
                        if (!ntfs_volume_get_free_space(vol)
                            && scapi) {
                                scapi->magic = MAGIC_API;
                                scapi->seccache = (struct PERMISSIONS_CACHE*)NULL;
                                scx = &scapi->security;
                                scx->vol = vol;
                                scx->uid = getuid();
                                scx->gid = getgid();
                                scx->pseccache = &scapi->seccache;
                                scx->vol->secure_flags = 0;
                                        /* accept no mapping and no $Secure */
                                ntfs_build_mapping(scx,(const char*)NULL,TRUE);
                        } else {
                                if (scapi)
                                        free(scapi);
                                else
                                        errno = ENOMEM;
                                mnt = ntfs_umount(vol,FALSE);
                                scapi = (struct SECURITY_API*)NULL;
                        }
                }
        } else
                if (getuid())
                        errno = EPERM;
                else
                        errno = EBUSY;
        return (scapi);
}

/*
 *              Leaving after ntfs_initialize_file_security()
 *
 *      Returns FALSE if FAILED
 */

BOOL ntfs_leave_file_security(struct SECURITY_API *scapi)
{
        int ok;
        ntfs_volume *vol;

        ok = FALSE;
        if (scapi && (scapi->magic == MAGIC_API) && scapi->security.vol) {
                vol = scapi->security.vol;
                ntfs_destroy_security_context(&scapi->security);
                free(scapi);
                if (!ntfs_umount(vol, 0))
                        ok = TRUE;
        }
        return (ok);
}