]> wimlib.net Git - wimlib/commitdiff
Add modified security.c from NTFS-3g
authorEric Biggers <ebiggers3@gmail.com>
Tue, 21 Aug 2012 22:17:36 +0000 (17:17 -0500)
committerEric Biggers <ebiggers3@gmail.com>
Tue, 21 Aug 2012 22:17:36 +0000 (17:17 -0500)
This simplifies WIM application because we can modifiy
ntfs_set_file_attributes() and ntfs_set_file_security() to take ntfs_inodes
instead of pathnodes, and we can also get rid of the SECURITY_API structure.

Makefile.am
src/ntfs-3g_security.c [new file with mode: 0644]
src/ntfs.c

index c77b0589d0d21d286f0dcd9607eaea995b87f176..9b00f8b9cb27607cdf1017414b7be0c19e4cd5ae 100644 (file)
@@ -32,6 +32,7 @@ libwim_la_SOURCES =           \
        src/modify.c            \
        src/mount.c             \
        src/ntfs.c              \
+       src/ntfs-3g_security.c  \
        src/resource.c          \
        src/security.c          \
        src/security.h          \
diff --git a/src/ntfs-3g_security.c b/src/ntfs-3g_security.c
new file mode 100644 (file)
index 0000000..a508be4
--- /dev/null
@@ -0,0 +1,5184 @@
+/**
+ * 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-2010 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
+
+#include <stdio.h>
+#ifdef HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+#ifdef HAVE_STRING_H
+#include <string.h>
+#endif
+#include <errno.h>
+#include <fcntl.h>
+#ifdef HAVE_SETXATTR
+#include <sys/xattr.h>
+#endif
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+
+#include <stdarg.h>
+
+#include <unistd.h>
+#include <pwd.h>
+#include <grp.h>
+
+#include <ntfs-3g/param.h>
+#include <ntfs-3g/types.h>
+#include <ntfs-3g/layout.h>
+#include <ntfs-3g/attrib.h>
+#include <ntfs-3g/index.h>
+#include <ntfs-3g/dir.h>
+#include <ntfs-3g/bitmap.h>
+#include <ntfs-3g/security.h>
+#include <ntfs-3g/acls.h>
+#include <ntfs-3g/cache.h>
+#include <ntfs-3g/misc.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_sid_is_valid(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_sid_is_valid(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;
+
+       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))
+                       res = 0;
+               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");
+                               } else {
+                                               /* restore errno */
+                                       errno = olderrno;
+                                       entry = xsii->entry;
+                               }
+                               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)) {
+                               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);
+}
+
+
+/*
+ *             Check whether current thread owner is member of file group
+ *
+ *     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);
+}
+
+/*
+ *     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
+ *
+ *     Do not call if default_permissions is set
+ */
+
+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;
+       mode_t perms;
+       int i;
+
+       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 = 07777;
+                       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 :
+                                       case POSIX_ACL_GROUP :
+                                               groupperms |= pxace->perms;
+                                               break;
+                                       case POSIX_ACL_MASK :
+                                               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 ((uid_t)pxace->id == scx->uid)
+                                       userperms = pxace->perms;
+                               break;
+                       case POSIX_ACL_MASK :
+                               mask = pxace->perms & 7;
+                               break;
+                       case POSIX_ACL_GROUP_OBJ :
+                       case POSIX_ACL_GROUP :
+                               if (((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 {
+                                       /* 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, 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;
+                       }
+               }
+       }
+       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 = 07777;
+                               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) {
+                       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)
+                                       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) {
+                       pydesc = ntfs_build_inherited_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);
+                               }
+                               pydesc = ntfs_build_inherited_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);
+               }
+       } 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);
+                       }
+               }
+               allowed = FALSE;
+               if (gotowner) {
+/* TODO : use CAP_FOWNER process capability */
+                       if (!processuid || (processuid == uid))
+                               allowed = TRUE;
+                       else
+                               errno = EPERM;
+               }
+       }
+       return (allowed);
+}
+
+#ifdef HAVE_SETXATTR    /* extended attributes interface required */
+
+#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);
+}
+
+#endif /* HAVE_SETXATTR */
+
+/*
+ *             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);
+}
+
+#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;
+                       /* clear setuid and setgid if owner has changed */
+                        /* unless request originated by root */
+                       if (uid && (fileuid != uid))
+                               mode &= 01777;
+#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 offowner;
+       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 (!usid)
+                       usid = adminsid;
+               if (!gsid)
+                       gsid = adminsid;
+       } else {
+               /*
+                * If there is no user mapping, we have to copy owner
+                * and group from parent directory.
+                * Windows never has to do that, because it can always
+                * rely on a user mapping
+                */
+               offowner = le32_to_cpu(pphead->owner);
+               usid = (const SID*)&parentattr[offowner];
+               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 = 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);
+                       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);
+                       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 += usidsz;
+               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
+                */
+       if (test_nino_flag(dir_ni, v3_Extensions)
+                       && dir_ni->security_id) {
+               cached = fetch_cache(scx, dir_ni);
+               if (cached)
+                       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 (securid) {
+                               cached = fetch_cache(scx, dir_ni);
+                               if (cached) {
+                                       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));
+}
+
+#ifdef HAVE_SETXATTR    /* extended attributes interface required */
+
+/*
+ *             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);
+}
+
+#endif /* HAVE_SETXATTR */
+
+/*
+ *     Open $Secure once for all
+ *     returns zero if it succeeds
+ *             non-zero if it fails. This is not an error (on NTFS v1.x)
+ */
+
+
+int ntfs_open_secure(ntfs_volume *vol)
+{
+       ntfs_inode *ni;
+       int res;
+
+       res = -1;
+       vol->secure_ni = (ntfs_inode*)NULL;
+       vol->secure_xsii = (ntfs_index_context*)NULL;
+       vol->secure_xsdh = (ntfs_index_context*)NULL;
+       if (vol->major_ver >= 3) {
+                       /* make sure this is a genuine $Secure inode 9 */
+               ni = ntfs_pathname_to_inode(vol, NULL, "$Secure");
+               if (ni && (ni->mft_no == 9)) {
+                       vol->secure_reentry = 0;
+                       vol->secure_xsii = ntfs_index_ctx_get(ni,
+                                               sii_stream, 4);
+                       vol->secure_xsdh = ntfs_index_ctx_get(ni,
+                                               sdh_stream, 4);
+                       if (ni && vol->secure_xsii && vol->secure_xsdh) {
+                               vol->secure_ni = ni;
+                               res = 0;
+                       }
+               }
+       }
+       return (res);
+}
+
+/*
+ *             Final cleaning
+ *     Allocated memory is freed to facilitate the detection of memory leaks
+ */
+
+void ntfs_close_secure(struct SECURITY_CONTEXT *scx)
+{
+       ntfs_volume *vol;
+
+       vol = scx->vol;
+       if (vol->secure_ni) {
+               ntfs_index_ctx_put(vol->secure_xsii);
+               ntfs_index_ctx_put(vol->secure_xsdh);
+               ntfs_inode_close(vol->secure_ni);
+               
+       }
+       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);
+}
+
+#if 0
+/*
+ *             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);
+}
+#endif
+
+/* 
+ * Set security data on a NTFS file given an inode
+ *
+ * Returns nonzero on success
+ */
+int _ntfs_set_file_security(ntfs_volume *vol, ntfs_inode *ni,
+                           u32 selection, const char *attr)
+{
+       const SECURITY_DESCRIPTOR_RELATIVE *phead;
+       int attrsz;
+       BOOL missing;
+       char *oldattr;
+       int res;
+
+       res = 0; /* default return */
+       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)) {
+               oldattr = getsecurityattr(vol, ni);
+               if (oldattr) {
+                       if (mergesecurityattr(
+                               vol,
+                               oldattr, attr,
+                               selection, ni)) {
+                               if (test_nino_flag(ni,
+                                           v3_Extensions))
+                                       res = le32_to_cpu(
+                                           ni->security_id);
+                               else
+                                       res = -1;
+                       }
+                       free(oldattr);
+               }
+       } else
+               errno = EINVAL;
+       return (res);
+}
+
+
+#if 0
+/*
+ *             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);
+}
+#endif
+
+/* 
+ * Set attributes of a NTFS file given an inode
+ *
+ * Returns nonzero on success
+ */
+int _ntfs_set_file_attributes(ntfs_inode *ni, s32 attrib)
+{
+       le32 settable = FILE_ATTR_SETTABLE;
+       ATTR_FLAGS dirflags;
+       int ret = 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;
+                       ret = ntfs_attr_set_flags(ni,
+                               AT_INDEX_ROOT,
+                               NTFS_INDEX_I30, 4,
+                               dirflags,
+                               ATTR_COMPRESSION_MASK);
+               }
+       }
+       if (ret == 0) {
+               ni->flags = (ni->flags & ~settable)
+                        | (cpu_to_le32(attrib) & settable);
+               NInoSetDirty(ni);
+               NInoFileNameSetDirty(ni);
+               ret = -1;
+       }
+       return ret;
+}
+
+
+#if 0
+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);
+                               ntfs_open_secure(vol);
+                       } 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_close_secure(&scapi->security);
+               free(scapi);
+               if (!ntfs_umount(vol, 0))
+                       ok = TRUE;
+       }
+       return (ok);
+}
+
+#endif
index f0fb2276262253e8c93971a1b8cb3f196546dd19..317f60d6904adf339323aedc727bc6a1703a2b63 100644 (file)
@@ -24,6 +24,8 @@
 #ifdef WITH_NTFS_3G
 #include "dentry.h"
 #include "lookup_table.h"
+#include <ntfs-3g/layout.h>
+#include <ntfs-3g/acls.h>
 #include <ntfs-3g/attrib.h>
 #include <ntfs-3g/misc.h>
 #include <ntfs-3g/reparse.h>
 
 struct ntfs_apply_args {
        struct SECURITY_API *scapi;
-       int flags;
+       int extract_flags;
        WIMStruct *w;
 };
 
+extern int _ntfs_set_file_security(ntfs_volume *vol, ntfs_inode *ni,
+                                  u32 selection, const char *attr);
+extern int _ntfs_set_file_attributes(ntfs_inode *ni, s32 attrib);
+
 /*
  *             Initializations before calling ntfs_get_file_security()
  *     ntfs_set_file_security() and ntfs_read_directory()
@@ -89,6 +95,29 @@ static struct SECURITY_API *_ntfs_initialize_file_security(const char *device,
        return (scapi);
 }
 
+/*
+ *     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 )
+
+
+
+#if 0
+#endif
+
 static int extract_resource_to_ntfs_attr(WIMStruct *w, const struct resource_entry *entry, 
                                         ntfs_attr *na)
 {
@@ -129,11 +158,6 @@ static int write_ntfs_data_streams(ntfs_inode *ni, const struct dentry *dentry,
        int ret;
 
 
-       if (dentry_is_directory(dentry)) {
-               wimlib_assert(!dentry_first_lte(dentry, w->lookup_table));
-               return 0;
-       }
-
        DEBUG("Writing NTFS data streams for `%s'", dentry->full_path_utf8);
 
        wimlib_assert(dentry->num_ads == 0);
@@ -157,108 +181,63 @@ static int write_ntfs_data_streams(ntfs_inode *ni, const struct dentry *dentry,
        return 0;
 }
 
-static int ntfs_apply_dentry(struct dentry *dentry, void *arg)
+static int __ntfs_apply_dentry(struct dentry *dentry, ntfs_inode *dir_ni,
+                              WIMStruct *w)
 {
-       struct ntfs_apply_args *args = arg;
-       struct SECURITY_API *scapi   = args->scapi;
-       ntfs_volume *vol             = scapi->security.vol;
-       int flags                    = args->flags;
-       WIMStruct *w                 = args->w;
-       int ret = 0;
-       ntfs_inode *dir_ni, *ni;
-       le32 secid;
-
-       DEBUG("Applying `%s'", dentry->full_path_utf8);
-
-       if (dentry_is_root(dentry))
-               return 0;
-
-       if (flags & WIMLIB_EXTRACT_FLAG_VERBOSE) {
-               wimlib_assert(dentry->full_path_utf8);
-               puts(dentry->full_path_utf8);
-       }
+       ntfs_inode *ni;
+       int ret;
+       mode_t type;
 
-       char *p = dentry->full_path_utf8 + dentry->full_path_utf8_len;
-       do {
-               p--;
-       } while (*p != '/');
+       print_dentry(dentry, w->lookup_table);
 
-       char orig = *p;
-       *p = '\0';
-       const char *dir_name;
-       if (p == dentry->full_path_utf8)
-               dir_name = "/"; 
+       if (dentry_is_directory(dentry))
+               type = S_IFDIR;
        else
-               dir_name = dentry->full_path_utf8;
+               type = S_IFREG;
 
-       dir_ni = ntfs_pathname_to_inode(vol, NULL, dir_name);
-       if (!dir_ni) {
-               ret = WIMLIB_ERR_NTFS_3G;
-               ERROR_WITH_ERRNO("Could not find NTFS inode for `%s'",
-                                dir_name);
-               goto out;
-       }
-       DEBUG("Found NTFS inode for `%s'", dir_name);
-       *p = orig;
+       ni = ntfs_create(dir_ni, 0, (ntfschar*)dentry->file_name,
+                        dentry->file_name_len / 2, type);
 
-       ret = 0;
-       secid = 0;
-       if (dentry_is_directory(dentry)) {
-               ni = ntfs_create(dir_ni, 0, (ntfschar*)dentry->file_name,
-                                dentry->file_name_len / 2, S_IFDIR);
-       } else {
-               ni = ntfs_create(dir_ni, 0, (ntfschar*)dentry->file_name,
-                                dentry->file_name_len / 2, S_IFREG);
-       }
        if (!ni) {
-               *p = orig;
                ERROR_WITH_ERRNO("Could not create NTFS object for `%s'",
                                 dentry->full_path_utf8);
                ret = WIMLIB_ERR_NTFS_3G;
                goto out;
        }
 
-       ret = write_ntfs_data_streams(ni, dentry, w);
-       if (ret != 0)
-               goto out;
-       if (ntfs_inode_close_in_dir(ni, dir_ni) != 0) {
-               ERROR_WITH_ERRNO("Failed to close new inode");
-               ret = WIMLIB_ERR_NTFS_3G;
-               goto out;
-       } else {
-               DEBUG("Closed inode `%s'", dentry->full_path_utf8);
+       if (!dentry_is_directory(dentry) &&
+            !(dentry->attributes & FILE_ATTRIBUTE_REPARSE_POINT)) {
+               ret = write_ntfs_data_streams(ni, dentry, w);
+               if (ret != 0)
+                       goto out;
        }
-       if (ntfs_inode_close(dir_ni) != 0) {
+
+       DEBUG("Setting file attributes 0x%x on `%s'",
+             dentry->attributes,
+             dentry->full_path_utf8);
+
+       if (!_ntfs_set_file_attributes(ni, dentry->attributes)) {
+               ERROR("Failed to set NTFS file attributes on `%s'",
+                      dentry->full_path_utf8);
                ret = WIMLIB_ERR_NTFS_3G;
-               ERROR_WITH_ERRNO("Failed to close directory inode");
                goto out;
-       } else {
-               DEBUG("Closed parent inode");
        }
+
        if (dentry->security_id != -1) {
                const struct wim_security_data *sd = wim_security_data(w);
                wimlib_assert(dentry->security_id < sd->num_entries);
                DEBUG("Applying security descriptor %d to `%s'",
                      dentry->security_id, dentry->full_path_utf8);
-               ret = ntfs_set_file_security(scapi, dentry->full_path_utf8,
-                                            ~0,
-                                            sd->descriptors[dentry->security_id]);
-               if (ret != 0) {
+               if (!_ntfs_set_file_security(ni->vol, ni, ~0,
+                                            sd->descriptors[dentry->security_id]))
+               {
                        ERROR_WITH_ERRNO("Failed to set security data on `%s'",
                                        dentry->full_path_utf8);
                        ret = WIMLIB_ERR_NTFS_3G;
                        goto out;
                }
        }
-       DEBUG("Setting file attributes 0x%x on `%s'",
-              dentry->attributes, dentry->full_path_utf8);
-       if (!ntfs_set_file_attributes(scapi, dentry->full_path_utf8,
-                                     dentry->attributes)) {
-               ERROR_WITH_ERRNO("Failed to set NTFS file attributes on `%s'",
-                                dentry->full_path_utf8);
-               ret = WIMLIB_ERR_NTFS_3G;
-               goto out;
-       }
+
        if (dentry->attributes & FILE_ATTR_REPARSE_POINT) {
                struct lookup_table_entry *lte;
                ntfs_inode *ni;
@@ -270,42 +249,94 @@ static int ntfs_apply_dentry(struct dentry *dentry, void *arg)
                        goto out;
                }
                
-               if (!(ni = ntfs_pathname_to_inode(vol, NULL, dentry->full_path_utf8))
-                    || (ret = ntfs_set_ntfs_reparse_data(ni, lte->symlink_buf,
-                                                            lte->resource_entry.original_size,
-                                                            0)) != 0)
-               {
+               ret = ntfs_set_ntfs_reparse_data(ni, lte->symlink_buf,
+                                                lte->resource_entry.original_size,
+                                                0);
+               if (ret != 0) {
                        ERROR_WITH_ERRNO("Failed to set NTFS reparse data on "
                                         "`%s'", dentry->full_path_utf8);
                        ret = WIMLIB_ERR_NTFS_3G;
                        goto out;
                }
        }
+
+       if (ntfs_inode_close_in_dir(ni, dir_ni) != 0) {
+               ERROR_WITH_ERRNO("Failed to close new inode");
+               ret = WIMLIB_ERR_NTFS_3G;
+               goto out;
+       }
 out:
        return ret;
 }
 
-static int do_ntfs_apply(WIMStruct *w, const char *device, int flags)
+static int ntfs_apply_dentry(struct dentry *dentry, void *arg)
+{
+       struct ntfs_apply_args *args = arg;
+       struct SECURITY_API *scapi   = args->scapi;
+       ntfs_volume *vol             = scapi->security.vol;
+       int extract_flags            = args->extract_flags;
+       WIMStruct *w                 = args->w;
+       ntfs_inode *dir_ni;
+       int ret;
+
+       DEBUG("Applying `%s'", dentry->full_path_utf8);
+
+       if (dentry_is_root(dentry))
+               return 0;
+
+       if (extract_flags & WIMLIB_EXTRACT_FLAG_VERBOSE) {
+               wimlib_assert(dentry->full_path_utf8);
+               puts(dentry->full_path_utf8);
+       }
+
+       char *p = dentry->full_path_utf8 + dentry->full_path_utf8_len;
+       do {
+               p--;
+       } while (*p != '/');
+
+       char orig = *p;
+       *p = '\0';
+       const char *dir_name = dentry->full_path_utf8;
+
+       dir_ni = ntfs_pathname_to_inode(vol, NULL, dir_name);
+       *p = orig;
+       if (!dir_ni) {
+               ERROR_WITH_ERRNO("Could not find NTFS inode for `%s'",
+                                dir_name);
+               return WIMLIB_ERR_NTFS_3G;
+       }
+       DEBUG("Found NTFS inode for `%s'", dir_name);
+
+       ret = __ntfs_apply_dentry(dentry, dir_ni, w);
+
+       if (ntfs_inode_close(dir_ni) != 0) {
+               if (ret == 0)
+                       ret = WIMLIB_ERR_NTFS_3G;
+               ERROR_WITH_ERRNO("Failed to close directory inode");
+       }
+       return ret;
+
+}
+
+static int do_ntfs_apply(WIMStruct *w, const char *device, int extract_flags)
 {
        struct SECURITY_API *scapi;
        int ret;
        
        scapi = _ntfs_initialize_file_security(device, 0);
        if (!scapi) {
-               ERROR_WITH_ERRNO("Failed to initialize NTFS file security API "
-                                "on NTFS volume `%s'", device);
+               ERROR_WITH_ERRNO("Failed to mount NTFS volume `%s'", device);
                return WIMLIB_ERR_NTFS_3G;
        }
        struct ntfs_apply_args args = {
-               .scapi = scapi,
-               .flags = flags,
-               .w     = w,
+               .scapi         = scapi,
+               .extract_flags = extract_flags,
+               .w             = w,
        };
        ret = for_dentry_in_tree(wim_root_dentry(w), ntfs_apply_dentry,
                                 &args);
-out:
        if (!ntfs_leave_file_security(scapi)) {
-               ERROR_WITH_ERRNO("Failed to leave file security");
+               ERROR_WITH_ERRNO("Failed to unmount NTFS volume");
                ret = WIMLIB_ERR_NTFS_3G;
        }
        return ret;