summaryrefslogtreecommitdiff
Diffstat
-rwxr-xr-xAUTHORS23
-rwxr-xr-xAndroid.mk89
-rwxr-xr-xAndroid.mk.bak89
-rwxr-xr-xCOPYING340
-rwxr-xr-xCOPYING.LIB482
-rwxr-xr-xCREDITS41
-rwxr-xr-xChangeLog7
-rwxr-xr-xINSTALL237
-rwxr-xr-xMakefile715
-rwxr-xr-xMakefile.am66
-rwxr-xr-xMakefile.in722
-rwxr-xr-xNEWS5
-rwxr-xr-xREADME68
-rwxr-xr-xaclocal.m47723
-rwxr-xr-xautogen.sh22
-rwxr-xr-xcompile142
-rwxr-xr-xconfig.guess1536
-rwxr-xr-xconfig.h361
-rwxr-xr-xconfig.h.in350
-rwxr-xr-xconfig.log3290
-rwxr-xr-xconfig.status1220
-rwxr-xr-xconfig.sub1658
-rwxr-xr-xconfigure25302
-rwxr-xr-xconfigure.ac441
-rwxr-xr-xdepcomp589
-rwxr-xr-xinclude/Makefile.am4
-rwxr-xr-xinclude/Makefile.in503
-rwxr-xr-xinclude/fuse-lite/Makefile.am10
-rwxr-xr-xinclude/fuse-lite/Makefile.in400
-rwxr-xr-xinclude/fuse-lite/fuse.h654
-rwxr-xr-xinclude/fuse-lite/fuse_common.h193
-rwxr-xr-xinclude/fuse-lite/fuse_kernel.h422
-rwxr-xr-xinclude/fuse-lite/fuse_lowlevel.h1375
-rwxr-xr-xinclude/fuse-lite/fuse_lowlevel_compat.h16
-rwxr-xr-xinclude/fuse-lite/fuse_opt.h261
-rwxr-xr-xinclude/ntfs-3g/Makefile.am45
-rwxr-xr-xinclude/ntfs-3g/Makefile.in471
-rwxr-xr-xinclude/ntfs-3g/acls.h199
-rwxr-xr-xinclude/ntfs-3g/attrib.h358
-rwxr-xr-xinclude/ntfs-3g/attrlist.h51
-rwxr-xr-xinclude/ntfs-3g/bitmap.h96
-rwxr-xr-xinclude/ntfs-3g/bootsect.h42
-rwxr-xr-xinclude/ntfs-3g/cache.h115
-rwxr-xr-xinclude/ntfs-3g/collate.h34
-rwxr-xr-xinclude/ntfs-3g/compat.h69
-rwxr-xr-xinclude/ntfs-3g/compress.h39
-rwxr-xr-xinclude/ntfs-3g/debug.h47
-rwxr-xr-xinclude/ntfs-3g/device.h128
-rwxr-xr-xinclude/ntfs-3g/device_io.h77
-rwxr-xr-xinclude/ntfs-3g/dir.h128
-rwxr-xr-xinclude/ntfs-3g/efs.h30
-rwxr-xr-xinclude/ntfs-3g/endians.h203
-rwxr-xr-xinclude/ntfs-3g/index.h167
-rwxr-xr-xinclude/ntfs-3g/inode.h225
-rwxr-xr-xinclude/ntfs-3g/layout.h2661
-rwxr-xr-xinclude/ntfs-3g/lcnalloc.h50
-rwxr-xr-xinclude/ntfs-3g/logfile.h394
-rwxr-xr-xinclude/ntfs-3g/logging.h118
-rwxr-xr-xinclude/ntfs-3g/mft.h132
-rwxr-xr-xinclude/ntfs-3g/misc.h30
-rwxr-xr-xinclude/ntfs-3g/mst.h34
-rwxr-xr-xinclude/ntfs-3g/ntfstime.h131
-rwxr-xr-xinclude/ntfs-3g/object_id.h35
-rwxr-xr-xinclude/ntfs-3g/param.h63
-rwxr-xr-xinclude/ntfs-3g/reparse.h39
-rwxr-xr-xinclude/ntfs-3g/runlist.h89
-rwxr-xr-xinclude/ntfs-3g/security.h353
-rwxr-xr-xinclude/ntfs-3g/support.h85
-rwxr-xr-xinclude/ntfs-3g/types.h124
-rwxr-xr-xinclude/ntfs-3g/unistr.h116
-rwxr-xr-xinclude/ntfs-3g/volume.h277
-rwxr-xr-xinstall-sh519
-rwxr-xr-xlibfuse-lite/Makefile.am28
-rwxr-xr-xlibfuse-lite/Makefile.in572
-rwxr-xr-xlibfuse-lite/fuse.c2789
-rwxr-xr-xlibfuse-lite/fuse_i.h25
-rwxr-xr-xlibfuse-lite/fuse_kern_chan.c96
-rwxr-xr-xlibfuse-lite/fuse_loop.c40
-rwxr-xr-xlibfuse-lite/fuse_lowlevel.c1395
-rwxr-xr-xlibfuse-lite/fuse_misc.h106
-rwxr-xr-xlibfuse-lite/fuse_opt.c368
-rwxr-xr-xlibfuse-lite/fuse_session.c183
-rwxr-xr-xlibfuse-lite/fuse_signals.c73
-rwxr-xr-xlibfuse-lite/fusermount.c772
-rwxr-xr-xlibfuse-lite/helper.c40
-rwxr-xr-xlibfuse-lite/mount.c256
-rwxr-xr-xlibfuse-lite/mount_util.c219
-rwxr-xr-xlibfuse-lite/mount_util.h22
-rwxr-xr-xlibntfs-3g/Makefile.am79
-rwxr-xr-xlibntfs-3g/Makefile.in841
-rwxr-xr-xlibntfs-3g/acls.c4296
-rwxr-xr-xlibntfs-3g/attrib.c5913
-rwxr-xr-xlibntfs-3g/attrlist.c314
-rwxr-xr-xlibntfs-3g/bitmap.c300
-rwxr-xr-xlibntfs-3g/bootsect.c285
-rwxr-xr-xlibntfs-3g/cache.c609
-rwxr-xr-xlibntfs-3g/collate.c271
-rwxr-xr-xlibntfs-3g/compat.c250
-rwxr-xr-xlibntfs-3g/compress.c1431
-rwxr-xr-xlibntfs-3g/debug.c79
-rwxr-xr-xlibntfs-3g/device.c730
-rwxr-xr-xlibntfs-3g/dir.c2582
-rwxr-xr-xlibntfs-3g/efs.c346
-rwxr-xr-xlibntfs-3g/index.c2063
-rwxr-xr-xlibntfs-3g/inode.c1566
-rwxr-xr-xlibntfs-3g/lcnalloc.c735
-rwxr-xr-xlibntfs-3g/libntfs-3g.pc.in10
-rwxr-xr-xlibntfs-3g/libntfs-3g.script.so.in2
-rwxr-xr-xlibntfs-3g/logfile.c737
-rwxr-xr-xlibntfs-3g/logging.c613
-rwxr-xr-xlibntfs-3g/mft.c1909
-rwxr-xr-xlibntfs-3g/misc.c61
-rwxr-xr-xlibntfs-3g/mst.c231
-rwxr-xr-xlibntfs-3g/object_id.c637
-rwxr-xr-xlibntfs-3g/reparse.c1222
-rwxr-xr-xlibntfs-3g/runlist.c2166
-rwxr-xr-xlibntfs-3g/security.c5167
-rwxr-xr-xlibntfs-3g/unistr.c1321
-rwxr-xr-xlibntfs-3g/unix_io.c386
-rwxr-xr-xlibntfs-3g/volume.c1663
-rwxr-xr-xlibntfs-3g/win32_io.c1477
-rwxr-xr-xlibtool7621
-rwxr-xr-xltmain.sh6956
-rwxr-xr-xm4/.keep0
-rwxr-xr-xmissing367
-rwxr-xr-xprog.IAB1071
-rwxr-xr-xprog.IAD5
-rwxr-xr-xprog.IMB466
-rwxr-xr-xprog.IMD2
-rwxr-xr-xprog.PFI2
-rwxr-xr-xprog.PO1
-rwxr-xr-xprog.PR14
-rwxr-xr-xprog.PRI219
-rwxr-xr-xprog.PS979
-rwxr-xr-xprog.SearchResults3
-rwxr-xr-xprog.WK39
-rwxr-xr-xsrc/lowntfs-3g.c4492
-rwxr-xr-xsrc/ntfs-3g.8.in349
-rwxr-xr-xsrc/ntfs-3g.c4461
-rwxr-xr-xsrc/ntfs-3g.probe.8.in81
-rwxr-xr-xsrc/ntfs-3g.probe.c163
-rwxr-xr-xsrc/ntfs-3g.secaudit.8.in171
-rwxr-xr-xsrc/ntfs-3g.usermap.8.in96
-rwxr-xr-xsrc/secaudit.c7176
-rwxr-xr-xsrc/secaudit.h731
-rwxr-xr-xsrc/test.c88
-rwxr-xr-xsrc/usermap.c1356
-rwxr-xr-xstamp-h11
148 files changed, 141286 insertions, 0 deletions
diff --git a/libntfs-3g/reparse.c b/libntfs-3g/reparse.c
new file mode 100755
index 0000000..0f6360e
--- a/dev/null
+++ b/libntfs-3g/reparse.c
@@ -0,0 +1,1222 @@
+/**
+ * reparse.c - Processing of reparse points
+ *
+ * This module is part of ntfs-3g library
+ *
+ * Copyright (c) 2008-2009 Jean-Pierre Andre
+ *
+ * This program/include file is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program/include file is distributed in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program (in the main directory of the NTFS-3G
+ * distribution in the file COPYING); if not, write to the Free Software
+ * Foundation,Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#ifdef HAVE_STRING_H
+#include <string.h>
+#endif
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+
+#ifdef HAVE_SETXATTR
+#include <sys/xattr.h>
+#endif
+
+#ifdef HAVE_SYS_SYSMACROS_H
+#include <sys/sysmacros.h>
+#endif
+
+#include "types.h"
+#include "debug.h"
+#include "attrib.h"
+#include "inode.h"
+#include "dir.h"
+#include "volume.h"
+#include "mft.h"
+#include "index.h"
+#include "lcnalloc.h"
+#include "logging.h"
+#include "misc.h"
+#include "reparse.h"
+
+/* the definitions in layout.h are wrong, we use names defined in
+ http://msdn.microsoft.com/en-us/library/aa365740(VS.85).aspx
+*/
+
+#define IO_REPARSE_TAG_DFS const_cpu_to_le32(0x8000000A)
+#define IO_REPARSE_TAG_DFSR const_cpu_to_le32(0x80000012)
+#define IO_REPARSE_TAG_HSM const_cpu_to_le32(0xC0000004)
+#define IO_REPARSE_TAG_HSM2 const_cpu_to_le32(0x80000006)
+#define IO_REPARSE_TAG_MOUNT_POINT const_cpu_to_le32(0xA0000003)
+#define IO_REPARSE_TAG_SIS const_cpu_to_le32(0x80000007)
+#define IO_REPARSE_TAG_SYMLINK const_cpu_to_le32(0xA000000C)
+
+struct MOUNT_POINT_REPARSE_DATA { /* reparse data for junctions */
+ le16 subst_name_offset;
+ le16 subst_name_length;
+ le16 print_name_offset;
+ le16 print_name_length;
+ char path_buffer[0]; /* above data assume this is char array */
+} ;
+
+struct SYMLINK_REPARSE_DATA { /* reparse data for symlinks */
+ le16 subst_name_offset;
+ le16 subst_name_length;
+ le16 print_name_offset;
+ le16 print_name_length;
+ le32 flags; /* 1 for full target, otherwise 0 */
+ char path_buffer[0]; /* above data assume this is char array */
+} ;
+
+struct REPARSE_INDEX { /* index entry in $Extend/$Reparse */
+ INDEX_ENTRY_HEADER header;
+ REPARSE_INDEX_KEY key;
+ le32 filling;
+} ;
+
+static const ntfschar dir_junction_head[] = {
+ const_cpu_to_le16('\\'),
+ const_cpu_to_le16('?'),
+ const_cpu_to_le16('?'),
+ const_cpu_to_le16('\\')
+} ;
+
+static const ntfschar vol_junction_head[] = {
+ const_cpu_to_le16('\\'),
+ const_cpu_to_le16('?'),
+ const_cpu_to_le16('?'),
+ const_cpu_to_le16('\\'),
+ const_cpu_to_le16('V'),
+ const_cpu_to_le16('o'),
+ const_cpu_to_le16('l'),
+ const_cpu_to_le16('u'),
+ const_cpu_to_le16('m'),
+ const_cpu_to_le16('e'),
+ const_cpu_to_le16('{'),
+} ;
+
+static ntfschar reparse_index_name[] = { const_cpu_to_le16('$'),
+ const_cpu_to_le16('R') };
+
+static const char mappingdir[] = ".NTFS-3G/";
+
+/*
+ * Fix a file name with doubtful case in some directory index
+ * and return the name with the casing used in directory.
+ *
+ * Should only be used to translate paths stored with case insensitivity
+ * (such as directory junctions) when no case conflict is expected.
+ * If there some ambiguity, the name which collates first is returned.
+ *
+ * The name is converted to upper case and searched the usual way.
+ * The collation rules for file names are such that we should get the
+ * first candidate if any.
+ */
+
+static u64 ntfs_fix_file_name(ntfs_inode *dir_ni, ntfschar *uname,
+ int uname_len)
+{
+ ntfs_volume *vol = dir_ni->vol;
+ ntfs_index_context *icx;
+ u64 mref;
+ le64 lemref;
+ int lkup;
+ int olderrno;
+ int i;
+ u32 cpuchar;
+ INDEX_ENTRY *entry;
+ FILE_NAME_ATTR *found;
+ struct {
+ FILE_NAME_ATTR attr;
+ ntfschar file_name[NTFS_MAX_NAME_LEN + 1];
+ } find;
+
+ mref = (u64)-1; /* default return (not found) */
+ icx = ntfs_index_ctx_get(dir_ni, NTFS_INDEX_I30, 4);
+ if (icx) {
+ if (uname_len > NTFS_MAX_NAME_LEN)
+ uname_len = NTFS_MAX_NAME_LEN;
+ find.attr.file_name_length = uname_len;
+ for (i=0; i<uname_len; i++) {
+ cpuchar = le16_to_cpu(uname[i]);
+ /*
+ * We need upper or lower value, whichever is smaller,
+ * but we can only convert to upper case, so we
+ * will fail when searching for an upper case char
+ * whose lower case is smaller (such as umlauted Y)
+ */
+ if ((cpuchar < vol->upcase_len)
+ && (le16_to_cpu(vol->upcase[cpuchar]) < cpuchar))
+ find.attr.file_name[i] = vol->upcase[cpuchar];
+ else
+ find.attr.file_name[i] = uname[i];
+ }
+ olderrno = errno;
+ lkup = ntfs_index_lookup((char*)&find, uname_len, icx);
+ if (errno == ENOENT)
+ errno = olderrno;
+ /*
+ * We generally only get the first matching candidate,
+ * so we still have to check whether this is a real match
+ */
+ if (icx->entry && (icx->entry->ie_flags & INDEX_ENTRY_END))
+ /* get next entry if reaching end of block */
+ entry = ntfs_index_next(icx->entry, icx);
+ else
+ entry = icx->entry;
+ if (entry) {
+ found = &entry->key.file_name;
+ if (lkup
+ && ntfs_names_are_equal(find.attr.file_name,
+ find.attr.file_name_length,
+ found->file_name, found->file_name_length,
+ IGNORE_CASE,
+ vol->upcase, vol->upcase_len))
+ lkup = 0;
+ if (!lkup) {
+ /*
+ * name found :
+ * fix original name and return inode
+ */
+ lemref = entry->indexed_file;
+ mref = le64_to_cpu(lemref);
+ for (i=0; i<found->file_name_length; i++)
+ uname[i] = found->file_name[i];
+ }
+ }
+ ntfs_index_ctx_put(icx);
+ }
+ return (mref);
+}
+
+/*
+ * Search for a directory junction or a symbolic link
+ * along the target path, with target defined as a full absolute path
+ *
+ * Returns the path translated to a Linux path
+ * or NULL if the path is not valid
+ */
+
+static char *search_absolute(ntfs_volume *vol, ntfschar *path,
+ int count, BOOL isdir)
+{
+ ntfs_inode *ni;
+ u64 inum;
+ char *target;
+ int start;
+ int len;
+
+ target = (char*)NULL; /* default return */
+ ni = ntfs_inode_open(vol, (MFT_REF)FILE_root);
+ if (ni) {
+ start = 0;
+ do {
+ len = 0;
+ while (((start + len) < count)
+ && (path[start + len] != const_cpu_to_le16('\\')))
+ len++;
+ inum = ntfs_fix_file_name(ni, &path[start], len);
+ ntfs_inode_close(ni);
+ ni = (ntfs_inode*)NULL;
+ if (inum != (u64)-1) {
+ inum = MREF(inum);
+ ni = ntfs_inode_open(vol, inum);
+ start += len;
+ if (start < count)
+ path[start++] = const_cpu_to_le16('/');
+ }
+ } while (ni
+ && (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY)
+ && (start < count));
+ if (ni
+ && (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY ? isdir : !isdir))
+ if (ntfs_ucstombs(path, count, &target, 0) < 0) {
+ if (target) {
+ free(target);
+ target = (char*)NULL;
+ }
+ }
+ if (ni)
+ ntfs_inode_close(ni);
+ }
+ return (target);
+}
+
+/*
+ * Search for a symbolic link along the target path,
+ * with the target defined as a relative path
+ *
+ * Note : the path used to access the current inode, may be
+ * different from the one implied in the target definition,
+ * when an inode has names in several directories.
+ *
+ * Returns the path translated to a Linux path
+ * or NULL if the path is not valid
+ */
+
+static char *search_relative(ntfs_inode *ni, ntfschar *path, int count)
+{
+ char *target = (char*)NULL;
+ ntfs_inode *curni;
+ ntfs_inode *newni;
+ u64 inum;
+ int pos;
+ int lth;
+ BOOL ok;
+ int max = 32; /* safety */
+
+ pos = 0;
+ ok = TRUE;
+ curni = ntfs_dir_parent_inode(ni);
+ while (curni && ok && (pos < (count - 1)) && --max) {
+ if ((count >= (pos + 2))
+ && (path[pos] == const_cpu_to_le16('.'))
+ && (path[pos+1] == const_cpu_to_le16('\\'))) {
+ path[1] = const_cpu_to_le16('/');
+ pos += 2;
+ } else {
+ if ((count >= (pos + 3))
+ && (path[pos] == const_cpu_to_le16('.'))
+ &&(path[pos+1] == const_cpu_to_le16('.'))
+ && (path[pos+2] == const_cpu_to_le16('\\'))) {
+ path[2] = const_cpu_to_le16('/');
+ pos += 3;
+ newni = ntfs_dir_parent_inode(curni);
+ if (curni != ni)
+ ntfs_inode_close(curni);
+ curni = newni;
+ if (!curni)
+ ok = FALSE;
+ } else {
+ lth = 0;
+ while (((pos + lth) < count)
+ && (path[pos + lth] != const_cpu_to_le16('\\')))
+ lth++;
+ if (lth > 0)
+ inum = ntfs_fix_file_name(curni,&path[pos],lth);
+ else
+ inum = (u64)-1;
+ if (!lth
+ || ((curni != ni)
+ && ntfs_inode_close(curni))
+ || (inum == (u64)-1))
+ ok = FALSE;
+ else {
+ curni = ntfs_inode_open(ni->vol, MREF(inum));
+ if (!curni)
+ ok = FALSE;
+ else {
+ if (ok && ((pos + lth) < count)) {
+ path[pos + lth] = const_cpu_to_le16('/');
+ pos += lth + 1;
+ } else {
+ pos += lth;
+ if ((ni->mrec->flags ^ curni->mrec->flags)
+ & MFT_RECORD_IS_DIRECTORY)
+ ok = FALSE;
+ if (ntfs_inode_close(curni))
+ ok = FALSE;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (ok && (ntfs_ucstombs(path, count, &target, 0) < 0)) {
+ free(target); // needed ?
+ target = (char*)NULL;
+ }
+ return (target);
+}
+
+/*
+ * Check whether a drive letter has been defined in .NTFS-3G
+ *
+ * Returns 1 if found,
+ * 0 if not found,
+ * -1 if there was an error (described by errno)
+ */
+
+static int ntfs_drive_letter(ntfs_volume *vol, ntfschar letter)
+{
+ char defines[NTFS_MAX_NAME_LEN + 5];
+ char *drive;
+ int ret;
+ int sz;
+ int olderrno;
+ ntfs_inode *ni;
+
+ ret = -1;
+ drive = (char*)NULL;
+ sz = ntfs_ucstombs(&letter, 1, &drive, 0);
+ if (sz > 0) {
+ strcpy(defines,mappingdir);
+ if ((*drive >= 'a') && (*drive <= 'z'))
+ *drive += 'A' - 'a';
+ strcat(defines,drive);
+ strcat(defines,":");
+ olderrno = errno;
+ ni = ntfs_pathname_to_inode(vol, NULL, defines);
+ if (ni && !ntfs_inode_close(ni))
+ ret = 1;
+ else
+ if (errno == ENOENT) {
+ ret = 0;
+ /* avoid errno pollution */
+ errno = olderrno;
+ }
+ }
+ if (drive)
+ free(drive);
+ return (ret);
+}
+
+/*
+ * Do some sanity checks on reparse data
+ *
+ * The only general check is about the size (at least the tag must
+ * be present)
+ * If the reparse data looks like a junction point or symbolic
+ * link, more checks can be done.
+ *
+ */
+
+static BOOL valid_reparse_data(ntfs_inode *ni,
+ const REPARSE_POINT *reparse_attr, size_t size)
+{
+ BOOL ok;
+ unsigned int offs;
+ unsigned int lth;
+ const struct MOUNT_POINT_REPARSE_DATA *mount_point_data;
+ const struct SYMLINK_REPARSE_DATA *symlink_data;
+
+ ok = ni && reparse_attr
+ && (size >= sizeof(REPARSE_POINT))
+ && (((size_t)le16_to_cpu(reparse_attr->reparse_data_length)
+ + sizeof(REPARSE_POINT)) == size);
+ if (ok) {
+ switch (reparse_attr->reparse_tag) {
+ case IO_REPARSE_TAG_MOUNT_POINT :
+ mount_point_data = (const struct MOUNT_POINT_REPARSE_DATA*)
+ reparse_attr->reparse_data;
+ offs = le16_to_cpu(mount_point_data->subst_name_offset);
+ lth = le16_to_cpu(mount_point_data->subst_name_length);
+ /* consistency checks */
+ if (!(ni->mrec->flags & MFT_RECORD_IS_DIRECTORY)
+ || ((size_t)((sizeof(REPARSE_POINT)
+ + sizeof(struct MOUNT_POINT_REPARSE_DATA)
+ + offs + lth)) > size))
+ ok = FALSE;
+ break;
+ case IO_REPARSE_TAG_SYMLINK :
+ symlink_data = (const struct SYMLINK_REPARSE_DATA*)
+ reparse_attr->reparse_data;
+ offs = le16_to_cpu(symlink_data->subst_name_offset);
+ lth = le16_to_cpu(symlink_data->subst_name_length);
+ if ((size_t)((sizeof(REPARSE_POINT)
+ + sizeof(struct SYMLINK_REPARSE_DATA)
+ + offs + lth)) > size)
+ ok = FALSE;
+ break;
+ default :
+ break;
+ }
+ }
+ if (!ok)
+ errno = EINVAL;
+ return (ok);
+}
+
+/*
+ * Check and translate the target of a junction point or
+ * a full absolute symbolic link.
+ *
+ * A full target definition begins with "\??\" or "\\?\"
+ *
+ * The fully defined target is redefined as a relative link,
+ * - either to the target if found on the same device.
+ * - or into the /.NTFS-3G directory for the user to define
+ * In the first situation, the target is translated to case-sensitive path.
+ *
+ * returns the target converted to a relative symlink
+ * or NULL if there were some problem, as described by errno
+ */
+
+static char *ntfs_get_fulllink(ntfs_volume *vol, ntfschar *junction,
+ int count, const char *mnt_point, BOOL isdir)
+{
+ char *target;
+ char *fulltarget;
+ int sz;
+ char *q;
+ enum { DIR_JUNCTION, VOL_JUNCTION, NO_JUNCTION } kind;
+
+ target = (char*)NULL;
+ fulltarget = (char*)NULL;
+ /*
+ * For a valid directory junction we want \??\x:\
+ * where \ is an individual char and x a non-null char
+ */
+ if ((count >= 7)
+ && !memcmp(junction,dir_junction_head,8)
+ && junction[4]
+ && (junction[5] == const_cpu_to_le16(':'))
+ && (junction[6] == const_cpu_to_le16('\\')))
+ kind = DIR_JUNCTION;
+ else
+ /*
+ * For a valid volume junction we want \\?\Volume{
+ * and a final \ (where \ is an individual char)
+ */
+ if ((count >= 12)
+ && !memcmp(junction,vol_junction_head,22)
+ && (junction[count-1] == const_cpu_to_le16('\\')))
+ kind = VOL_JUNCTION;
+ else
+ kind = NO_JUNCTION;
+ /*
+ * Directory junction with an explicit path and
+ * no specific definition for the drive letter :
+ * try to interpret as a target on the same volume
+ */
+ if ((kind == DIR_JUNCTION)
+ && (count >= 7)
+ && junction[7]
+ && !ntfs_drive_letter(vol, junction[4])) {
+ target = search_absolute(vol,&junction[7],count - 7, isdir);
+ if (target) {
+ fulltarget = (char*)ntfs_malloc(strlen(mnt_point)
+ + strlen(target) + 2);
+ if (fulltarget) {
+ strcpy(fulltarget,mnt_point);
+ strcat(fulltarget,"/");
+ strcat(fulltarget,target);
+ }
+ free(target);
+ }
+ }
+ /*
+ * Volume junctions or directory junctions with
+ * target not found on current volume :
+ * link to /.NTFS-3G/target which the user can
+ * define as a symbolic link to the real target
+ */
+ if (((kind == DIR_JUNCTION) && !fulltarget)
+ || (kind == VOL_JUNCTION)) {
+ sz = ntfs_ucstombs(&junction[4],
+ (kind == VOL_JUNCTION ? count - 5 : count - 4),
+ &target, 0);
+ if ((sz > 0) && target) {
+ /* reverse slashes */
+ for (q=target; *q; q++)
+ if (*q == '\\')
+ *q = '/';
+ /* force uppercase drive letter */
+ if ((target[1] == ':')
+ && (target[0] >= 'a')
+ && (target[0] <= 'z'))
+ target[0] += 'A' - 'a';
+ fulltarget = (char*)ntfs_malloc(strlen(mnt_point)
+ + sizeof(mappingdir) + strlen(target) + 1);
+ if (fulltarget) {
+ strcpy(fulltarget,mnt_point);
+ strcat(fulltarget,"/");
+ strcat(fulltarget,mappingdir);
+ strcat(fulltarget,target);
+ }
+ }
+ if (target)
+ free(target);
+ }
+ return (fulltarget);
+}
+
+/*
+ * Check and translate the target of an absolute symbolic link.
+ *
+ * An absolute target definition begins with "\" or "x:\"
+ *
+ * The absolute target is redefined as a relative link,
+ * - either to the target if found on the same device.
+ * - or into the /.NTFS-3G directory for the user to define
+ * In the first situation, the target is translated to case-sensitive path.
+ *
+ * returns the target converted to a relative symlink
+ * or NULL if there were some problem, as described by errno
+ */
+
+static char *ntfs_get_abslink(ntfs_volume *vol, ntfschar *junction,
+ int count, const char *mnt_point, BOOL isdir)
+{
+ char *target;
+ char *fulltarget;
+ int sz;
+ char *q;
+ enum { FULL_PATH, ABS_PATH, REJECTED_PATH } kind;
+
+ target = (char*)NULL;
+ fulltarget = (char*)NULL;
+ /*
+ * For a full valid path we want x:\
+ * where \ is an individual char and x a non-null char
+ */
+ if ((count >= 3)
+ && junction[0]
+ && (junction[1] == const_cpu_to_le16(':'))
+ && (junction[2] == const_cpu_to_le16('\\')))
+ kind = FULL_PATH;
+ else
+ /*
+ * For an absolute path we want an initial \
+ */
+ if ((count >= 0)
+ && (junction[0] == const_cpu_to_le16('\\')))
+ kind = ABS_PATH;
+ else
+ kind = REJECTED_PATH;
+ /*
+ * Full path, with a drive letter and
+ * no specific definition for the drive letter :
+ * try to interpret as a target on the same volume.
+ * Do the same for an abs path with no drive letter.
+ */
+ if (((kind == FULL_PATH)
+ && (count >= 3)
+ && junction[3]
+ && !ntfs_drive_letter(vol, junction[0]))
+ || (kind == ABS_PATH)) {
+ if (kind == ABS_PATH)
+ target = search_absolute(vol, &junction[1],
+ count - 1, isdir);
+ else
+ target = search_absolute(vol, &junction[3],
+ count - 3, isdir);
+ if (target) {
+ fulltarget = (char*)ntfs_malloc(strlen(mnt_point)
+ + strlen(target) + 2);
+ if (fulltarget) {
+ strcpy(fulltarget,mnt_point);
+ strcat(fulltarget,"/");
+ strcat(fulltarget,target);
+ }
+ free(target);
+ }
+ }
+ /*
+ * full path with target not found on current volume :
+ * link to /.NTFS-3G/target which the user can
+ * define as a symbolic link to the real target
+ */
+ if ((kind == FULL_PATH) && !fulltarget) {
+ sz = ntfs_ucstombs(&junction[0],
+ count,&target, 0);
+ if ((sz > 0) && target) {
+ /* reverse slashes */
+ for (q=target; *q; q++)
+ if (*q == '\\')
+ *q = '/';
+ /* force uppercase drive letter */
+ if ((target[1] == ':')
+ && (target[0] >= 'a')
+ && (target[0] <= 'z'))
+ target[0] += 'A' - 'a';
+ fulltarget = (char*)ntfs_malloc(strlen(mnt_point)
+ + sizeof(mappingdir) + strlen(target) + 1);
+ if (fulltarget) {
+ strcpy(fulltarget,mnt_point);
+ strcat(fulltarget,"/");
+ strcat(fulltarget,mappingdir);
+ strcat(fulltarget,target);
+ }
+ }
+ if (target)
+ free(target);
+ }
+ return (fulltarget);
+}
+
+/*
+ * Check and translate the target of a relative symbolic link.
+ *
+ * A relative target definition does not begin with "\"
+ *
+ * The original definition of relative target is kept, it is just
+ * translated to a case-sensitive path.
+ *
+ * returns the target converted to a relative symlink
+ * or NULL if there were some problem, as described by errno
+ */
+
+static char *ntfs_get_rellink(ntfs_inode *ni, ntfschar *junction, int count)
+{
+ char *target;
+
+ target = search_relative(ni,junction,count);
+ return (target);
+}
+
+/*
+ * Get the target for a junction point or symbolic link
+ * Should only be called for files or directories with reparse data
+ *
+ * returns the target converted to a relative path, or NULL
+ * if some error occurred, as described by errno
+ * errno is EOPNOTSUPP if the reparse point is not a valid
+ * symbolic link or directory junction
+ */
+
+char *ntfs_make_symlink(ntfs_inode *ni, const char *mnt_point,
+ int *pattr_size)
+{
+ s64 attr_size = 0;
+ char *target;
+ unsigned int offs;
+ unsigned int lth;
+ ntfs_volume *vol;
+ REPARSE_POINT *reparse_attr;
+ struct MOUNT_POINT_REPARSE_DATA *mount_point_data;
+ struct SYMLINK_REPARSE_DATA *symlink_data;
+ enum { FULL_TARGET, ABS_TARGET, REL_TARGET } kind;
+ ntfschar *p;
+ BOOL bad;
+ BOOL isdir;
+
+ target = (char*)NULL;
+ bad = TRUE;
+ isdir = (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY)
+ != const_cpu_to_le16(0);
+ vol = ni->vol;
+ reparse_attr = (REPARSE_POINT*)ntfs_attr_readall(ni,
+ AT_REPARSE_POINT,(ntfschar*)NULL, 0, &attr_size);
+ if (reparse_attr && attr_size
+ && valid_reparse_data(ni, reparse_attr, attr_size)) {
+ switch (reparse_attr->reparse_tag) {
+ case IO_REPARSE_TAG_MOUNT_POINT :
+ mount_point_data = (struct MOUNT_POINT_REPARSE_DATA*)
+ reparse_attr->reparse_data;
+ offs = le16_to_cpu(mount_point_data->subst_name_offset);
+ lth = le16_to_cpu(mount_point_data->subst_name_length);
+ /* reparse data consistency has been checked */
+ target = ntfs_get_fulllink(vol,
+ (ntfschar*)&mount_point_data->path_buffer[offs],
+ lth/2, mnt_point, isdir);
+ if (target)
+ bad = FALSE;
+ break;
+ case IO_REPARSE_TAG_SYMLINK :
+ symlink_data = (struct SYMLINK_REPARSE_DATA*)
+ reparse_attr->reparse_data;
+ offs = le16_to_cpu(symlink_data->subst_name_offset);
+ lth = le16_to_cpu(symlink_data->subst_name_length);
+ p = (ntfschar*)&symlink_data->path_buffer[offs];
+ /*
+ * Predetermine the kind of target,
+ * the called function has to make a full check
+ */
+ if (*p++ == const_cpu_to_le16('\\')) {
+ if ((*p == const_cpu_to_le16('?'))
+ || (*p == const_cpu_to_le16('\\')))
+ kind = FULL_TARGET;
+ else
+ kind = ABS_TARGET;
+ } else
+ if (*p == const_cpu_to_le16(':'))
+ kind = ABS_TARGET;
+ else
+ kind = REL_TARGET;
+ p--;
+ /* reparse data consistency has been checked */
+ switch (kind) {
+ case FULL_TARGET :
+ if (!(symlink_data->flags
+ & const_cpu_to_le32(1))) {
+ target = ntfs_get_fulllink(vol,
+ p, lth/2,
+ mnt_point, isdir);
+ if (target)
+ bad = FALSE;
+ }
+ break;
+ case ABS_TARGET :
+ if (symlink_data->flags
+ & const_cpu_to_le32(1)) {
+ target = ntfs_get_abslink(vol,
+ p, lth/2,
+ mnt_point, isdir);
+ if (target)
+ bad = FALSE;
+ }
+ break;
+ case REL_TARGET :
+ if (symlink_data->flags
+ & const_cpu_to_le32(1)) {
+ target = ntfs_get_rellink(ni,
+ p, lth/2);
+ if (target)
+ bad = FALSE;
+ }
+ break;
+ }
+ break;
+ }
+ free(reparse_attr);
+ }
+ *pattr_size = attr_size;
+ if (bad)
+ errno = EOPNOTSUPP;
+ return (target);
+}
+
+/*
+ * Check whether a reparse point looks like a junction point
+ * or a symbolic link.
+ * Should only be called for files or directories with reparse data
+ *
+ * The validity of the target is not checked.
+ */
+
+BOOL ntfs_possible_symlink(ntfs_inode *ni)
+{
+ s64 attr_size = 0;
+ REPARSE_POINT *reparse_attr;
+ BOOL possible;
+
+ possible = FALSE;
+ reparse_attr = (REPARSE_POINT*)ntfs_attr_readall(ni,
+ AT_REPARSE_POINT,(ntfschar*)NULL, 0, &attr_size);
+ if (reparse_attr && attr_size) {
+ switch (reparse_attr->reparse_tag) {
+ case IO_REPARSE_TAG_MOUNT_POINT :
+ case IO_REPARSE_TAG_SYMLINK :
+ possible = TRUE;
+ default : ;
+ }
+ free(reparse_attr);
+ }
+ return (possible);
+}
+
+#ifdef HAVE_SETXATTR /* extended attributes interface required */
+
+/*
+ * Set the index for new reparse data
+ *
+ * Returns 0 if success
+ * -1 if failure, explained by errno
+ */
+
+static int set_reparse_index(ntfs_inode *ni, ntfs_index_context *xr,
+ le32 reparse_tag)
+{
+ struct REPARSE_INDEX indx;
+ u64 file_id_cpu;
+ le64 file_id;
+ le16 seqn;
+
+ seqn = ni->mrec->sequence_number;
+ file_id_cpu = MK_MREF(ni->mft_no,le16_to_cpu(seqn));
+ file_id = cpu_to_le64(file_id_cpu);
+ indx.header.data_offset = const_cpu_to_le16(
+ sizeof(INDEX_ENTRY_HEADER)
+ + sizeof(REPARSE_INDEX_KEY));
+ indx.header.data_length = const_cpu_to_le16(0);
+ indx.header.reservedV = const_cpu_to_le32(0);
+ indx.header.length = const_cpu_to_le16(
+ sizeof(struct REPARSE_INDEX));
+ indx.header.key_length = const_cpu_to_le16(
+ sizeof(REPARSE_INDEX_KEY));
+ indx.header.flags = const_cpu_to_le16(0);
+ indx.header.reserved = const_cpu_to_le16(0);
+ indx.key.reparse_tag = reparse_tag;
+ /* danger on processors which require proper alignment ! */
+ memcpy(&indx.key.file_id, &file_id, 8);
+ indx.filling = const_cpu_to_le32(0);
+ ntfs_index_ctx_reinit(xr);
+ return (ntfs_ie_add(xr,(INDEX_ENTRY*)&indx));
+}
+
+#endif /* HAVE_SETXATTR */
+
+/*
+ * Remove a reparse data index entry if attribute present
+ *
+ * Returns the size of existing reparse data
+ * (the existing reparse tag is returned)
+ * -1 if failure, explained by errno
+ */
+
+static int remove_reparse_index(ntfs_attr *na, ntfs_index_context *xr,
+ le32 *preparse_tag)
+{
+ REPARSE_INDEX_KEY key;
+ u64 file_id_cpu;
+ le64 file_id;
+ s64 size;
+ le16 seqn;
+ int ret;
+
+ ret = na->data_size;
+ if (ret) {
+ /* read the existing reparse_tag */
+ size = ntfs_attr_pread(na, 0, 4, preparse_tag);
+ if (size == 4) {
+ seqn = na->ni->mrec->sequence_number;
+ file_id_cpu = MK_MREF(na->ni->mft_no,le16_to_cpu(seqn));
+ file_id = cpu_to_le64(file_id_cpu);
+ key.reparse_tag = *preparse_tag;
+ /* danger on processors which require proper alignment ! */
+ memcpy(&key.file_id, &file_id, 8);
+ if (!ntfs_index_lookup(&key, sizeof(REPARSE_INDEX_KEY), xr)
+ && ntfs_index_rm(xr))
+ ret = -1;
+ } else {
+ ret = -1;
+ errno = ENODATA;
+ }
+ }
+ return (ret);
+}
+
+/*
+ * Open the $Extend/$Reparse file and its index
+ *
+ * Return the index context if opened
+ * or NULL if an error occurred (errno tells why)
+ *
+ * The index has to be freed and inode closed when not needed any more.
+ */
+
+static ntfs_index_context *open_reparse_index(ntfs_volume *vol)
+{
+ u64 inum;
+ ntfs_inode *ni;
+ ntfs_inode *dir_ni;
+ ntfs_index_context *xr;
+
+ /* do not use path_name_to inode - could reopen root */
+ dir_ni = ntfs_inode_open(vol, FILE_Extend);
+ ni = (ntfs_inode*)NULL;
+ if (dir_ni) {
+ inum = ntfs_inode_lookup_by_mbsname(dir_ni,"$Reparse");
+ if (inum != (u64)-1)
+ ni = ntfs_inode_open(vol, inum);
+ ntfs_inode_close(dir_ni);
+ }
+ if (ni) {
+ xr = ntfs_index_ctx_get(ni, reparse_index_name, 2);
+ if (!xr) {
+ ntfs_inode_close(ni);
+ }
+ } else
+ xr = (ntfs_index_context*)NULL;
+ return (xr);
+}
+
+#ifdef HAVE_SETXATTR /* extended attributes interface required */
+
+/*
+ * Update the reparse data and index
+ *
+ * The reparse data attribute should have been created, and
+ * an existing index is expected if there is an existing value.
+ *
+ * Returns 0 if success
+ * -1 if failure, explained by errno
+ * If could not remove the existing index, nothing is done,
+ * If could not write the new data, no index entry is inserted
+ * If failed to insert the index, data is removed
+ */
+
+static int update_reparse_data(ntfs_inode *ni, ntfs_index_context *xr,
+ const char *value, size_t size)
+{
+ int res;
+ int written;
+ int oldsize;
+ ntfs_attr *na;
+ le32 reparse_tag;
+
+ res = 0;
+ na = ntfs_attr_open(ni, AT_REPARSE_POINT, AT_UNNAMED, 0);
+ if (na) {
+ /* remove the existing reparse data */
+ oldsize = remove_reparse_index(na,xr,&reparse_tag);
+ if (oldsize < 0)
+ res = -1;
+ else {
+ /* resize attribute */
+ res = ntfs_attr_truncate(na, (s64)size);
+ /* overwrite value if any */
+ if (!res && value) {
+ written = (int)ntfs_attr_pwrite(na,
+ (s64)0, (s64)size, value);
+ if (written != (s64)size) {
+ ntfs_log_error("Failed to update "
+ "reparse data\n");
+ errno = EIO;
+ res = -1;
+ }
+ }
+ if (!res
+ && set_reparse_index(ni,xr,
+ ((const REPARSE_POINT*)value)->reparse_tag)
+ && (oldsize > 0)) {
+ /*
+ * If cannot index, try to remove the reparse
+ * data and log the error. There will be an
+ * inconsistency if removal fails.
+ */
+ ntfs_attr_rm(na);
+ ntfs_log_error("Failed to index reparse data."
+ " Possible corruption.\n");
+ }
+ }
+ ntfs_attr_close(na);
+ NInoSetDirty(ni);
+ } else
+ res = -1;
+ return (res);
+}
+
+#endif /* HAVE_SETXATTR */
+
+/*
+ * Delete a reparse index entry
+ *
+ * Returns 0 if success
+ * -1 if failure, explained by errno
+ */
+
+int ntfs_delete_reparse_index(ntfs_inode *ni)
+{
+ ntfs_index_context *xr;
+ ntfs_inode *xrni;
+ ntfs_attr *na;
+ le32 reparse_tag;
+ int res;
+
+ res = 0;
+ na = ntfs_attr_open(ni, AT_REPARSE_POINT, AT_UNNAMED, 0);
+ if (na) {
+ /*
+ * read the existing reparse data (the tag is enough)
+ * and un-index it
+ */
+ xr = open_reparse_index(ni->vol);
+ if (xr) {
+ if (remove_reparse_index(na,xr,&reparse_tag) < 0)
+ res = -1;
+ xrni = xr->ni;
+ ntfs_index_entry_mark_dirty(xr);
+ NInoSetDirty(xrni);
+ ntfs_index_ctx_put(xr);
+ ntfs_inode_close(xrni);
+ }
+ ntfs_attr_close(na);
+ }
+ return (res);
+}
+
+#ifdef HAVE_SETXATTR /* extended attributes interface required */
+
+/*
+ * Get the ntfs reparse data into an extended attribute
+ *
+ * Returns the reparse data size
+ * and the buffer is updated if it is long enough
+ */
+
+int ntfs_get_ntfs_reparse_data(ntfs_inode *ni, char *value, size_t size)
+{
+ REPARSE_POINT *reparse_attr;
+ s64 attr_size;
+
+ attr_size = 0; /* default to no data and no error */
+ if (ni) {
+ if (ni->flags & FILE_ATTR_REPARSE_POINT) {
+ reparse_attr = (REPARSE_POINT*)ntfs_attr_readall(ni,
+ AT_REPARSE_POINT,(ntfschar*)NULL, 0, &attr_size);
+ if (reparse_attr) {
+ if (attr_size <= (s64)size) {
+ if (value)
+ memcpy(value,reparse_attr,
+ attr_size);
+ else
+ errno = EINVAL;
+ }
+ free(reparse_attr);
+ }
+ } else
+ errno = ENODATA;
+ }
+ return (attr_size ? (int)attr_size : -errno);
+}
+
+/*
+ * Set the reparse data from an extended attribute
+ *
+ * Warning : the new data is not checked
+ *
+ * Returns 0, or -1 if there is a problem
+ */
+
+int ntfs_set_ntfs_reparse_data(ntfs_inode *ni,
+ const char *value, size_t size, int flags)
+{
+ int res;
+ u8 dummy;
+ ntfs_inode *xrni;
+ ntfs_index_context *xr;
+
+ res = 0;
+ if (ni && valid_reparse_data(ni, (const REPARSE_POINT*)value, size)) {
+ xr = open_reparse_index(ni->vol);
+ if (xr) {
+ if (!ntfs_attr_exist(ni,AT_REPARSE_POINT,
+ AT_UNNAMED,0)) {
+ if (!(flags & XATTR_REPLACE)) {
+ /*
+ * no reparse data attribute : add one,
+ * apparently, this does not feed the new value in
+ * Note : NTFS version must be >= 3
+ */
+ if (ni->vol->major_ver >= 3) {
+ res = ntfs_attr_add(ni,
+ AT_REPARSE_POINT,
+ AT_UNNAMED,0,&dummy,
+ (s64)0);
+ if (!res) {
+ ni->flags |=
+ FILE_ATTR_REPARSE_POINT;
+ NInoFileNameSetDirty(ni);
+ }
+ NInoSetDirty(ni);
+ } else {
+ errno = EOPNOTSUPP;
+ res = -1;
+ }
+ } else {
+ errno = ENODATA;
+ res = -1;
+ }
+ } else {
+ if (flags & XATTR_CREATE) {
+ errno = EEXIST;
+ res = -1;
+ }
+ }
+ if (!res) {
+ /* update value and index */
+ res = update_reparse_data(ni,xr,value,size);
+ }
+ xrni = xr->ni;
+ ntfs_index_entry_mark_dirty(xr);
+ NInoSetDirty(xrni);
+ ntfs_index_ctx_put(xr);
+ ntfs_inode_close(xrni);
+ } else {
+ res = -1;
+ }
+ } else {
+ errno = EINVAL;
+ res = -1;
+ }
+ return (res ? -1 : 0);
+}
+
+/*
+ * Remove the reparse data
+ *
+ * Returns 0, or -1 if there is a problem
+ */
+
+int ntfs_remove_ntfs_reparse_data(ntfs_inode *ni)
+{
+ int res;
+ int olderrno;
+ ntfs_attr *na;
+ ntfs_inode *xrni;
+ ntfs_index_context *xr;
+ le32 reparse_tag;
+
+ res = 0;
+ if (ni) {
+ /*
+ * open and delete the reparse data
+ */
+ na = ntfs_attr_open(ni, AT_REPARSE_POINT,
+ AT_UNNAMED,0);
+ if (na) {
+ /* first remove index (reparse data needed) */
+ xr = open_reparse_index(ni->vol);
+ if (xr) {
+ if (remove_reparse_index(na,xr,
+ &reparse_tag) < 0) {
+ res = -1;
+ } else {
+ /* now remove attribute */
+ res = ntfs_attr_rm(na);
+ if (!res) {
+ ni->flags &=
+ ~FILE_ATTR_REPARSE_POINT;
+ NInoFileNameSetDirty(ni);
+ } else {
+ /*
+ * If we could not remove the
+ * attribute, try to restore the
+ * index and log the error. There
+ * will be an inconsistency if
+ * the reindexing fails.
+ */
+ set_reparse_index(ni, xr,
+ reparse_tag);
+ ntfs_log_error(
+ "Failed to remove reparse data."
+ " Possible corruption.\n");
+ }
+ }
+ xrni = xr->ni;
+ ntfs_index_entry_mark_dirty(xr);
+ NInoSetDirty(xrni);
+ ntfs_index_ctx_put(xr);
+ ntfs_inode_close(xrni);
+ }
+ olderrno = errno;
+ ntfs_attr_close(na);
+ /* avoid errno pollution */
+ if (errno == ENOENT)
+ errno = olderrno;
+ } else {
+ errno = ENODATA;
+ res = -1;
+ }
+ NInoSetDirty(ni);
+ } else {
+ errno = EINVAL;
+ res = -1;
+ }
+ return (res ? -1 : 0);
+}
+
+#endif /* HAVE_SETXATTR */