/**
 * ntfs-3g_common.c - Common definitions for ntfs-3g and lowntfs-3g.
 *
 * Copyright (c) 2010-2011 Jean-Pierre Andre
 * Copyright (c) 2010      Erik Larsson
 *
 * 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_STRING_H
#include <string.h>
#endif

#ifdef HAVE_ERRNO_H
#include <errno.h>
#endif

#include <getopt.h>
#include <fuse.h>

#include "inode.h"
#include "security.h"
#include "xattrs.h"
#include "ntfs-3g_common.h"
#include "misc.h"

const char xattr_ntfs_3g[] = "ntfs-3g.";

const char nf_ns_user_prefix[] = "user.";
const int nf_ns_user_prefix_len = sizeof(nf_ns_user_prefix) - 1;
const char nf_ns_system_prefix[] = "system.";
const int nf_ns_system_prefix_len = sizeof(nf_ns_system_prefix) - 1;
const char nf_ns_security_prefix[] = "security.";
const int nf_ns_security_prefix_len = sizeof(nf_ns_security_prefix) - 1;
const char nf_ns_trusted_prefix[] = "trusted.";
const int nf_ns_trusted_prefix_len = sizeof(nf_ns_trusted_prefix) - 1;

static const char nf_ns_alt_xattr_efsinfo[] = "user.ntfs.efsinfo";

#ifdef HAVE_SETXATTR

static const char def_opts[] = "allow_other,nonempty,";

	/*
	 *	 Table of recognized options
	 * Their order may be significant
	 * The options invalid in some configuration should still
	 * be present, so that an error can be returned
	 */
const struct DEFOPTION optionlist[] = {
	{ "ro", OPT_RO, FLGOPT_APPEND | FLGOPT_BOGUS },
	{ "noatime", OPT_NOATIME, FLGOPT_BOGUS },
	{ "atime", OPT_ATIME, FLGOPT_BOGUS },
	{ "relatime", OPT_RELATIME, FLGOPT_BOGUS },
	{ "fake_rw", OPT_FAKE_RW, FLGOPT_BOGUS },
	{ "fsname", OPT_FSNAME, FLGOPT_NOSUPPORT },
	{ "no_def_opts", OPT_NO_DEF_OPTS, FLGOPT_BOGUS },
	{ "default_permissions", OPT_DEFAULT_PERMISSIONS, FLGOPT_BOGUS },
	{ "permissions", OPT_PERMISSIONS, FLGOPT_BOGUS },
	{ "acl", OPT_ACL, FLGOPT_BOGUS },
	{ "umask", OPT_UMASK, FLGOPT_OCTAL },
	{ "fmask", OPT_FMASK, FLGOPT_OCTAL },
	{ "dmask", OPT_DMASK, FLGOPT_OCTAL },
	{ "uid", OPT_UID, FLGOPT_DECIMAL },
	{ "gid", OPT_GID, FLGOPT_DECIMAL },
	{ "show_sys_files", OPT_SHOW_SYS_FILES, FLGOPT_BOGUS },
	{ "hide_hid_files", OPT_HIDE_HID_FILES, FLGOPT_BOGUS },
	{ "hide_dot_files", OPT_HIDE_DOT_FILES, FLGOPT_BOGUS },
	{ "ignore_case", OPT_IGNORE_CASE, FLGOPT_BOGUS },
	{ "windows_names", OPT_WINDOWS_NAMES, FLGOPT_BOGUS },
	{ "compression", OPT_COMPRESSION, FLGOPT_BOGUS },
	{ "nocompression", OPT_NOCOMPRESSION, FLGOPT_BOGUS },
	{ "silent", OPT_SILENT, FLGOPT_BOGUS },
	{ "recover", OPT_RECOVER, FLGOPT_BOGUS },
	{ "norecover", OPT_NORECOVER, FLGOPT_BOGUS },
	{ "remove_hiberfile", OPT_REMOVE_HIBERFILE, FLGOPT_BOGUS },
	{ "sync", OPT_SYNC, FLGOPT_BOGUS | FLGOPT_APPEND },
	{ "locale", OPT_LOCALE, FLGOPT_STRING },
	{ "nfconv", OPT_NFCONV, FLGOPT_BOGUS },
	{ "nonfconv", OPT_NONFCONV, FLGOPT_BOGUS },
	{ "streams_interface", OPT_STREAMS_INTERFACE, FLGOPT_STRING },
	{ "user_xattr", OPT_USER_XATTR, FLGOPT_BOGUS },
	{ "noauto", OPT_NOAUTO, FLGOPT_BOGUS },
	{ "debug", OPT_DEBUG, FLGOPT_BOGUS },
	{ "no_detach", OPT_NO_DETACH, FLGOPT_BOGUS },
	{ "remount", OPT_REMOUNT, FLGOPT_BOGUS },
	{ "blksize", OPT_BLKSIZE, FLGOPT_STRING },
	{ "inherit", OPT_INHERIT, FLGOPT_BOGUS },
	{ "addsecurids", OPT_ADDSECURIDS, FLGOPT_BOGUS },
	{ "staticgrps", OPT_STATICGRPS, FLGOPT_BOGUS },
	{ "usermapping", OPT_USERMAPPING, FLGOPT_STRING },
	{ "xattrmapping", OPT_XATTRMAPPING, FLGOPT_STRING },
	{ "efs_raw", OPT_EFS_RAW, FLGOPT_BOGUS },
	{ (const char*)NULL, 0, 0 } /* end marker */
} ;

#define STRAPPEND_MAX_INSIZE   8192
#define strappend_is_large(x) ((x) > STRAPPEND_MAX_INSIZE)

int ntfs_strappend(char **dest, const char *append)
{
	char *p;
	size_t size_append, size_dest = 0;
	
	if (!dest)
		return -1;
	if (!append)
		return 0;

	size_append = strlen(append);
	if (*dest)
		size_dest = strlen(*dest);
	
	if (strappend_is_large(size_dest) || strappend_is_large(size_append)) {
		errno = EOVERFLOW;
		ntfs_log_perror("%s: Too large input buffer", EXEC_NAME);
		return -1;
	}
	
	p = (char*)realloc(*dest, size_dest + size_append + 1);
    	if (!p) {
		ntfs_log_perror("%s: Memory realloction failed", EXEC_NAME);
		return -1;
	}
	
	*dest = p;
	strcpy(*dest + size_dest, append);
	
	return 0;
}

static int bogus_option_value(char *val, const char *s)
{
	if (val) {
		ntfs_log_error("'%s' option shouldn't have value.\n", s);
		return -1;
	}
	return 0;
}

static int missing_option_value(char *val, const char *s)
{
	if (!val) {
		ntfs_log_error("'%s' option should have a value.\n", s);
		return -1;
	}
	return 0;
}

char *parse_mount_options(ntfs_fuse_context_t *ctx,
			const struct ntfs_options *popts, BOOL low_fuse)
{
	char *options, *s, *opt, *val, *ret = NULL;
	const char *orig_opts = popts->options;
	BOOL no_def_opts = FALSE;
	int default_permissions = 0;
	int permissions = 0;
	int acl = 0;
	int want_permissions = 0;
	int intarg;
	const struct DEFOPTION *poptl;

	ctx->secure_flags = 0;
#ifdef HAVE_SETXATTR	/* extended attributes interface required */
	ctx->efs_raw = FALSE;
#endif /* HAVE_SETXATTR */
	ctx->compression = DEFAULT_COMPRESSION;
	ctx->atime = ATIME_ENABLED;
	options = strdup(orig_opts ? orig_opts : "");
	if (!options) {
		ntfs_log_perror("%s: strdup failed", EXEC_NAME);
		return NULL;
	}
	
	s = options;
	while (s && *s && (val = strsep(&s, ","))) {
		opt = strsep(&val, "=");
		poptl = optionlist;
		while (poptl->name && strcmp(poptl->name,opt))
			poptl++;
		if (poptl->name) {
			if ((poptl->flags & FLGOPT_BOGUS)
			    && bogus_option_value(val, opt))
				goto err_exit;
			if ((poptl->flags & FLGOPT_OCTAL)
			    && (!val
				|| !sscanf(val, "%o", &intarg))) {
				ntfs_log_error("'%s' option needs an octal value\n",
					opt);
				goto err_exit;
			}
			if ((poptl->flags & FLGOPT_DECIMAL)
			    && (!val
				|| !sscanf(val, "%i", &intarg))) {
				ntfs_log_error("'%s' option needs a decimal value\n",
					opt);
				goto err_exit;
			}
			if ((poptl->flags & FLGOPT_STRING)
			    && missing_option_value(val, opt))
				goto err_exit;

			switch (poptl->type) {
			case OPT_RO :
			case OPT_FAKE_RW :
				ctx->ro = TRUE;
				break;
			case OPT_NOATIME :
				ctx->atime = ATIME_DISABLED;
				break;
			case OPT_ATIME :
				ctx->atime = ATIME_ENABLED;
				break;
			case OPT_RELATIME :
				ctx->atime = ATIME_RELATIVE;
				break;
			case OPT_NO_DEF_OPTS :
				no_def_opts = TRUE; /* Don't add default options. */
				ctx->silent = FALSE; /* cancel default silent */
				break;
			case OPT_DEFAULT_PERMISSIONS :
				default_permissions = 1;
				break;
			case OPT_PERMISSIONS :
				permissions = 1;
				break;
#if POSIXACLS
			case OPT_ACL :
				acl = 1;
				break;
#endif
			case OPT_UMASK :
				ctx->dmask = ctx->fmask = intarg;
				want_permissions = 1;
				break;
			case OPT_FMASK :
				ctx->fmask = intarg;
			       	want_permissions = 1;
				break;
			case OPT_DMASK :
				ctx->dmask = intarg;
			       	want_permissions = 1;
				break;
			case OPT_UID :
				ctx->uid = intarg;
			       	want_permissions = 1;
				break;
			case OPT_GID :
				ctx->gid = intarg;
				want_permissions = 1;
				break;
			case OPT_SHOW_SYS_FILES :
				ctx->show_sys_files = TRUE;
				break;
			case OPT_HIDE_HID_FILES :
				ctx->hide_hid_files = TRUE;
				break;
			case OPT_HIDE_DOT_FILES :
				ctx->hide_dot_files = TRUE;
				break;
			case OPT_WINDOWS_NAMES :
				ctx->windows_names = TRUE;
				break;
			case OPT_IGNORE_CASE :
				if (low_fuse)
					ctx->ignore_case = TRUE;
				else {
					ntfs_log_error("'%s' is an unsupported option.\n",
						poptl->name);
					goto err_exit;
				}
				break;
			case OPT_COMPRESSION :
				ctx->compression = TRUE;
				break;
			case OPT_NOCOMPRESSION :
				ctx->compression = FALSE;
				break;
			case OPT_SILENT :
				ctx->silent = TRUE;
				break;
			case OPT_RECOVER :
				ctx->recover = TRUE;
				break;
			case OPT_NORECOVER :
				ctx->recover = FALSE;
				break;
			case OPT_REMOVE_HIBERFILE :
				ctx->hiberfile = TRUE;
				break;
			case OPT_SYNC :
				ctx->sync = TRUE;
				break;
			case OPT_LOCALE :
				ntfs_set_char_encoding(val);
				break;
#if defined(__APPLE__) || defined(__DARWIN__)
#ifdef ENABLE_NFCONV
			case OPT_NFCONV :
				if (ntfs_macosx_normalize_filenames(1)) {
					ntfs_log_error("ntfs_macosx_normalize_filenames(1) failed!\n");
					goto err_exit;
				}
				break;
			case OPT_NONFCONV :
				if (ntfs_macosx_normalize_filenames(0)) {
					ntfs_log_error("ntfs_macosx_normalize_filenames(0) failed!\n");
					goto err_exit;
				}
				break;
#endif /* ENABLE_NFCONV */
#endif /* defined(__APPLE__) || defined(__DARWIN__) */
			case OPT_STREAMS_INTERFACE :
				if (!strcmp(val, "none"))
					ctx->streams = NF_STREAMS_INTERFACE_NONE;
				else if (!strcmp(val, "xattr"))
					ctx->streams = NF_STREAMS_INTERFACE_XATTR;
				else if (!strcmp(val, "openxattr"))
					ctx->streams = NF_STREAMS_INTERFACE_OPENXATTR;
				else if (!low_fuse && !strcmp(val, "windows"))
					ctx->streams = NF_STREAMS_INTERFACE_WINDOWS;
				else {
					ntfs_log_error("Invalid named data streams "
						"access interface.\n");
					goto err_exit;
				}
				break;
			case OPT_USER_XATTR :
				ctx->streams = NF_STREAMS_INTERFACE_XATTR;
				break;
			case OPT_NOAUTO :
				/* Don't pass noauto option to fuse. */
				break;
			case OPT_DEBUG :
				ctx->debug = TRUE;
				ntfs_log_set_levels(NTFS_LOG_LEVEL_DEBUG);
				ntfs_log_set_levels(NTFS_LOG_LEVEL_TRACE);
				break;
			case OPT_NO_DETACH :
				ctx->no_detach = TRUE;
				break;
			case OPT_REMOUNT :
				ntfs_log_error("Remounting is not supported at present."
					" You have to umount volume and then "
					"mount it once again.\n");
				goto err_exit;
			case OPT_BLKSIZE :
				ntfs_log_info("WARNING: blksize option is ignored "
				      "because ntfs-3g must calculate it.\n");
				break;
			case OPT_INHERIT :
				/*
				 * do not overwrite inherited permissions
				 * in create()
				 */
				ctx->inherit = TRUE;
				break;
			case OPT_ADDSECURIDS :
				/*
				 * create security ids for files being read
				 * with an individual security attribute
				 */
				ctx->secure_flags |= (1 << SECURITY_ADDSECURIDS);
				break;
			case OPT_STATICGRPS :
				/*
				 * use static definition of groups
				 * for file access control
				 */
				ctx->secure_flags |= (1 << SECURITY_STATICGRPS);
				break;
			case OPT_USERMAPPING :
				ctx->usermap_path = strdup(val);
				if (!ctx->usermap_path) {
					ntfs_log_error("no more memory to store "
						"'usermapping' option.\n");
					goto err_exit;
				}
				break;
#ifdef HAVE_SETXATTR	/* extended attributes interface required */
#ifdef XATTR_MAPPINGS
			case OPT_XATTRMAPPING :
				ctx->xattrmap_path = strdup(val);
				if (!ctx->xattrmap_path) {
					ntfs_log_error("no more memory to store "
						"'xattrmapping' option.\n");
					goto err_exit;
				}
				break;
#endif /* XATTR_MAPPINGS */
			case OPT_EFS_RAW :
				ctx->efs_raw = TRUE;
				break;
#endif /* HAVE_SETXATTR */
			case OPT_FSNAME : /* Filesystem name. */
			/*
			 * We need this to be able to check whether filesystem
			 * mounted or not.
			 *      (falling through to default)
			 */
			default :
				ntfs_log_error("'%s' is an unsupported option.\n",
					poptl->name);
				goto err_exit;
			}
			if ((poptl->flags & FLGOPT_APPEND)
			    && (ntfs_strappend(&ret, poptl->name)
				    || ntfs_strappend(&ret, ",")))
				goto err_exit;
		} else { /* Probably FUSE option. */
			if (ntfs_strappend(&ret, opt))
				goto err_exit;
			if (val) {
				if (ntfs_strappend(&ret, "="))
					goto err_exit;
				if (ntfs_strappend(&ret, val))
					goto err_exit;
			}
			if (ntfs_strappend(&ret, ","))
				goto err_exit;
		}
	}
	if (!no_def_opts && ntfs_strappend(&ret, def_opts))
		goto err_exit;
	if ((default_permissions || (permissions && !acl))
			&& ntfs_strappend(&ret, "default_permissions,"))
		goto err_exit;
			/* The atime options exclude each other */
	if (ctx->atime == ATIME_RELATIVE && ntfs_strappend(&ret, "relatime,"))
		goto err_exit;
	else if (ctx->atime == ATIME_ENABLED && ntfs_strappend(&ret, "atime,"))
		goto err_exit;
	else if (ctx->atime == ATIME_DISABLED && ntfs_strappend(&ret, "noatime,"))
		goto err_exit;
	
	if (ntfs_strappend(&ret, "fsname="))
		goto err_exit;
	if (ntfs_strappend(&ret, popts->device))
		goto err_exit;
	if (permissions && !acl)
		ctx->secure_flags |= (1 << SECURITY_DEFAULT);
	if (acl)
		ctx->secure_flags |= (1 << SECURITY_ACL);
	if (want_permissions)
		ctx->secure_flags |= (1 << SECURITY_WANTED);
	if (ctx->ro)
		ctx->secure_flags &= ~(1 << SECURITY_ADDSECURIDS);
exit:
	free(options);
	return ret;
err_exit:
	free(ret);
	ret = NULL;
	goto exit;
}

/**
 * parse_options - Read and validate the programs command line
 * Read the command line, verify the syntax and parse the options.
 *
 * Return:   0 success, -1 error.
 */
int ntfs_parse_options(struct ntfs_options *popts, void (*usage)(void),
			int argc, char *argv[])
{
	int c;

	static const char *sopt = "-o:hnvV";
	static const struct option lopt[] = {
		{ "options",	 required_argument,	NULL, 'o' },
		{ "help",	 no_argument,		NULL, 'h' },
		{ "no-mtab",	 no_argument,		NULL, 'n' },
		{ "verbose",	 no_argument,		NULL, 'v' },
		{ "version",	 no_argument,		NULL, 'V' },
		{ NULL,		 0,			NULL,  0  }
	};

	opterr = 0; /* We'll handle the errors, thank you. */

	while ((c = getopt_long(argc, argv, sopt, lopt, NULL)) != -1) {
		switch (c) {
		case 1:	/* A non-option argument */
			if (!popts->device) {
				popts->device = ntfs_malloc(PATH_MAX + 1);
				if (!popts->device)
					return -1;
				
				/* Canonicalize device name (mtab, etc) */
				if (!realpath(optarg, popts->device)) {
					ntfs_log_perror("%s: Failed to access "
					     "volume '%s'", EXEC_NAME, optarg);
					free(popts->device);
					popts->device = NULL;
					return -1;
				}
			} else if (!popts->mnt_point) {
				popts->mnt_point = optarg;
			} else {
				ntfs_log_error("%s: You must specify exactly one "
						"device and exactly one mount "
						"point.\n", EXEC_NAME);
				return -1;
			}
			break;
		case 'o':
			if (popts->options)
				if (ntfs_strappend(&popts->options, ","))
					return -1;
			if (ntfs_strappend(&popts->options, optarg))
				return -1;
			break;
		case 'h':
			usage();
			exit(9);
		case 'n':
			/*
			 * no effect - automount passes it, meaning 'no-mtab'
			 */
			break;
		case 'v':
			/*
			 * We must handle the 'verbose' option even if
			 * we don't use it because mount(8) passes it.
			 */
			break;
		case 'V':
			ntfs_log_info("%s %s %s %d\n", EXEC_NAME, VERSION, 
				      FUSE_TYPE, fuse_version());
			exit(0);
		default:
			ntfs_log_error("%s: Unknown option '%s'.\n", EXEC_NAME,
				       argv[optind - 1]);
			return -1;
		}
	}

	if (!popts->device) {
		ntfs_log_error("%s: No device is specified.\n", EXEC_NAME);
		return -1;
	}
	if (!popts->mnt_point) {
		ntfs_log_error("%s: No mountpoint is specified.\n", EXEC_NAME);
		return -1;
	}

	return 0;
}

int ntfs_fuse_listxattr_common(ntfs_inode *ni, ntfs_attr_search_ctx *actx,
			char *list, size_t size, BOOL prefixing)
{
	int ret = 0;
	char *to = list;
#ifdef XATTR_MAPPINGS
	BOOL accepted;
	const struct XATTRMAPPING *item;
#endif /* XATTR_MAPPINGS */

		/* first list the regular user attributes (ADS) */
	while (!ntfs_attr_lookup(AT_DATA, NULL, 0, CASE_SENSITIVE,
				0, NULL, 0, actx)) {
		char *tmp_name = NULL;
		int tmp_name_len;

		if (!actx->attr->name_length)
			continue;
		tmp_name_len = ntfs_ucstombs(
			(ntfschar *)((u8*)actx->attr +
				le16_to_cpu(actx->attr->name_offset)),
			actx->attr->name_length, &tmp_name, 0);
		if (tmp_name_len < 0) {
			ret = -errno;
			goto exit;
		}
				/*
				 * When using name spaces, do not return
				 * security, trusted or system attributes
				 * (filtered elsewhere anyway)
				 * otherwise insert "user." prefix
				 */
		if (prefixing) {
			if ((strlen(tmp_name) > sizeof(xattr_ntfs_3g))
			  && !strncmp(tmp_name,xattr_ntfs_3g,
				sizeof(xattr_ntfs_3g)-1))
				tmp_name_len = 0;
			else
				ret += tmp_name_len
					 + nf_ns_user_prefix_len + 1;
		} else
			ret += tmp_name_len + 1;
		if (size && tmp_name_len) {
			if ((size_t)ret <= size) {
				if (prefixing) {
					strcpy(to, nf_ns_user_prefix);
					to += nf_ns_user_prefix_len;
				}
				strncpy(to, tmp_name, tmp_name_len);
				to += tmp_name_len;
				*to = 0;
				to++;
			} else {
				free(tmp_name);
				ret = -ERANGE;
				goto exit;
			}
		}
		free(tmp_name);
	}
#ifdef XATTR_MAPPINGS
		/* now append the system attributes mapped to user space */
	for (item=ni->vol->xattr_mapping; item; item=item->next) {
		switch (item->xattr) {
		case XATTR_NTFS_EFSINFO :
			accepted = ni->vol->efs_raw
				&& (ni->flags & FILE_ATTR_ENCRYPTED);
			break;
		case XATTR_NTFS_REPARSE_DATA :
			accepted = (ni->flags & FILE_ATTR_REPARSE_POINT)
					!= const_cpu_to_le32(0);
			break;
// TODO : we are supposed to only return xattrs which are set
// this is more complex for OBJECT_ID and DOS_NAME
		default : accepted = TRUE;
			break;
		}
		if (accepted) {
			ret += strlen(item->name) + 1;
			if (size) {
				if ((size_t)ret <= size) {
					strcpy(to, item->name);
					to += strlen(item->name);
					*to++ = 0;
				} else {
					ret = -ERANGE;
					goto exit;
				}
			}
#else /* XATTR_MAPPINGS */
		/* List efs info xattr for encrypted files */
	if (ni->vol->efs_raw && (ni->flags & FILE_ATTR_ENCRYPTED)) {
		ret += sizeof(nf_ns_alt_xattr_efsinfo);
		if ((size_t)ret <= size) {
			memcpy(to, nf_ns_alt_xattr_efsinfo,
				sizeof(nf_ns_alt_xattr_efsinfo));
			to += sizeof(nf_ns_alt_xattr_efsinfo);
#endif /* XATTR_MAPPINGS */
		}
	}
exit :
	return (ret);
}

#endif /* HAVE_SETXATTR */
