⛏️ index : haiku.git

/**
 * Copyright (c) 2005-2007 Yura Pakhuchiy
 * Copyright (c) 2005 Yuval Fledel
 * Copyright (c) 2006-2009 Szabolcs Szakacsits
 * Copyright (c) 2007-2021 Jean-Pierre Andre
 * Copyright (c) 2009 Erik Larsson
 *
 * This program 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 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
 */
#include "lowntfs.h"

#include <stdlib.h>
#include <errno.h>

#include "libntfs/dir.h"
#include "libntfs/reparse.h"
#include "libntfs/misc.h"


#define DISABLE_PLUGINS
#define KERNELPERMS 1
#define INODE(ino) ino
#define ntfs_allowed_dir_access(...) (1) /* permissions checks done elsewhere */

#define set_archive(ni) (ni)->flags |= FILE_ATTR_ARCHIVE


static const char ntfs_bad_reparse[] = "unsupported reparse tag 0x%08lx";
	 /* exact length of target text, without the terminator */
#define ntfs_bad_reparse_lth (sizeof(ntfs_bad_reparse) + 2)

static const char ghostformat[] = ".ghost-ntfs-3g-%020llu";
#define GHOSTLTH 40 /* max length of a ghost file name - see ghostformat */

static u32 ntfs_sequence = 0;


static void
set_fuse_error(int *err)
{
	if (!*err)
		*err = errno;
}

static void
ntfs_fuse_update_times(ntfs_inode *ni, ntfs_time_update_flags mask)
{
#ifdef __HAIKU__
	mask &= ~NTFS_UPDATE_ATIME;
#else
	if (ctx->atime == ATIME_DISABLED)
		mask &= ~NTFS_UPDATE_ATIME;
	else if (ctx->atime == ATIME_RELATIVE && mask == NTFS_UPDATE_ATIME &&
			(sle64_to_cpu(ni->last_access_time)
				>= sle64_to_cpu(ni->last_data_change_time)) &&
			(sle64_to_cpu(ni->last_access_time)
				>= sle64_to_cpu(ni->last_mft_change_time)))
		return;
#endif
	ntfs_inode_update_times(ni, mask);
}


static BOOL ntfs_fuse_fill_security_context(struct lowntfs_context *ctx,
			struct SECURITY_CONTEXT *scx)
{
	const struct fuse_ctx *fusecontext;

	scx->vol = ctx->vol;
	scx->mapping[MAPUSERS] = NULL;
	scx->mapping[MAPGROUPS] = NULL;
	scx->pseccache = NULL;
	scx->uid = 0;
	scx->gid = 0;
	scx->tid = 0;
	scx->umask = 0;
	return (scx->mapping[MAPUSERS] != (struct MAPPING*)NULL);
}


u64
ntfs_fuse_inode_lookup(struct lowntfs_context *ctx, u64 parent, const char *name)
{
	u64 ino = (u64)-1;
	u64 inum;
	ntfs_inode *dir_ni;

	/* Open target directory. */
	dir_ni = ntfs_inode_open(ctx->vol, INODE(parent));
	if (dir_ni) {
		/* Lookup file */
		inum = ntfs_inode_lookup_by_mbsname(dir_ni, name);
			/* never return inodes 0 and 1 */
		if (MREF(inum) <= 1) {
			inum = (u64)-1;
			errno = ENOENT;
		}
		if (ntfs_inode_close(dir_ni)
			|| (inum == (u64)-1))
			ino = (u64)-1;
		else
			ino = MREF(inum);
	}
	return (ino);
}


int
ntfs_fuse_getstat(struct lowntfs_context *ctx, struct SECURITY_CONTEXT *scx,
	ntfs_inode *ni, struct stat *stbuf)
{
	int res = 0;
	ntfs_attr *na;
	BOOL withusermapping;

	memset(stbuf, 0, sizeof(struct stat));
	withusermapping = scx && (scx->mapping[MAPUSERS] != (struct MAPPING*)NULL);
	stbuf->st_nlink = le16_to_cpu(ni->mrec->link_count);
	if (ctx->posix_nlink
		&& !(ni->flags & FILE_ATTR_REPARSE_POINT))
		stbuf->st_nlink = ntfs_dir_link_cnt(ni);
	if ((ni->mrec->flags & MFT_RECORD_IS_DIRECTORY)
		|| (ni->flags & FILE_ATTR_REPARSE_POINT)) {
		if (ni->flags & FILE_ATTR_REPARSE_POINT) {
#ifndef DISABLE_PLUGINS
			const plugin_operations_t *ops;
			REPARSE_POINT *reparse;

			res = CALL_REPARSE_PLUGIN(ni, getattr, stbuf);
			if (!res) {
				apply_umask(stbuf);
			} else {
				stbuf->st_size = ntfs_bad_reparse_lth;
				stbuf->st_blocks =
					(ni->allocated_size + 511) >> 9;
				stbuf->st_mode = S_IFLNK;
				res = 0;
			}
			goto ok;
#else /* DISABLE_PLUGINS */
			char *target;

			errno = 0;
			target = ntfs_make_symlink(ni, ctx->abs_mnt_point);
				/*
				 * If the reparse point is not a valid
				 * directory junction, and there is no error
				 * we still display as a symlink
				 */
			if (target || (errno == EOPNOTSUPP)) {
				if (target)
					stbuf->st_size = strlen(target);
				else
					stbuf->st_size = ntfs_bad_reparse_lth;
				stbuf->st_blocks =
					(ni->allocated_size + 511) >> 9;
				stbuf->st_nlink =
					le16_to_cpu(ni->mrec->link_count);
				stbuf->st_mode = S_IFLNK;
				free(target);
			} else {
				res = -errno;
				goto exit;
			}
#endif /* DISABLE_PLUGINS */
		} else {
			/* Directory. */
			stbuf->st_mode = S_IFDIR | (0777 & ~ctx->dmask);
			/* get index size, if not known */
			if (!test_nino_flag(ni, KnownSize)) {
				na = ntfs_attr_open(ni, AT_INDEX_ALLOCATION,
						NTFS_INDEX_I30, 4);
				if (na) {
					ni->data_size = na->data_size;
					ni->allocated_size = na->allocated_size;
					set_nino_flag(ni, KnownSize);
					ntfs_attr_close(na);
				}
			}
			stbuf->st_size = ni->data_size;
			stbuf->st_blocks = ni->allocated_size >> 9;
			if (!ctx->posix_nlink)
				stbuf->st_nlink = 1;	/* Make find(1) work */
		}
	} else {
		/* Regular or Interix (INTX) file. */
		stbuf->st_mode = S_IFREG;
		stbuf->st_size = ni->data_size;
#ifdef HAVE_SETXATTR	/* extended attributes interface required */
		/*
		 * return data size rounded to next 512 byte boundary for
		 * encrypted files to include padding required for decryption
		 * also include 2 bytes for padding info
		*/
		if (ctx->efs_raw
			&& (ni->flags & FILE_ATTR_ENCRYPTED)
			&& ni->data_size)
			stbuf->st_size = ((ni->data_size + 511) & ~511) + 2;
#endif /* HAVE_SETXATTR */
		/*
		 * Temporary fix to make ActiveSync work via Samba 3.0.
		 * See more on the ntfs-3g-devel list.
		 */
		stbuf->st_blocks = (ni->allocated_size + 511) >> 9;
		if (ni->flags & FILE_ATTR_SYSTEM) {
			na = ntfs_attr_open(ni, AT_DATA, AT_UNNAMED, 0);
			if (!na) {
				stbuf->st_ino = ni->mft_no;
				goto nodata;
			}
			/* Check whether it's Interix FIFO or socket. */
			if (!(ni->flags & FILE_ATTR_HIDDEN)) {
				/* FIFO. */
				if (na->data_size == 0)
					stbuf->st_mode = S_IFIFO;
				/* Socket link. */
				if (na->data_size == 1)
					stbuf->st_mode = S_IFSOCK;
			}
			/*
			 * Check whether it's Interix symbolic link, block or
			 * character device.
			 */
			if ((u64)na->data_size <= sizeof(INTX_FILE_TYPES)
					+ sizeof(ntfschar) * PATH_MAX
				&& (u64)na->data_size >
					sizeof(INTX_FILE_TYPES)) {
				INTX_FILE *intx_file;

				intx_file =
					(INTX_FILE*)ntfs_malloc(na->data_size);
				if (!intx_file) {
					res = -errno;
					ntfs_attr_close(na);
					goto exit;
				}
				if (ntfs_attr_pread(na, 0, na->data_size,
						intx_file) != na->data_size) {
					res = -errno;
					free(intx_file);
					ntfs_attr_close(na);
					goto exit;
				}
				if (intx_file->magic == INTX_BLOCK_DEVICE &&
						na->data_size == (s64)offsetof(
						INTX_FILE, device_end)) {
					stbuf->st_mode = S_IFBLK;
					stbuf->st_rdev = makedev(le64_to_cpu(
							intx_file->major),
							le64_to_cpu(
							intx_file->minor));
				}
				if (intx_file->magic == INTX_CHARACTER_DEVICE &&
						na->data_size == (s64)offsetof(
						INTX_FILE, device_end)) {
					stbuf->st_mode = S_IFCHR;
					stbuf->st_rdev = makedev(le64_to_cpu(
							intx_file->major),
							le64_to_cpu(
							intx_file->minor));
				}
				if (intx_file->magic == INTX_SYMBOLIC_LINK) {
					char *target = NULL;
					int len;

					/* st_size should be set to length of
					 * symlink target as multibyte string */
					len = ntfs_ucstombs(
							intx_file->target,
							(na->data_size -
								offsetof(INTX_FILE,
									 target)) /
								   sizeof(ntfschar),
								 &target, 0);
					if (len < 0) {
						res = -errno;
						free(intx_file);
						ntfs_attr_close(na);
						goto exit;
					}
					free(target);
					stbuf->st_mode = S_IFLNK;
					stbuf->st_size = len;
				}
				free(intx_file);
			}
			ntfs_attr_close(na);
		}
		stbuf->st_mode |= (0777 & ~ctx->fmask);
	}
#ifndef DISABLE_PLUGINS
ok:
#endif /* DISABLE_PLUGINS */
	if (withusermapping) {
		ntfs_get_owner_mode(scx,ni,stbuf);
	} else {
#ifndef __HAIKU__
		stbuf->st_uid = ctx->uid;
		stbuf->st_gid = ctx->gid;
#endif
	}
	if (S_ISLNK(stbuf->st_mode))
		stbuf->st_mode |= 0777;
nodata :
	stbuf->st_ino = ni->mft_no;
#ifdef HAVE_STRUCT_STAT_ST_ATIMESPEC
	stbuf->st_atimespec = ntfs2timespec(ni->last_access_time);
	stbuf->st_ctimespec = ntfs2timespec(ni->last_mft_change_time);
	stbuf->st_mtimespec = ntfs2timespec(ni->last_data_change_time);
#elif defined(HAVE_STRUCT_STAT_ST_ATIM)
	stbuf->st_atim = ntfs2timespec(ni->last_access_time);
	stbuf->st_ctim = ntfs2timespec(ni->last_mft_change_time);
	stbuf->st_mtim = ntfs2timespec(ni->last_data_change_time);
#elif defined(HAVE_STRUCT_STAT_ST_ATIMENSEC)
	{
	struct timespec ts;

	ts = ntfs2timespec(ni->last_access_time);
	stbuf->st_atime = ts.tv_sec;
	stbuf->st_atimensec = ts.tv_nsec;
	ts = ntfs2timespec(ni->last_mft_change_time);
	stbuf->st_ctime = ts.tv_sec;
	stbuf->st_ctimensec = ts.tv_nsec;
	ts = ntfs2timespec(ni->last_data_change_time);
	stbuf->st_mtime = ts.tv_sec;
	stbuf->st_mtimensec = ts.tv_nsec;
	}
#else
#warning "No known way to set nanoseconds in struct stat !"
	{
	struct timespec ts;

	ts = ntfs2timespec(ni->last_access_time);
	stbuf->st_atime = ts.tv_sec;
	ts = ntfs2timespec(ni->last_mft_change_time);
	stbuf->st_ctime = ts.tv_sec;
	ts = ntfs2timespec(ni->last_data_change_time);
	stbuf->st_mtime = ts.tv_sec;
	}
#endif
#ifdef __HAIKU__
	stbuf->st_crtim = ntfs2timespec(ni->creation_time);
#endif
exit:
	return (res);
}


int
ntfs_fuse_readlink(struct lowntfs_context* ctx, u64 ino, void* buffer, size_t* bufferSize)
{
	ntfs_inode *ni = NULL;
	ntfs_attr *na = NULL;
	INTX_FILE *intx_file = NULL;
	char *buf = (char*)NULL;
	int res = 0;

	/* Get inode. */
	ni = ntfs_inode_open(ctx->vol, INODE(ino));
	if (!ni) {
		res = -errno;
		goto exit;
	}
		/*
		 * Reparse point : analyze as a junction point
		 */
	if (ni->flags & FILE_ATTR_REPARSE_POINT) {
		REPARSE_POINT *reparse;
		le32 tag;
		int lth;
#ifndef DISABLE_PLUGINS
		const plugin_operations_t *ops;

		res = CALL_REPARSE_PLUGIN(ni, readlink, &buf);
			/* plugin missing or reparse tag failing the check */
		if (res && ((errno == ELIBACC) || (errno == EINVAL)))
			errno = EOPNOTSUPP;
#else /* DISABLE_PLUGINS */
		errno = 0;
		res = 0;
		buf = ntfs_make_symlink(ni, ctx->abs_mnt_point);
#endif /* DISABLE_PLUGINS */
		if (!buf && (errno == EOPNOTSUPP)) {
			buf = (char*)malloc(ntfs_bad_reparse_lth + 1);
			if (buf) {
				reparse = ntfs_get_reparse_point(ni);
				if (reparse) {
					tag = reparse->reparse_tag;
					free(reparse);
				} else
					tag = const_cpu_to_le32(0);
				lth = snprintf(buf, ntfs_bad_reparse_lth + 1,
						ntfs_bad_reparse,
						(long)le32_to_cpu(tag));
				res = 0;
				if (lth != ntfs_bad_reparse_lth) {
					free(buf);
					buf = (char*)NULL;
				}
			}
		}
		if (!buf)
			res = -errno;
		goto exit;
	}
	/* Sanity checks. */
	if (!(ni->flags & FILE_ATTR_SYSTEM)) {
		res = -EINVAL;
		goto exit;
	}
	na = ntfs_attr_open(ni, AT_DATA, AT_UNNAMED, 0);
	if (!na) {
		res = -errno;
		goto exit;
	}
	if ((size_t)na->data_size <= sizeof(INTX_FILE_TYPES)) {
		res = -EINVAL;
		goto exit;
	}
	if ((size_t)na->data_size > sizeof(INTX_FILE_TYPES) +
			sizeof(ntfschar) * PATH_MAX) {
		res = -ENAMETOOLONG;
		goto exit;
	}
	/* Receive file content. */
	intx_file = (INTX_FILE*)ntfs_malloc(na->data_size);
	if (!intx_file) {
		res = -errno;
		goto exit;
	}
	if (ntfs_attr_pread(na, 0, na->data_size, intx_file) != na->data_size) {
		res = -errno;
		goto exit;
	}
	/* Sanity check. */
	if (intx_file->magic != INTX_SYMBOLIC_LINK) {
		res = -EINVAL;
		goto exit;
	}
	/* Convert link from unicode to local encoding. */
	if (ntfs_ucstombs(intx_file->target, (na->data_size -
			offsetof(INTX_FILE, target)) / sizeof(ntfschar),
			&buf, 0) < 0) {
		res = -errno;
		goto exit;
	}
exit:
	if (intx_file)
		free(intx_file);
	if (na)
		ntfs_attr_close(na);
	ntfs_inode_close(ni);

#ifdef __HAIKU__
	if (buf && !res) {
		strlcpy(buffer, buf, *bufferSize);
		*bufferSize = strlen(buf);
			// Indicate the actual length of the link.
	}
#endif

	free(buf);
	return res;
}


int
ntfs_fuse_read(ntfs_inode* ni, off_t offset, char* buf, size_t size)
{
	ntfs_attr *na = NULL;
	int res;
	s64 total = 0;
	s64 max_read;

	if (!size) {
		res = 0;
		goto exit;
	}

	if (ni->flags & FILE_ATTR_REPARSE_POINT) {
#ifndef DISABLE_PLUGINS
		const plugin_operations_t *ops;
		REPARSE_POINT *reparse;
		struct open_file *of;

		of = (struct open_file*)(long)fi->fh;
		res = CALL_REPARSE_PLUGIN(ni, read, buf, size, offset, &of->fi);
		if (res >= 0) {
			goto stamps;
		}
#else /* DISABLE_PLUGINS */
		errno = EOPNOTSUPP;
		res = errno;
#endif /* DISABLE_PLUGINS */
		goto exit;
	}
	na = ntfs_attr_open(ni, AT_DATA, AT_UNNAMED, 0);
	if (!na) {
		res = errno;
		goto exit;
	}
	max_read = na->data_size;
#ifdef HAVE_SETXATTR	/* extended attributes interface required */
	/* limit reads at next 512 byte boundary for encrypted attributes */
	if (ctx->efs_raw
		&& max_read
		&& (na->data_flags & ATTR_IS_ENCRYPTED)
		&& NAttrNonResident(na)) {
		max_read = ((na->data_size+511) & ~511) + 2;
	}
#endif /* HAVE_SETXATTR */
	if (offset + (off_t)size > max_read) {
		if (max_read < offset)
			goto ok;
		size = max_read - offset;
	}
	while (size > 0) {
		s64 ret = ntfs_attr_pread(na, offset, size, buf + total);
		if (ret != (s64)size)
			ntfs_log_perror("ntfs_attr_pread error reading inode %lld at "
				"offset %lld: %lld <> %lld", (long long)ni->mft_no,
				(long long)offset, (long long)size, (long long)ret);
		if (ret <= 0 || ret > (s64)size) {
			res = (ret < 0) ? errno : EIO;
			goto exit;
		}
		size -= ret;
		offset += ret;
		total += ret;
	}
ok:
	res = total;
#ifndef DISABLE_PLUGINS
stamps :
#endif /* DISABLE_PLUGINS */
	ntfs_fuse_update_times(ni, NTFS_UPDATE_ATIME);
exit:
	if (na)
		ntfs_attr_close(na);
	return res;
}

int
ntfs_fuse_write(struct lowntfs_context* ctx, ntfs_inode* ni, const char *buf,
	size_t size, off_t offset)
{
	ntfs_attr *na = NULL;
	int res, total = 0;

	if (ni->flags & FILE_ATTR_REPARSE_POINT) {
#ifndef DISABLE_PLUGINS
		const plugin_operations_t *ops;
		REPARSE_POINT *reparse;
		struct open_file *of;

		of = (struct open_file*)(long)fi->fh;
		res = CALL_REPARSE_PLUGIN(ni, write, buf, size, offset,
								&of->fi);
		if (res >= 0) {
			goto stamps;
		}
#else /* DISABLE_PLUGINS */
		res = EOPNOTSUPP;
		errno = EOPNOTSUPP;
#endif /* DISABLE_PLUGINS */
		goto exit;
	}
	na = ntfs_attr_open(ni, AT_DATA, AT_UNNAMED, 0);
	if (!na) {
		res = errno;
		goto exit;
	}
	while (size) {
		s64 ret = ntfs_attr_pwrite(na, offset, size, buf + total);
		if (ret <= 0) {
			res = errno;
			goto exit;
		}
		size   -= ret;
		offset += ret;
		total  += ret;
	}
	res = total;
#ifndef DISABLE_PLUGINS
stamps :
#endif /* DISABLE_PLUGINS */
	if ((res > 0)
		&& (!ctx->dmtime
		|| (sle64_to_cpu(ntfs_current_time())
			 - sle64_to_cpu(ni->last_data_change_time)) > ctx->dmtime))
		ntfs_fuse_update_times(ni, NTFS_UPDATE_MCTIME);
exit:
	if (na)
		ntfs_attr_close(na);
	if (res > 0)
		set_archive(ni);
	return res;
}

int
ntfs_fuse_create(struct lowntfs_context *ctx, ino_t parent, const char *name,
	mode_t typemode, dev_t dev, const char *target, ino_t* ino)
{
	ntfschar *uname = NULL, *utarget = NULL;
	ntfs_inode *dir_ni = NULL, *ni;
	struct open_file *of;
	int state = 0;
	le32 securid;
	gid_t gid;
	mode_t dsetgid;
	mode_t type = typemode & ~07777;
	mode_t perm;
	struct SECURITY_CONTEXT security;
	int res = 0, uname_len, utarget_len;
	const int fi = 1;

	/* Generate unicode filename. */
	uname_len = ntfs_mbstoucs(name, &uname);
	if ((uname_len < 0)
		|| (ctx->windows_names
		&& ntfs_forbidden_names(ctx->vol,uname,uname_len,TRUE))) {
		res = -errno;
		goto exit;
	}
	/* Deny creating into $Extend */
	if (parent == FILE_Extend) {
		errno = EPERM;
		res = -errno;
		goto exit;
	}
	/* Open parent directory. */
	dir_ni = ntfs_inode_open(ctx->vol, INODE(parent));
	if (!dir_ni) {
		res = -errno;
		goto exit;
	}
#if !KERNELPERMS | (POSIXACLS & !KERNELACLS)
		/* make sure parent directory is writeable and executable */
	if (!ntfs_fuse_fill_security_context(ctx, &security)
		   || ntfs_allowed_create(&security,
				dir_ni, &gid, &dsetgid)) {
#else
		ntfs_fuse_fill_security_context(ctx, &security);
		ntfs_allowed_create(&security, dir_ni, &gid, &dsetgid);
#endif
		if (S_ISDIR(type))
			perm = (typemode & ~ctx->dmask & 0777)
				| (dsetgid & S_ISGID);
		else
			if ((ctx->special_files == NTFS_FILES_WSL)
				&& S_ISLNK(type))
				perm = typemode | 0777;
			else
				perm = typemode & ~ctx->fmask & 0777;
			/*
			 * Try to get a security id available for
			 * file creation (from inheritance or argument).
			 * This is not possible for NTFS 1.x, and we will
			 * have to build a security attribute later.
			 */
		if (!security.mapping[MAPUSERS])
			securid = const_cpu_to_le32(0);
		else
			if (ctx->inherit)
				securid = ntfs_inherited_id(&security,
					dir_ni, S_ISDIR(type));
			else
#if POSIXACLS
				securid = ntfs_alloc_securid(&security,
					security.uid, gid,
					dir_ni, perm, S_ISDIR(type));
#else
				securid = ntfs_alloc_securid(&security,
					security.uid, gid,
					perm & ~security.umask, S_ISDIR(type));
#endif
		/* Create object specified in @type. */
		if (dir_ni->flags & FILE_ATTR_REPARSE_POINT) {
#ifndef DISABLE_PLUGINS
			const plugin_operations_t *ops;
			REPARSE_POINT *reparse;

			reparse = (REPARSE_POINT*)NULL;
			ops = select_reparse_plugin(ctx, dir_ni, &reparse);
			if (ops && ops->create) {
				ni = (*ops->create)(dir_ni, reparse,
					securid, uname, uname_len, type);
			} else {
				ni = (ntfs_inode*)NULL;
				errno = EOPNOTSUPP;
			}
			free(reparse);
#else /* DISABLE_PLUGINS */
			ni = (ntfs_inode*)NULL;
			errno = EOPNOTSUPP;
			res = -errno;
#endif /* DISABLE_PLUGINS */
		} else {
			switch (type) {
				case S_IFCHR:
				case S_IFBLK:
					ni = ntfs_create_device(dir_ni, securid,
							uname, uname_len,
							type, dev);
					break;
				case S_IFLNK:
					utarget_len = ntfs_mbstoucs(target,
							&utarget);
					if (utarget_len < 0) {
						res = -errno;
						goto exit;
					}
					ni = ntfs_create_symlink(dir_ni,
							securid,
							uname, uname_len,
							utarget, utarget_len);
					break;
				default:
					ni = ntfs_create(dir_ni, securid, uname,
							uname_len, type);
					break;
			}
		}
		if (ni) {
				/*
				 * set the security attribute if a security id
				 * could not be allocated (eg NTFS 1.x)
				 */
			if (security.mapping[MAPUSERS]) {
#if POSIXACLS
				if (!securid
				   && ntfs_set_inherited_posix(&security, ni,
					security.uid, gid,
					dir_ni, perm) < 0)
					set_fuse_error(&res);
#else
				if (!securid
				   && ntfs_set_owner_mode(&security, ni,
					security.uid, gid,
					perm & ~security.umask) < 0)
					set_fuse_error(&res);
#endif
			}
			set_archive(ni);
			/* mark a need to compress the end of file */
			if (fi && (ni->flags & FILE_ATTR_COMPRESSED)) {
				state |= CLOSE_COMPRESSED;
			}
#ifdef HAVE_SETXATTR	/* extended attributes interface required */
			/* mark a future need to fixup encrypted inode */
			if (fi
				&& ctx->efs_raw
				&& (ni->flags & FILE_ATTR_ENCRYPTED))
				state |= CLOSE_ENCRYPTED;
#endif /* HAVE_SETXATTR */
			if (fi && ctx->dmtime)
				state |= CLOSE_DMTIME;
			ntfs_inode_update_mbsname(dir_ni, name, ni->mft_no);
			NInoSetDirty(ni);
			*ino = ni->mft_no;
#ifndef __HAIKU__
			e->ino = ni->mft_no;
			e->generation = 1;
			e->attr_timeout = ATTR_TIMEOUT;
			e->entry_timeout = ENTRY_TIMEOUT;
			res = ntfs_fuse_getstat(&security, ni, &e->attr);
#endif
			/*
			 * closing ni requires access to dir_ni to
			 * synchronize the index, avoid double opening.
			 */
			if (ntfs_inode_close_in_dir(ni, dir_ni))
				set_fuse_error(&res);
			ntfs_fuse_update_times(dir_ni, NTFS_UPDATE_MCTIME);
		} else
			res = -errno;
#if !KERNELPERMS | (POSIXACLS & !KERNELACLS)
	} else
		res = -errno;
#endif

exit:
	free(uname);
	if (ntfs_inode_close(dir_ni))
		set_fuse_error(&res);
	if (utarget)
		free(utarget);
#ifndef __HAIKU__
	if ((res >= 0) && fi) {
		of = (struct open_file*)malloc(sizeof(struct open_file));
		if (of) {
			of->parent = 0;
			of->ino = e->ino;
			of->state = state;
			of->next = ctx->open_files;
			of->previous = (struct open_file*)NULL;
			if (ctx->open_files)
				ctx->open_files->previous = of;
			ctx->open_files = of;
			fi->fh = (long)of;
		}
	}
#else
	// FIXME: store "state" somewhere
#endif
	return res;
}

static int ntfs_fuse_newlink(struct lowntfs_context *ctx,
		ino_t ino, ino_t newparent,
		const char *newname, struct fuse_entry_param *e)
{
	ntfschar *uname = NULL;
	ntfs_inode *dir_ni = NULL, *ni;
	int res = 0, uname_len;
	struct SECURITY_CONTEXT security;

	/* Open file for which create hard link. */
	ni = ntfs_inode_open(ctx->vol, INODE(ino));
	if (!ni) {
		res = -errno;
		goto exit;
	}

	/* Do not accept linking to a directory (except for renaming) */
	if (e && (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY)) {
		errno = EPERM;
		res = -errno;
		goto exit;
	}
	/* Generate unicode filename. */
	uname_len = ntfs_mbstoucs(newname, &uname);
	if ((uname_len < 0)
			|| (ctx->windows_names
				&& ntfs_forbidden_names(ctx->vol,uname,uname_len,TRUE))) {
		res = -errno;
		goto exit;
	}
	/* Open parent directory. */
	dir_ni = ntfs_inode_open(ctx->vol, INODE(newparent));
	if (!dir_ni) {
		res = -errno;
		goto exit;
	}

#if !KERNELPERMS | (POSIXACLS & !KERNELACLS)
		/* make sure the target parent directory is writeable */
	if (ntfs_fuse_fill_security_context(ctx, &security)
		&& !ntfs_allowed_access(&security,dir_ni,S_IWRITE + S_IEXEC))
		res = -EACCES;
	else
#else
	ntfs_fuse_fill_security_context(ctx, &security);
#endif
		{
		if (dir_ni->flags & FILE_ATTR_REPARSE_POINT) {
#ifndef DISABLE_PLUGINS
			const plugin_operations_t *ops;
			REPARSE_POINT *reparse;

			res = CALL_REPARSE_PLUGIN(dir_ni, link,
					ni, uname, uname_len);
			if (res < 0)
				goto exit;
#else /* DISABLE_PLUGINS */
			res = -EOPNOTSUPP;
			goto exit;
#endif /* DISABLE_PLUGINS */
		} else {
			if (ntfs_link(ni, dir_ni, uname, uname_len)) {
				res = -errno;
				goto exit;
			}
		}
		ntfs_inode_update_mbsname(dir_ni, newname, ni->mft_no);
#ifndef __HAIKU__
		if (e) {
			e->ino = ni->mft_no;
			e->generation = 1;
			e->attr_timeout = ATTR_TIMEOUT;
			e->entry_timeout = ENTRY_TIMEOUT;
			res = ntfs_fuse_getstat(&security, ni, &e->attr);
		}
#endif
		set_archive(ni);
		ntfs_fuse_update_times(ni, NTFS_UPDATE_CTIME);
		ntfs_fuse_update_times(dir_ni, NTFS_UPDATE_MCTIME);
	}
exit:
	/*
	 * Must close dir_ni first otherwise ntfs_inode_sync_file_name(ni)
	 * may fail because ni may not be in parent's index on the disk yet.
	 */
	if (ntfs_inode_close(dir_ni))
		set_fuse_error(&res);
	if (ntfs_inode_close(ni))
		set_fuse_error(&res);
	free(uname);
	return (res);
}


int
ntfs_fuse_rm(struct lowntfs_context *ctx, ino_t parent, const char *name,
	enum RM_TYPES rm_type)
{
	ntfschar *uname = NULL;
	ntfschar *ugname;
	ntfs_inode *dir_ni = NULL, *ni = NULL;
	int res = 0, uname_len;
	int ugname_len;
	u64 iref;
	ino_t ino;
	char ghostname[GHOSTLTH];
#if !KERNELPERMS | (POSIXACLS & !KERNELACLS)
	struct SECURITY_CONTEXT security;
#endif
#if defined(__sun) && defined (__SVR4)
	int isdir;
#endif /* defined(__sun) && defined (__SVR4) */
	int no_check_open = (rm_type & RM_NO_CHECK_OPEN) != 0;
	rm_type &= ~RM_NO_CHECK_OPEN;

	/* Deny removing from $Extend */
	if (parent == FILE_Extend) {
		res = -EPERM;
		goto exit;
	}
	/* Open parent directory. */
	dir_ni = ntfs_inode_open(ctx->vol, INODE(parent));
	if (!dir_ni) {
		res = -errno;
		goto exit;
	}
	/* Generate unicode filename. */
	uname_len = ntfs_mbstoucs(name, &uname);
	if (uname_len < 0) {
		res = -errno;
		goto exit;
	}
	/* Open object for delete. */
	iref = ntfs_inode_lookup_by_mbsname(dir_ni, name);
	if (iref == (u64)-1) {
		res = -errno;
		goto exit;
	}
	ino = (ino_t)MREF(iref);
	/* deny unlinking metadata files */
	if (ino < FILE_first_user) {
		errno = EPERM;
		res = -errno;
		goto exit;
	}

	ni = ntfs_inode_open(ctx->vol, ino);
	if (!ni) {
		res = -errno;
		goto exit;
	}

#if defined(__sun) && defined (__SVR4)
	/* on Solaris : deny unlinking directories */
	isdir = ni->mrec->flags & MFT_RECORD_IS_DIRECTORY;
#ifndef DISABLE_PLUGINS
		/* get emulated type from plugin if available */
	if (ni->flags & FILE_ATTR_REPARSE_POINT) {
		struct stat st;
		const plugin_operations_t *ops;
		REPARSE_POINT *reparse;

			/* Avoid double opening of parent directory */
		res = ntfs_inode_close(dir_ni);
		if (res)
			goto exit;
		dir_ni = (ntfs_inode*)NULL;
		res = CALL_REPARSE_PLUGIN(ni, getattr, &st);
		if (res)
			goto exit;
		isdir = S_ISDIR(st.st_mode);
		dir_ni = ntfs_inode_open(ctx->vol, INODE(parent));
		if (!dir_ni) {
			res = -errno;
			goto exit;
		}
	}
#endif /* DISABLE_PLUGINS */
	if (rm_type == (isdir ? RM_LINK : RM_DIR)) {
		errno = (isdir ? EISDIR : ENOTDIR);
		res = -errno;
		goto exit;
	}
#endif /* defined(__sun) && defined (__SVR4) */

#if !KERNELPERMS | (POSIXACLS & !KERNELACLS)
	/* JPA deny unlinking if directory is not writable and executable */
	if (ntfs_fuse_fill_security_context(ctx, &security)
		&& !ntfs_allowed_dir_access(&security, dir_ni, ino, ni,
				   S_IEXEC + S_IWRITE + S_ISVTX)) {
		errno = EACCES;
		res = -errno;
		goto exit;
	}
#endif
#ifndef __HAIKU__
		/*
		 * We keep one open_file record per opening, to avoid
		 * having to check the list of open files when opening
		 * and closing (which are more frequent than unlinking).
		 * As a consequence, we may have to create several
		 * ghosts names for the same file.
		 * The file may have been opened with a different name
		 * in a different parent directory. The ghost is
		 * nevertheless created in the parent directory of the
		 * name being unlinked, and permissions to do so are the
		 * same as required for unlinking.
		 */
	for (of=ctx->open_files; of; of = of->next) {
		if ((of->ino == ino) && !(of->state & CLOSE_GHOST)) {
#else
	int* close_state = NULL;
	if (!no_check_open) {
		close_state = ntfs_haiku_get_close_state(ctx, ino);
		if (close_state && (*close_state & CLOSE_GHOST) == 0) {
#endif
			/* file was open, create a ghost in unlink parent */
			ntfs_inode *gni;
			u64 gref;

			/* ni has to be closed for linking ghost */
			if (ni) {
				if (ntfs_inode_close(ni)) {
					res = -errno;
					goto exit;
				}
				ni = (ntfs_inode*)NULL;
			}
			*close_state |= CLOSE_GHOST;
			u64 ghost = ++ctx->latest_ghost;

			sprintf(ghostname,ghostformat,ghost);
				/* Generate unicode filename. */
			ugname = (ntfschar*)NULL;
			ugname_len = ntfs_mbstoucs(ghostname, &ugname);
			if (ugname_len < 0) {
				res = -errno;
				goto exit;
			}
			/* sweep existing ghost if any, ignoring errors */
			gref = ntfs_inode_lookup_by_mbsname(dir_ni, ghostname);
			if (gref != (u64)-1) {
				gni = ntfs_inode_open(ctx->vol, MREF(gref));
				ntfs_delete(ctx->vol, (char*)NULL, gni, dir_ni,
					 ugname, ugname_len);
				/* ntfs_delete() always closes gni and dir_ni */
				dir_ni = (ntfs_inode*)NULL;
			} else {
				if (ntfs_inode_close(dir_ni)) {
					res = -errno;
					goto out;
				}
				dir_ni = (ntfs_inode*)NULL;
			}
			free(ugname);
			res = ntfs_fuse_newlink(ctx, ino, parent, ghostname,
					(struct fuse_entry_param*)NULL);
			if (res)
				goto out;

#ifdef __HAIKU__
			// We have to do this before the parent directory is reopened.
			ntfs_haiku_put_close_state(ctx, ino, ghost);
			close_state = NULL;
#endif

				/* now reopen then parent directory */
			dir_ni = ntfs_inode_open(ctx->vol, INODE(parent));
			if (!dir_ni) {
				res = -errno;
				goto exit;
			}
		}
	}
	if (!ni) {
		ni = ntfs_inode_open(ctx->vol, ino);
		if (!ni) {
			res = -errno;
			goto exit;
		}
	}
	if (dir_ni->flags & FILE_ATTR_REPARSE_POINT) {
#ifndef DISABLE_PLUGINS
		const plugin_operations_t *ops;
		REPARSE_POINT *reparse;

		res = CALL_REPARSE_PLUGIN(dir_ni, unlink, (char*)NULL,
				ni, uname, uname_len);
#else /* DISABLE_PLUGINS */
		errno = EOPNOTSUPP;
		res = -errno;
#endif /* DISABLE_PLUGINS */
	} else {
		if (ntfs_delete(ctx->vol, (char*)NULL, ni, dir_ni,
					 uname, uname_len))
			res = -errno;
		/* ntfs_delete() always closes ni and dir_ni */
	}
	ni = dir_ni = NULL;
exit:
	if (ntfs_inode_close(ni) && !res)
		res = -errno;
	if (ntfs_inode_close(dir_ni) && !res)
		res = -errno;
out :
#ifdef __HAIKU__
	if (close_state)
		ntfs_haiku_put_close_state(ctx, ino, -1);
#endif
	free(uname);
	return res;
}

int ntfs_fuse_release(struct lowntfs_context *ctx, ino_t parent, ino_t ino, int state, u64 ghost)
{
	ntfs_inode *ni = NULL;
	ntfs_attr *na = NULL;
	char ghostname[GHOSTLTH];
	int res;

	/* Only for marked descriptors there is something to do */
	if (!(state & (CLOSE_COMPRESSED | CLOSE_ENCRYPTED
				| CLOSE_DMTIME | CLOSE_REPARSE))) {
		res = 0;
		goto out;
	}
	ni = ntfs_inode_open(ctx->vol, INODE(ino));
	if (!ni) {
		res = -errno;
		goto exit;
	}
	if (ni->flags & FILE_ATTR_REPARSE_POINT) {
#ifndef DISABLE_PLUGINS
		const plugin_operations_t *ops;
		REPARSE_POINT *reparse;

		res = CALL_REPARSE_PLUGIN(ni, release, &of->fi);
		if (!res) {
			goto stamps;
		}
#else /* DISABLE_PLUGINS */
			/* Assume release() was not needed */
		res = 0;
#endif /* DISABLE_PLUGINS */
		goto exit;
	}
	na = ntfs_attr_open(ni, AT_DATA, AT_UNNAMED, 0);
	if (!na) {
		res = -errno;
		goto exit;
	}
	res = 0;
	if (state & CLOSE_COMPRESSED)
		res = ntfs_attr_pclose(na);
#ifdef HAVE_SETXATTR	/* extended attributes interface required */
	if (of->state & CLOSE_ENCRYPTED)
		res = ntfs_efs_fixup_attribute(NULL, na);
#endif /* HAVE_SETXATTR */
#ifndef DISABLE_PLUGINS
stamps :
#endif /* DISABLE_PLUGINS */
	if (state & CLOSE_DMTIME)
		ntfs_inode_update_times(ni,NTFS_UPDATE_MCTIME);
exit:
	if (na)
		ntfs_attr_close(na);
	if (ntfs_inode_close(ni))
		set_fuse_error(&res);
out:
		/* remove the associate ghost file (even if release failed) */
	if (1) {
		if (state & CLOSE_GHOST) {
			sprintf(ghostname,ghostformat,ghost);
			ntfs_fuse_rm(ctx, parent, ghostname, RM_ANY | RM_NO_CHECK_OPEN);
		}
#ifndef __HAIKU__
			/* remove from open files list */
		if (of->next)
			of->next->previous = of->previous;
		if (of->previous)
			of->previous->next = of->next;
		else
			ctx->open_files = of->next;
		free(of);
#endif
	}
#ifndef __HAIKU__
	if (res)
		fuse_reply_err(req, -res);
	else
		fuse_reply_err(req, 0);
#endif
	return res;
}

static int ntfs_fuse_safe_rename(struct lowntfs_context *ctx, ino_t ino,
			ino_t parent, const char *name, ino_t xino,
			ino_t newparent, const char *newname,
			const char *tmp)
{
	int ret;

	ntfs_log_trace("Entering\n");

	ret = ntfs_fuse_newlink(ctx, xino, newparent, tmp,
				(struct fuse_entry_param*)NULL);
	if (ret)
		return ret;

	ret = ntfs_fuse_rm(ctx, newparent, newname, RM_ANY);
	if (!ret) {

		ret = ntfs_fuse_newlink(ctx, ino, newparent, newname,
					(struct fuse_entry_param*)NULL);
		if (ret)
			goto restore;

		ret = ntfs_fuse_rm(ctx, parent, name, RM_ANY | RM_NO_CHECK_OPEN);
		if (ret) {
			if (ntfs_fuse_rm(ctx, newparent, newname, RM_ANY))
				goto err;
			goto restore;
		}
	}

	goto cleanup;
restore:
	if (ntfs_fuse_newlink(ctx, xino, newparent, newname,
				(struct fuse_entry_param*)NULL)) {
err:
		ntfs_log_perror("Rename failed. Existing file '%s' was renamed "
				"to '%s'", newname, tmp);
	} else {
cleanup:
		/*
		 * Condition for this unlink has already been checked in
		 * "ntfs_fuse_rename_existing_dest()", so it should never
		 * fail (unless concurrent access to directories when fuse
		 * is multithreaded)
		 */
		if (ntfs_fuse_rm(ctx, newparent, tmp, RM_ANY) < 0)
			ntfs_log_perror("Rename failed. Existing file '%s' still present "
				"as '%s'", newname, tmp);
	}
	return	ret;
}

static int ntfs_fuse_rename_existing_dest(struct lowntfs_context *ctx, ino_t ino,
			ino_t parent, const char *name,
			ino_t xino, ino_t newparent,
			const char *newname)
{
	int ret, len;
	char *tmp;
	const char *ext = ".ntfs-3g-";
#if !KERNELPERMS | (POSIXACLS & !KERNELACLS)
	ntfs_inode *newdir_ni;
	struct SECURITY_CONTEXT security;
#endif

	ntfs_log_trace("Entering\n");

	len = strlen(newname) + strlen(ext) + 10 + 1; /* wc(str(2^32)) + \0 */
	tmp = (char*)ntfs_malloc(len);
	if (!tmp)
		return -errno;

	ret = snprintf(tmp, len, "%s%s%010d", newname, ext, ++ntfs_sequence);
	if (ret != len - 1) {
		ntfs_log_error("snprintf failed: %d != %d\n", ret, len - 1);
		ret = -EOVERFLOW;
	} else {
#if !KERNELPERMS | (POSIXACLS & !KERNELACLS)
			/*
			 * Make sure existing dest can be removed.
			 * This is only needed if parent directory is
			 * sticky, because in this situation condition
			 * for unlinking is different from condition for
			 * linking
			 */
		newdir_ni = ntfs_inode_open(ctx->vol, INODE(newparent));
		if (newdir_ni) {
			if (!ntfs_fuse_fill_security_context(ctx,&security)
				|| ntfs_allowed_dir_access(&security, newdir_ni,
					xino, (ntfs_inode*)NULL,
					S_IEXEC + S_IWRITE + S_ISVTX)) {
				if (ntfs_inode_close(newdir_ni))
					ret = -errno;
				else
					ret = ntfs_fuse_safe_rename(ctx, ino,
							parent, name, xino,
							newparent, newname,
							tmp);
			} else {
				ntfs_inode_close(newdir_ni);
				ret = -EACCES;
			}
		} else
			ret = -errno;
#else
		ret = ntfs_fuse_safe_rename(ctx, ino, parent, name,
					xino, newparent, newname, tmp);
#endif
	}
	free(tmp);
	return	ret;
}


int ntfs_fuse_rename(struct lowntfs_context *ctx, ino_t parent,
			const char *name, ino_t newparent,
			const char *newname)
{
	int ret;
	ino_t ino;
	ino_t xino;
	ntfs_inode *ni;

	ntfs_log_debug("rename: old: '%s'  new: '%s'\n", name, newname);

	/*
	 *  FIXME: Rename should be atomic.
	 */

	ino = ntfs_fuse_inode_lookup(ctx, parent, name);
	if (ino == (ino_t)-1) {
		ret = -errno;
		goto out;
	}
	/* Check whether target is present */
	xino = ntfs_fuse_inode_lookup(ctx, newparent, newname);
	if (xino != (ino_t)-1) {
			/*
			 * Target exists : no need to check whether it
			 * designates the same inode, this has already
			 * been checked (by fuse ?)
			 */
		ni = ntfs_inode_open(ctx->vol, INODE(xino));
		if (!ni)
			ret = -errno;
		else {
			ret = ntfs_check_empty_dir(ni);
			if (ret < 0) {
				ret = -errno;
				ntfs_inode_close(ni);
				goto out;
			}

			if (ntfs_inode_close(ni)) {
				set_fuse_error(&ret);
				goto out;
			}
			ret = ntfs_fuse_rename_existing_dest(ctx, ino, parent,
						name, xino, newparent, newname);
		}
	} else {
			/* target does not exist */
		ret = ntfs_fuse_newlink(ctx, ino, newparent, newname,
					(struct fuse_entry_param*)NULL);
		if (ret)
			goto out;

		ret = ntfs_fuse_rm(ctx, parent, name, RM_ANY | RM_NO_CHECK_OPEN);
		if (ret)
			ntfs_fuse_rm(ctx, newparent, newname, RM_ANY);
	}
out:
#ifndef __HAIKU__
	if (ret)
		fuse_reply_err(req, -ret);
	else
		fuse_reply_err(req, 0);
#endif
	return ret;
}