/*
 * security/ccsecurity/file.c
 *
 * Copyright (C) 2005-2011  NTT DATA CORPORATION
 *
 * Version: 1.8.2   2011/06/20
 */

#include "internal.h"
#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 32)

/*
 * may_open() receives open flags modified by open_to_namei_flags() until
 * 2.6.32. We stop here in case some distributions backported ACC_MODE changes,
 * for we can't determine whether may_open() receives open flags modified by
 * open_to_namei_flags() or not.
 */
#ifdef ACC_MODE
#error ACC_MODE already defined.
#endif
#define ACC_MODE(x) ("\000\004\002\006"[(x)&O_ACCMODE])

#if defined(RHEL_MAJOR) && RHEL_MAJOR == 6
/* RHEL6 passes unmodified flags since 2.6.32-71.14.1.el6 . */
#undef ACC_MODE
#define ACC_MODE(x) ("\004\002\006"[(x)&O_ACCMODE])
#endif

#endif

/* Mapping table from "enum ccs_path_acl_index" to "enum ccs_mac_index". */
static const u8 ccs_p2mac[CCS_MAX_PATH_OPERATION] = {
	[CCS_TYPE_EXECUTE]    = CCS_MAC_FILE_EXECUTE,
	[CCS_TYPE_READ]       = CCS_MAC_FILE_OPEN,
	[CCS_TYPE_WRITE]      = CCS_MAC_FILE_OPEN,
	[CCS_TYPE_APPEND]     = CCS_MAC_FILE_OPEN,
	[CCS_TYPE_UNLINK]     = CCS_MAC_FILE_UNLINK,
	[CCS_TYPE_GETATTR]    = CCS_MAC_FILE_GETATTR,
	[CCS_TYPE_RMDIR]      = CCS_MAC_FILE_RMDIR,
	[CCS_TYPE_TRUNCATE]   = CCS_MAC_FILE_TRUNCATE,
	[CCS_TYPE_SYMLINK]    = CCS_MAC_FILE_SYMLINK,
	[CCS_TYPE_CHROOT]     = CCS_MAC_FILE_CHROOT,
	[CCS_TYPE_UMOUNT]     = CCS_MAC_FILE_UMOUNT,
};

/* Mapping table from "enum ccs_mkdev_acl_index" to "enum ccs_mac_index". */
const u8 ccs_pnnn2mac[CCS_MAX_MKDEV_OPERATION] = {
	[CCS_TYPE_MKBLOCK] = CCS_MAC_FILE_MKBLOCK,
	[CCS_TYPE_MKCHAR]  = CCS_MAC_FILE_MKCHAR,
};

/* Mapping table from "enum ccs_path2_acl_index" to "enum ccs_mac_index". */
const u8 ccs_pp2mac[CCS_MAX_PATH2_OPERATION] = {
	[CCS_TYPE_LINK]       = CCS_MAC_FILE_LINK,
	[CCS_TYPE_RENAME]     = CCS_MAC_FILE_RENAME,
	[CCS_TYPE_PIVOT_ROOT] = CCS_MAC_FILE_PIVOT_ROOT,
};

/*
 * Mapping table from "enum ccs_path_number_acl_index" to "enum ccs_mac_index".
 */
const u8 ccs_pn2mac[CCS_MAX_PATH_NUMBER_OPERATION] = {
	[CCS_TYPE_CREATE] = CCS_MAC_FILE_CREATE,
	[CCS_TYPE_MKDIR]  = CCS_MAC_FILE_MKDIR,
	[CCS_TYPE_MKFIFO] = CCS_MAC_FILE_MKFIFO,
	[CCS_TYPE_MKSOCK] = CCS_MAC_FILE_MKSOCK,
	[CCS_TYPE_IOCTL]  = CCS_MAC_FILE_IOCTL,
	[CCS_TYPE_CHMOD]  = CCS_MAC_FILE_CHMOD,
	[CCS_TYPE_CHOWN]  = CCS_MAC_FILE_CHOWN,
	[CCS_TYPE_CHGRP]  = CCS_MAC_FILE_CHGRP,
};

/**
 * ccs_put_name_union - Drop reference on "struct ccs_name_union".
 *
 * @ptr: Pointer to "struct ccs_name_union".
 *
 * Returns nothing.
 */
void ccs_put_name_union(struct ccs_name_union *ptr)
{
	ccs_put_group(ptr->group);
	ccs_put_name(ptr->filename);
}

/**
 * ccs_put_number_union - Drop reference on "struct ccs_number_union".
 *
 * @ptr: Pointer to "struct ccs_number_union".
 *
 * Returns nothing.
 */
void ccs_put_number_union(struct ccs_number_union *ptr)
{
	ccs_put_group(ptr->group);
}

/**
 * ccs_compare_number_union - Check whether a value matches "struct ccs_number_union" or not.
 *
 * @value: Number to check.
 * @ptr:   Pointer to "struct ccs_number_union".
 *
 * Returns true if @value matches @ptr, false otherwise.
 */
bool ccs_compare_number_union(const unsigned long value,
			      const struct ccs_number_union *ptr)
{
	if (ptr->group)
		return ccs_number_matches_group(value, value, ptr->group);
	return value >= ptr->values[0] && value <= ptr->values[1];
}

/**
 * ccs_compare_name_union - Check whether a name matches "struct ccs_name_union" or not.
 *
 * @name: Pointer to "struct ccs_path_info".
 * @ptr:  Pointer to "struct ccs_name_union".
 *
 * Returns "struct ccs_path_info" if @name matches @ptr, NULL otherwise.
 */
const struct ccs_path_info *ccs_compare_name_union
(const struct ccs_path_info *name, const struct ccs_name_union *ptr)
{
	if (ptr->group)
		return ccs_path_matches_group(name, ptr->group);
	if (ccs_path_matches_pattern(name, ptr->filename))
		return ptr->filename;
	return NULL;
}

/**
 * ccs_add_slash - Add trailing '/' if needed.
 *
 * @buf: Pointer to "struct ccs_path_info".
 *
 * Returns nothing.
 *
 * @buf must be generated by ccs_encode() because this function does not
 * allocate memory for adding '/'.
 */
static void ccs_add_slash(struct ccs_path_info *buf)
{
	if (buf->is_dir)
		return;
	/* This is OK because ccs_encode() reserves space for appending "/". */
	strcat((char *) buf->name, "/");
	ccs_fill_path_info(buf);
}

/**
 * ccs_get_realpath - Get realpath.
 *
 * @buf:    Pointer to "struct ccs_path_info".
 * @dentry: Pointer to "struct dentry".
 * @mnt:    Pointer to "struct vfsmount". Maybe NULL.
 *
 * Returns true on success, false otherwise.
 */
static bool ccs_get_realpath(struct ccs_path_info *buf, struct dentry *dentry,
			     struct vfsmount *mnt)
{
	struct path path = { mnt, dentry };
	buf->name = ccs_realpath_from_path(&path);
	if (buf->name) {
		ccs_fill_path_info(buf);
		return true;
	}
	return false;
}

/**
 * ccs_audit_path_log - Audit path request log.
 *
 * @r: Pointer to "struct ccs_request_info".
 *
 * Returns 0 on success, negative value otherwise.
 */
static int ccs_audit_path_log(struct ccs_request_info *r)
{
	return ccs_supervisor(r, "file %s %s\n", ccs_path_keyword
			      [r->param.path.operation],
			      r->param.path.filename->name);
}

/**
 * ccs_audit_path2_log - Audit path/path request log.
 *
 * @r: Pointer to "struct ccs_request_info".
 *
 * Returns 0 on success, negative value otherwise.
 */
static int ccs_audit_path2_log(struct ccs_request_info *r)
{
	return ccs_supervisor(r, "file %s %s %s\n", ccs_mac_keywords
			      [ccs_pp2mac[r->param.path2.operation]],
			      r->param.path2.filename1->name,
			      r->param.path2.filename2->name);
}

/**
 * ccs_audit_mkdev_log - Audit path/number/number/number request log.
 *
 * @r: Pointer to "struct ccs_request_info".
 *
 * Returns 0 on success, negative value otherwise.
 */
static int ccs_audit_mkdev_log(struct ccs_request_info *r)
{
	return ccs_supervisor(r, "file %s %s 0%o %u %u\n", ccs_mac_keywords
			      [ccs_pnnn2mac[r->param.mkdev.operation]],
			      r->param.mkdev.filename->name,
			      r->param.mkdev.mode, r->param.mkdev.major,
			      r->param.mkdev.minor);
}

/**
 * ccs_audit_path_number_log - Audit path/number request log.
 *
 * @r: Pointer to "struct ccs_request_info".
 *
 * Returns 0 on success, negative value otherwise.
 */
static int ccs_audit_path_number_log(struct ccs_request_info *r)
{
	const u8 type = r->param.path_number.operation;
	u8 radix;
	char buffer[64];
	switch (type) {
	case CCS_TYPE_CREATE:
	case CCS_TYPE_MKDIR:
	case CCS_TYPE_MKFIFO:
	case CCS_TYPE_MKSOCK:
	case CCS_TYPE_CHMOD:
		radix = CCS_VALUE_TYPE_OCTAL;
		break;
	case CCS_TYPE_IOCTL:
		radix = CCS_VALUE_TYPE_HEXADECIMAL;
		break;
	default:
		radix = CCS_VALUE_TYPE_DECIMAL;
		break;
	}
	ccs_print_ulong(buffer, sizeof(buffer), r->param.path_number.number,
			radix);
	return ccs_supervisor(r, "file %s %s %s\n", ccs_mac_keywords
			      [ccs_pn2mac[type]],
			      r->param.path_number.filename->name, buffer);
}

/**
 * ccs_check_path_acl - Check permission for path operation.
 *
 * @r:   Pointer to "struct ccs_request_info".
 * @ptr: Pointer to "struct ccs_acl_info".
 *
 * Returns true if granted, false otherwise.
 *
 * To be able to use wildcard for domain transition, this function sets
 * matching entry on success. Since the caller holds ccs_read_lock(),
 * it is safe to set matching entry.
 */
static bool ccs_check_path_acl(struct ccs_request_info *r,
			       const struct ccs_acl_info *ptr)
{
	const struct ccs_path_acl *acl = container_of(ptr, typeof(*acl), head);
	if (acl->perm & (1 << r->param.path.operation)) {
		r->param.path.matched_path =
			ccs_compare_name_union(r->param.path.filename,
					       &acl->name);
		return r->param.path.matched_path != NULL;
	}
	return false;
}

/**
 * ccs_check_path_number_acl - Check permission for path number operation.
 *
 * @r:   Pointer to "struct ccs_request_info".
 * @ptr: Pointer to "struct ccs_acl_info".
 *
 * Returns true if granted, false otherwise.
 */
static bool ccs_check_path_number_acl(struct ccs_request_info *r,
				      const struct ccs_acl_info *ptr)
{
	const struct ccs_path_number_acl *acl =
		container_of(ptr, typeof(*acl), head);
	return (acl->perm & (1 << r->param.path_number.operation)) &&
		ccs_compare_number_union(r->param.path_number.number,
					 &acl->number) &&
		ccs_compare_name_union(r->param.path_number.filename,
				       &acl->name);
}

/**
 * ccs_check_path2_acl - Check permission for path path operation.
 *
 * @r:   Pointer to "struct ccs_request_info".
 * @ptr: Pointer to "struct ccs_acl_info".
 *
 * Returns true if granted, false otherwise.
 */
static bool ccs_check_path2_acl(struct ccs_request_info *r,
				const struct ccs_acl_info *ptr)
{
	const struct ccs_path2_acl *acl =
		container_of(ptr, typeof(*acl), head);
	return (acl->perm & (1 << r->param.path2.operation)) &&
		ccs_compare_name_union(r->param.path2.filename1, &acl->name1)
		&& ccs_compare_name_union(r->param.path2.filename2,
					  &acl->name2);
}

/**
 * ccs_check_mkdev_acl - Check permission for path number number number operation.
 *
 * @r:   Pointer to "struct ccs_request_info".
 * @ptr: Pointer to "struct ccs_acl_info".
 *
 * Returns true if granted, false otherwise.
 */
static bool ccs_check_mkdev_acl(struct ccs_request_info *r,
				const struct ccs_acl_info *ptr)
{
	const struct ccs_mkdev_acl *acl =
		container_of(ptr, typeof(*acl), head);
	return (acl->perm & (1 << r->param.mkdev.operation)) &&
		ccs_compare_number_union(r->param.mkdev.mode, &acl->mode) &&
		ccs_compare_number_union(r->param.mkdev.major, &acl->major) &&
		ccs_compare_number_union(r->param.mkdev.minor, &acl->minor) &&
		ccs_compare_name_union(r->param.mkdev.filename, &acl->name);
}

/**
 * ccs_same_path_acl - Check for duplicated "struct ccs_path_acl" entry.
 *
 * @a: Pointer to "struct ccs_acl_info".
 * @b: Pointer to "struct ccs_acl_info".
 *
 * Returns true if @a == @b except permission bits, false otherwise.
 */
static bool ccs_same_path_acl(const struct ccs_acl_info *a,
			      const struct ccs_acl_info *b)
{
	const struct ccs_path_acl *p1 = container_of(a, typeof(*p1), head);
	const struct ccs_path_acl *p2 = container_of(b, typeof(*p2), head);
	return ccs_same_name_union(&p1->name, &p2->name);
}

/**
 * ccs_merge_path_acl - Merge duplicated "struct ccs_path_acl" entry.
 *
 * @a:         Pointer to "struct ccs_acl_info".
 * @b:         Pointer to "struct ccs_acl_info".
 * @is_delete: True for @a &= ~@b, false for @a |= @b.
 *
 * Returns true if @a is empty, false otherwise.
 */
static bool ccs_merge_path_acl(struct ccs_acl_info *a, struct ccs_acl_info *b,
			       const bool is_delete)
{
	u16 * const a_perm = &container_of(a, struct ccs_path_acl, head)->perm;
	u16 perm = *a_perm;
	const u16 b_perm = container_of(b, struct ccs_path_acl, head)->perm;
	if (is_delete)
		perm &= ~b_perm;
	else
		perm |= b_perm;
	*a_perm = perm;
	return !perm;
}

/**
 * ccs_update_path_acl - Update "struct ccs_path_acl" list.
 *
 * @perm:  Permission.
 * @param: Pointer to "struct ccs_acl_param".
 *
 * Returns 0 on success, negative value otherwise.
 *
 * Caller holds ccs_read_lock().
 */
static int ccs_update_path_acl(const u16 perm, struct ccs_acl_param *param)
{
	struct ccs_path_acl e = {
		.head.type = CCS_TYPE_PATH_ACL,
		.perm = perm
	};
	int error;
	if (!ccs_parse_name_union(param, &e.name))
		error = -EINVAL;
	else
		error = ccs_update_domain(&e.head, sizeof(e), param,
					  ccs_same_path_acl,
					  ccs_merge_path_acl);
	ccs_put_name_union(&e.name);
	return error;
}

/**
 * ccs_same_mkdev_acl - Check for duplicated "struct ccs_mkdev_acl" entry.
 *
 * @a: Pointer to "struct ccs_acl_info".
 * @b: Pointer to "struct ccs_acl_info".
 *
 * Returns true if @a == @b except permission bits, false otherwise.
 */
static bool ccs_same_mkdev_acl(const struct ccs_acl_info *a,
			       const struct ccs_acl_info *b)
{
	const struct ccs_mkdev_acl *p1 = container_of(a, typeof(*p1), head);
	const struct ccs_mkdev_acl *p2 = container_of(b, typeof(*p2), head);
	return ccs_same_name_union(&p1->name, &p2->name) &&
		ccs_same_number_union(&p1->mode, &p2->mode) &&
		ccs_same_number_union(&p1->major, &p2->major) &&
		ccs_same_number_union(&p1->minor, &p2->minor);
}

/**
 * ccs_merge_mkdev_acl - Merge duplicated "struct ccs_mkdev_acl" entry.
 *
 * @a:         Pointer to "struct ccs_acl_info".
 * @b:         Pointer to "struct ccs_acl_info".
 * @is_delete: True for @a &= ~@b, false for @a |= @b.
 *
 * Returns true if @a is empty, false otherwise.
 */
static bool ccs_merge_mkdev_acl(struct ccs_acl_info *a, struct ccs_acl_info *b,
				const bool is_delete)
{
	u8 *const a_perm = &container_of(a, struct ccs_mkdev_acl, head)->perm;
	u8 perm = *a_perm;
	const u8 b_perm = container_of(b, struct ccs_mkdev_acl, head)->perm;
	if (is_delete)
		perm &= ~b_perm;
	else
		perm |= b_perm;
	*a_perm = perm;
	return !perm;
}

/**
 * ccs_update_mkdev_acl - Update "struct ccs_mkdev_acl" list.
 *
 * @perm:  Permission.
 * @param: Pointer to "struct ccs_acl_param".
 *
 * Returns 0 on success, negative value otherwise.
 *
 * Caller holds ccs_read_lock().
 */
static int ccs_update_mkdev_acl(const u8 perm, struct ccs_acl_param *param)
{
	struct ccs_mkdev_acl e = {
		.head.type = CCS_TYPE_MKDEV_ACL,
		.perm = perm
	};
	int error;
	if (!ccs_parse_name_union(param, &e.name) ||
	    !ccs_parse_number_union(param, &e.mode) ||
	    !ccs_parse_number_union(param, &e.major) ||
	    !ccs_parse_number_union(param, &e.minor))
		error = -EINVAL;
	else
		error = ccs_update_domain(&e.head, sizeof(e), param,
					  ccs_same_mkdev_acl,
					  ccs_merge_mkdev_acl);
	ccs_put_name_union(&e.name);
	ccs_put_number_union(&e.mode);
	ccs_put_number_union(&e.major);
	ccs_put_number_union(&e.minor);
	return error;
}

/**
 * ccs_same_path2_acl - Check for duplicated "struct ccs_path2_acl" entry.
 *
 * @a: Pointer to "struct ccs_acl_info".
 * @b: Pointer to "struct ccs_acl_info".
 *
 * Returns true if @a == @b except permission bits, false otherwise.
 */
static bool ccs_same_path2_acl(const struct ccs_acl_info *a,
			       const struct ccs_acl_info *b)
{
	const struct ccs_path2_acl *p1 = container_of(a, typeof(*p1), head);
	const struct ccs_path2_acl *p2 = container_of(b, typeof(*p2), head);
	return ccs_same_name_union(&p1->name1, &p2->name1) &&
		ccs_same_name_union(&p1->name2, &p2->name2);
}

/**
 * ccs_merge_path2_acl - Merge duplicated "struct ccs_path2_acl" entry.
 *
 * @a:         Pointer to "struct ccs_acl_info".
 * @b:         Pointer to "struct ccs_acl_info".
 * @is_delete: True for @a &= ~@b, false for @a |= @b.
 *
 * Returns true if @a is empty, false otherwise.
 */
static bool ccs_merge_path2_acl(struct ccs_acl_info *a, struct ccs_acl_info *b,
				const bool is_delete)
{
	u8 * const a_perm = &container_of(a, struct ccs_path2_acl, head)->perm;
	u8 perm = *a_perm;
	const u8 b_perm = container_of(b, struct ccs_path2_acl, head)->perm;
	if (is_delete)
		perm &= ~b_perm;
	else
		perm |= b_perm;
	*a_perm = perm;
	return !perm;
}

/**
 * ccs_update_path2_acl - Update "struct ccs_path2_acl" list.
 *
 * @perm:  Permission.
 * @param: Pointer to "struct ccs_acl_param".
 *
 * Returns 0 on success, negative value otherwise.
 *
 * Caller holds ccs_read_lock().
 */
static int ccs_update_path2_acl(const u8 perm, struct ccs_acl_param *param)
{
	struct ccs_path2_acl e = {
		.head.type = CCS_TYPE_PATH2_ACL,
		.perm = perm
	};
	int error;
	if (!ccs_parse_name_union(param, &e.name1) ||
	    !ccs_parse_name_union(param, &e.name2))
		error = -EINVAL;
	else
		error = ccs_update_domain(&e.head, sizeof(e), param,
					  ccs_same_path2_acl,
					  ccs_merge_path2_acl);
	ccs_put_name_union(&e.name1);
	ccs_put_name_union(&e.name2);
	return error;
}

/**
 * ccs_same_mount_acl - Check for duplicated "struct ccs_mount_acl" entry.
 *
 * @a: Pointer to "struct ccs_acl_info".
 * @b: Pointer to "struct ccs_acl_info".
 *
 * Returns true if @a == @b, false otherwise.
 */
static bool ccs_same_mount_acl(const struct ccs_acl_info *a,
			       const struct ccs_acl_info *b)
{
	const struct ccs_mount_acl *p1 = container_of(a, typeof(*p1), head);
	const struct ccs_mount_acl *p2 = container_of(b, typeof(*p2), head);
	return ccs_same_name_union(&p1->dev_name, &p2->dev_name) &&
		ccs_same_name_union(&p1->dir_name, &p2->dir_name) &&
		ccs_same_name_union(&p1->fs_type, &p2->fs_type) &&
		ccs_same_number_union(&p1->flags, &p2->flags);
}

/**
 * ccs_update_mount_acl - Write "struct ccs_mount_acl" list.
 *
 * @param: Pointer to "struct ccs_acl_param".
 *
 * Returns 0 on success, negative value otherwise.
 *
 * Caller holds ccs_read_lock().
 */
static int ccs_update_mount_acl(struct ccs_acl_param *param)
{
	struct ccs_mount_acl e = { .head.type = CCS_TYPE_MOUNT_ACL };
	int error;
	if (!ccs_parse_name_union(param, &e.dev_name) ||
	    !ccs_parse_name_union(param, &e.dir_name) ||
	    !ccs_parse_name_union(param, &e.fs_type) ||
	    !ccs_parse_number_union(param, &e.flags))
		error = -EINVAL;
	else
		error = ccs_update_domain(&e.head, sizeof(e), param,
					  ccs_same_mount_acl, NULL);
	ccs_put_name_union(&e.dev_name);
	ccs_put_name_union(&e.dir_name);
	ccs_put_name_union(&e.fs_type);
	ccs_put_number_union(&e.flags);
	return error;
}

/**
 * ccs_path_permission - Check permission for path operation.
 *
 * @r:         Pointer to "struct ccs_request_info".
 * @operation: Type of operation.
 * @filename:  Filename to check.
 *
 * Returns 0 on success, CCS_RETRY_REQUEST on retry, negative value otherwise.
 *
 * Caller holds ccs_read_lock().
 */
int ccs_path_permission(struct ccs_request_info *r, u8 operation,
			const struct ccs_path_info *filename)
{
	int error;
	r->type = ccs_p2mac[operation];
	r->mode = ccs_get_mode(r->profile, r->type);
	if (r->mode == CCS_CONFIG_DISABLED)
		return 0;
	r->param_type = CCS_TYPE_PATH_ACL;
	r->param.path.filename = filename;
	r->param.path.operation = operation;
	do {
		ccs_check_acl(r, ccs_check_path_acl);
		error = ccs_audit_path_log(r);
		/*
		 * Do not retry for execute request, for aggregator may have
		 * changed.
		 */
	} while (error == CCS_RETRY_REQUEST && operation != CCS_TYPE_EXECUTE);
	return error;
}

#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 32)

/**
 * __ccs_save_open_mode - Remember original flags passed to sys_open().
 *
 * @mode: Flags passed to sys_open().
 *
 * Returns nothing.
 *
 * TOMOYO does not check "file write" if open(path, O_TRUNC | O_RDONLY) was
 * requested because write() is not permitted. Instead, TOMOYO checks
 * "file truncate" if O_TRUNC is passed.
 *
 * TOMOYO does not check "file read" and "file write" if open(path, 3) was
 * requested because read()/write() are not permitted. Instead, TOMOYO checks
 * "file ioctl" when ioctl() is requested.
 */
static void __ccs_save_open_mode(int mode)
{
	if ((mode & 3) == 3)
		ccs_current_security()->ccs_flags |= CCS_OPEN_FOR_IOCTL_ONLY;
#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 14)
	/* O_TRUNC passes MAY_WRITE to ccs_open_permission(). */
	else if (!(mode & 3) && (mode & O_TRUNC))
		ccs_current_security()->ccs_flags |=
			CCS_OPEN_FOR_READ_TRUNCATE;
#endif
}

/**
 * __ccs_clear_open_mode - Forget original flags passed to sys_open().
 *
 * Returns nothing.
 */
static void __ccs_clear_open_mode(void)
{
	ccs_current_security()->ccs_flags &= ~(CCS_OPEN_FOR_IOCTL_ONLY |
					       CCS_OPEN_FOR_READ_TRUNCATE);
}

#endif

/**
 * __ccs_open_permission - Check permission for "read" and "write".
 *
 * @dentry: Pointer to "struct dentry".
 * @mnt:    Pointer to "struct vfsmount". Maybe NULL.
 * @flag:   Flags for open().
 *
 * Returns 0 on success, negative value otherwise.
 */
static int __ccs_open_permission(struct dentry *dentry, struct vfsmount *mnt,
				 const int flag)
{
	struct ccs_request_info r;
	struct ccs_obj_info obj = {
		.path1.dentry = dentry,
		.path1.mnt = mnt,
	};
	const u32 ccs_flags = ccs_current_flags();
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 33)
	const u8 acc_mode = (flag & 3) == 3 ? 0 : ACC_MODE(flag);
#else
	const u8 acc_mode = (ccs_flags & CCS_OPEN_FOR_IOCTL_ONLY) ? 0 :
#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 14)
		(ccs_flags & CCS_OPEN_FOR_READ_TRUNCATE) ? 4 :
#endif
		ACC_MODE(flag);
#endif
	int error = 0;
	struct ccs_path_info buf;
	int idx;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 30)
	if (current->in_execve && !(ccs_flags & CCS_TASK_IS_IN_EXECVE))
		return 0;
#endif
	buf.name = NULL;
	r.mode = CCS_CONFIG_DISABLED;
	idx = ccs_read_lock();
	if (acc_mode && ccs_init_request_info(&r, CCS_MAC_FILE_OPEN)
	    != CCS_CONFIG_DISABLED) {
		if (!ccs_get_realpath(&buf, dentry, mnt)) {
			error = -ENOMEM;
			goto out;
		}
		r.obj = &obj;
		if (acc_mode & MAY_READ)
			error = ccs_path_permission(&r, CCS_TYPE_READ, &buf);
		if (!error && (acc_mode & MAY_WRITE))
			error = ccs_path_permission(&r, (flag & O_APPEND) ?
						    CCS_TYPE_APPEND :
						    CCS_TYPE_WRITE, &buf);
	}
#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 32)
	if (!error && (flag & O_TRUNC) &&
	    ccs_init_request_info(&r, CCS_MAC_FILE_TRUNCATE)
	    != CCS_CONFIG_DISABLED) {
		if (!buf.name && !ccs_get_realpath(&buf, dentry, mnt)) {
			error = -ENOMEM;
			goto out;
		}
		r.obj = &obj;
		error = ccs_path_permission(&r, CCS_TYPE_TRUNCATE, &buf);
	}
#endif
out:
	kfree(buf.name);
	ccs_read_unlock(idx);
	if (r.mode != CCS_CONFIG_ENFORCING)
		error = 0;
	return error;
}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 33)

/**
 * ccs_new_open_permission - Check permission for "read" and "write".
 *
 * @filp: Pointer to "struct file".
 *
 * Returns 0 on success, negative value otherwise.
 */
static int ccs_new_open_permission(struct file *filp)
{
	return __ccs_open_permission(filp->f_path.dentry, filp->f_path.mnt,
				     filp->f_flags);
}

#endif

/**
 * ccs_path_perm - Check permission for "unlink", "rmdir", "truncate", "symlink", "append", "getattr", "chroot" and "unmount".
 *
 * @operation: Type of operation.
 * @dentry:    Pointer to "struct dentry".
 * @mnt:       Pointer to "struct vfsmount". Maybe NULL.
 * @target:    Symlink's target if @operation is CCS_TYPE_SYMLINK,
 *             NULL otherwise.
 *
 * Returns 0 on success, negative value otherwise.
 */
static int ccs_path_perm(const u8 operation, struct dentry *dentry,
			 struct vfsmount *mnt, const char *target)
{
	struct ccs_request_info r;
	struct ccs_obj_info obj = {
		.path1.dentry = dentry,
		.path1.mnt = mnt,
	};
	int error = 0;
	struct ccs_path_info buf;
	bool is_enforce = false;
	struct ccs_path_info symlink_target;
	int idx;
	buf.name = NULL;
	symlink_target.name = NULL;
	idx = ccs_read_lock();
	if (ccs_init_request_info(&r, ccs_p2mac[operation])
	    == CCS_CONFIG_DISABLED)
		goto out;
	is_enforce = (r.mode == CCS_CONFIG_ENFORCING);
	error = -ENOMEM;
	if (!ccs_get_realpath(&buf, dentry, mnt))
		goto out;
	r.obj = &obj;
	switch (operation) {
	case CCS_TYPE_RMDIR:
	case CCS_TYPE_CHROOT:
		ccs_add_slash(&buf);
		break;
	case CCS_TYPE_SYMLINK:
		symlink_target.name = ccs_encode(target);
		if (!symlink_target.name)
			goto out;
		ccs_fill_path_info(&symlink_target);
		obj.symlink_target = &symlink_target;
		break;
	}
	error = ccs_path_permission(&r, operation, &buf);
	if (operation == CCS_TYPE_SYMLINK)
		kfree(symlink_target.name);
out:
	kfree(buf.name);
	ccs_read_unlock(idx);
	if (!is_enforce)
		error = 0;
	return error;
}

/**
 * ccs_mkdev_perm - Check permission for "mkblock" and "mkchar".
 *
 * @operation: Type of operation. (CCS_TYPE_MKCHAR or CCS_TYPE_MKBLOCK)
 * @dentry:    Pointer to "struct dentry".
 * @mnt:       Pointer to "struct vfsmount". Maybe NULL.
 * @mode:      Create mode.
 * @dev:       Device number.
 *
 * Returns 0 on success, negative value otherwise.
 */
static int ccs_mkdev_perm(const u8 operation, struct dentry *dentry,
			  struct vfsmount *mnt, const unsigned int mode,
			  unsigned int dev)
{
	struct ccs_request_info r;
	struct ccs_obj_info obj = {
		.path1.dentry = dentry,
		.path1.mnt = mnt,
	};
	int error = 0;
	struct ccs_path_info buf;
	bool is_enforce = false;
	int idx;
	idx = ccs_read_lock();
	if (ccs_init_request_info(&r, ccs_pnnn2mac[operation])
	    == CCS_CONFIG_DISABLED)
		goto out;
	is_enforce = (r.mode == CCS_CONFIG_ENFORCING);
	error = -EPERM;
	if (!capable(CAP_MKNOD))
		goto out;
	error = -ENOMEM;
	if (!ccs_get_realpath(&buf, dentry, mnt))
		goto out;
	r.obj = &obj;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 0)
	dev = new_decode_dev(dev);
#endif
	r.param_type = CCS_TYPE_MKDEV_ACL;
	r.param.mkdev.filename = &buf;
	r.param.mkdev.operation = operation;
	r.param.mkdev.mode = mode;
	r.param.mkdev.major = MAJOR(dev);
	r.param.mkdev.minor = MINOR(dev);
	do {
		ccs_check_acl(&r, ccs_check_mkdev_acl);
		error = ccs_audit_mkdev_log(&r);
	} while (error == CCS_RETRY_REQUEST);
	kfree(buf.name);
out:
	ccs_read_unlock(idx);
	if (!is_enforce)
		error = 0;
	return error;
}

/**
 * ccs_path2_perm - Check permission for "rename", "link" and "pivot_root".
 *
 * @operation: Type of operation.
 * @dentry1:   Pointer to "struct dentry".
 * @mnt1:      Pointer to "struct vfsmount". Maybe NULL.
 * @dentry2:   Pointer to "struct dentry".
 * @mnt2:      Pointer to "struct vfsmount". Maybe NULL.
 *
 * Returns 0 on success, negative value otherwise.
 */
static int ccs_path2_perm(const u8 operation, struct dentry *dentry1,
			  struct vfsmount *mnt1, struct dentry *dentry2,
			  struct vfsmount *mnt2)
{
	struct ccs_request_info r;
	int error = 0;
	struct ccs_path_info buf1;
	struct ccs_path_info buf2;
	bool is_enforce = false;
	struct ccs_obj_info obj = {
		.path1.dentry = dentry1,
		.path1.mnt = mnt1,
		.path2.dentry = dentry2,
		.path2.mnt = mnt2,
	};
	int idx;
	buf1.name = NULL;
	buf2.name = NULL;
	idx = ccs_read_lock();
	if (ccs_init_request_info(&r, ccs_pp2mac[operation])
	    == CCS_CONFIG_DISABLED)
		goto out;
	is_enforce = (r.mode == CCS_CONFIG_ENFORCING);
	error = -ENOMEM;
	if (!ccs_get_realpath(&buf1, dentry1, mnt1) ||
	    !ccs_get_realpath(&buf2, dentry2, mnt2))
		goto out;
	switch (operation) {
	case CCS_TYPE_RENAME:
	case CCS_TYPE_LINK:
		if (!dentry1->d_inode || !S_ISDIR(dentry1->d_inode->i_mode))
			break;
		/* fall through */
	case CCS_TYPE_PIVOT_ROOT:
		ccs_add_slash(&buf1);
		ccs_add_slash(&buf2);
		break;
	}
	r.obj = &obj;
	r.param_type = CCS_TYPE_PATH2_ACL;
	r.param.path2.operation = operation;
	r.param.path2.filename1 = &buf1;
	r.param.path2.filename2 = &buf2;
	do {
		ccs_check_acl(&r, ccs_check_path2_acl);
		error = ccs_audit_path2_log(&r);
	} while (error == CCS_RETRY_REQUEST);
out:
	kfree(buf1.name);
	kfree(buf2.name);
	ccs_read_unlock(idx);
	if (!is_enforce)
		error = 0;
	return error;
}

/**
 * ccs_same_path_number_acl - Check for duplicated "struct ccs_path_number_acl" entry.
 *
 * @a: Pointer to "struct ccs_acl_info".
 * @b: Pointer to "struct ccs_acl_info".
 *
 * Returns true if @a == @b except permission bits, false otherwise.
 */
static bool ccs_same_path_number_acl(const struct ccs_acl_info *a,
				     const struct ccs_acl_info *b)
{
	const struct ccs_path_number_acl *p1 = container_of(a, typeof(*p1),
							    head);
	const struct ccs_path_number_acl *p2 = container_of(b, typeof(*p2),
							    head);
	return ccs_same_name_union(&p1->name, &p2->name) &&
		ccs_same_number_union(&p1->number, &p2->number);
}

/**
 * ccs_merge_path_number_acl - Merge duplicated "struct ccs_path_number_acl" entry.
 *
 * @a:         Pointer to "struct ccs_acl_info".
 * @b:         Pointer to "struct ccs_acl_info".
 * @is_delete: True for @a &= ~@b, false for @a |= @b.
 *
 * Returns true if @a is empty, false otherwise.
 */
static bool ccs_merge_path_number_acl(struct ccs_acl_info *a,
				      struct ccs_acl_info *b,
				      const bool is_delete)
{
	u8 * const a_perm = &container_of(a, struct ccs_path_number_acl, head)
		->perm;
	u8 perm = *a_perm;
	const u8 b_perm = container_of(b, struct ccs_path_number_acl, head)
		->perm;
	if (is_delete)
		perm &= ~b_perm;
	else
		perm |= b_perm;
	*a_perm = perm;
	return !perm;
}

/**
 * ccs_update_path_number_acl - Update create/mkdir/mkfifo/mksock/ioctl/chmod/chown/chgrp ACL.
 *
 * @perm:  Permission.
 * @param: Pointer to "struct ccs_acl_param".
 *
 * Returns 0 on success, negative value otherwise.
 */
static int ccs_update_path_number_acl(const u8 perm,
				      struct ccs_acl_param *param)
{
	struct ccs_path_number_acl e = {
		.head.type = CCS_TYPE_PATH_NUMBER_ACL,
		.perm = perm
	};
	int error;
	if (!ccs_parse_name_union(param, &e.name) ||
	    !ccs_parse_number_union(param, &e.number))
		error = -EINVAL;
	else
		error = ccs_update_domain(&e.head, sizeof(e), param,
					  ccs_same_path_number_acl,
					  ccs_merge_path_number_acl);
	ccs_put_name_union(&e.name);
	ccs_put_number_union(&e.number);
	return error;
}

/**
 * ccs_path_number_perm - Check permission for "create", "mkdir", "mkfifo", "mksock", "ioctl", "chmod", "chown", "chgrp".
 *
 * @type:   Type of operation.
 * @dentry: Pointer to "struct dentry".
 * @vfsmnt: Pointer to "struct vfsmount". Maybe NULL.
 * @number: Number.
 *
 * Returns 0 on success, negative value otherwise.
 */
static int ccs_path_number_perm(const u8 type, struct dentry *dentry,
				struct vfsmount *vfsmnt, unsigned long number)
{
	struct ccs_request_info r;
	struct ccs_obj_info obj = {
		.path1.dentry = dentry,
		.path1.mnt = vfsmnt,
	};
	int error = 0;
	struct ccs_path_info buf;
	int idx;
	if (!dentry)
		return 0;
	idx = ccs_read_lock();
	if (ccs_init_request_info(&r, ccs_pn2mac[type]) == CCS_CONFIG_DISABLED)
		goto out;
	error = -ENOMEM;
	if (!ccs_get_realpath(&buf, dentry, vfsmnt))
		goto out;
	r.obj = &obj;
	if (type == CCS_TYPE_MKDIR)
		ccs_add_slash(&buf);
	r.param_type = CCS_TYPE_PATH_NUMBER_ACL;
	r.param.path_number.operation = type;
	r.param.path_number.filename = &buf;
	r.param.path_number.number = number;
	do {
		ccs_check_acl(&r, ccs_check_path_number_acl);
		error = ccs_audit_path_number_log(&r);
	} while (error == CCS_RETRY_REQUEST);
	kfree(buf.name);
out:
	ccs_read_unlock(idx);
	if (r.mode != CCS_CONFIG_ENFORCING)
		error = 0;
	return error;
}

/**
 * __ccs_ioctl_permission - Check permission for "ioctl".
 *
 * @filp: Pointer to "struct file".
 * @cmd:  Ioctl command number.
 * @arg:  Param for @cmd.
 *
 * Returns 0 on success, negative value otherwise.
 */
static int __ccs_ioctl_permission(struct file *filp, unsigned int cmd,
				  unsigned long arg)
{
	return ccs_path_number_perm(CCS_TYPE_IOCTL, filp->f_dentry,
				    filp->f_vfsmnt, cmd);
}

/**
 * __ccs_chmod_permission - Check permission for "chmod".
 *
 * @dentry: Pointer to "struct dentry".
 * @vfsmnt: Pointer to "struct vfsmount". Maybe NULL.
 * @mode:   Mode.
 *
 * Returns 0 on success, negative value otherwise.
 */
static int __ccs_chmod_permission(struct dentry *dentry,
				  struct vfsmount *vfsmnt, mode_t mode)
{
	if (mode == (mode_t) -1)
		return 0;
	return ccs_path_number_perm(CCS_TYPE_CHMOD, dentry, vfsmnt,
				    mode & S_IALLUGO);
}

/**
 * __ccs_chown_permission - Check permission for "chown/chgrp".
 *
 * @dentry: Pointer to "struct dentry".
 * @vfsmnt: Pointer to "struct vfsmount". Maybe NULL.
 * @user:   User ID.
 * @group:  Group ID.
 *
 * Returns 0 on success, negative value otherwise.
 */
static int __ccs_chown_permission(struct dentry *dentry,
				  struct vfsmount *vfsmnt, uid_t user,
				  gid_t group)
{
	int error = 0;
	if (user == (uid_t) -1 && group == (gid_t) -1)
		return 0;
	if (user != (uid_t) -1)
		error = ccs_path_number_perm(CCS_TYPE_CHOWN, dentry, vfsmnt,
					     user);
	if (!error && group != (gid_t) -1)
		error = ccs_path_number_perm(CCS_TYPE_CHGRP, dentry, vfsmnt,
					     group);
	return error;
}

/**
 * __ccs_fcntl_permission - Check permission for changing O_APPEND flag.
 *
 * @file: Pointer to "struct file".
 * @cmd:  Command number.
 * @arg:  Value for @cmd.
 *
 * Returns 0 on success, negative value otherwise.
 */
static int __ccs_fcntl_permission(struct file *file, unsigned int cmd,
				  unsigned long arg)
{
	if (!(cmd == F_SETFL && ((arg ^ file->f_flags) & O_APPEND)))
		return 0;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 33)
	return __ccs_open_permission(file->f_dentry, file->f_vfsmnt,
				     O_WRONLY | (arg & O_APPEND));
#elif defined(RHEL_MAJOR) && RHEL_MAJOR == 6
	return __ccs_open_permission(file->f_dentry, file->f_vfsmnt,
				     O_WRONLY | (arg & O_APPEND));
#else
	return __ccs_open_permission(file->f_dentry, file->f_vfsmnt,
				     (O_WRONLY + 1) | (arg & O_APPEND));
#endif
}

/**
 * __ccs_pivot_root_permission - Check permission for pivot_root().
 *
 * @old_path: Pointer to "struct path".
 * @new_path: Pointer to "struct path".
 *
 * Returns 0 on success, negative value otherwise.
 */
static int __ccs_pivot_root_permission(struct path *old_path,
				       struct path *new_path)
{
	return ccs_path2_perm(CCS_TYPE_PIVOT_ROOT, new_path->dentry,
			      new_path->mnt, old_path->dentry, old_path->mnt);
}

/**
 * __ccs_chroot_permission - Check permission for chroot().
 *
 * @path: Pointer to "struct path".
 *
 * Returns 0 on success, negative value otherwise.
 */
static int __ccs_chroot_permission(struct path *path)
{
	return ccs_path_perm(CCS_TYPE_CHROOT, path->dentry, path->mnt, NULL);
}

/**
 * __ccs_umount_permission - Check permission for unmount.
 *
 * @mnt:   Pointer to "struct vfsmount".
 * @flags: Unused.
 *
 * Returns 0 on success, negative value otherwise.
 */
static int __ccs_umount_permission(struct vfsmount *mnt, int flags)
{
	return ccs_path_perm(CCS_TYPE_UMOUNT, mnt->mnt_root, mnt, NULL);
}

/**
 * ccs_write_file - Update file related list.
 *
 * @param: Pointer to "struct ccs_acl_param".
 *
 * Returns 0 on success, negative value otherwise.
 *
 * Caller holds ccs_read_lock().
 */
int ccs_write_file(struct ccs_acl_param *param)
{
	u16 perm = 0;
	u8 type;
	const char *operation = ccs_read_token(param);
	for (type = 0; type < CCS_MAX_PATH_OPERATION; type++)
		if (ccs_permstr(operation, ccs_path_keyword[type]))
			perm |= 1 << type;
	if (perm)
		return ccs_update_path_acl(perm, param);
	for (type = 0; type < CCS_MAX_PATH2_OPERATION; type++)
		if (ccs_permstr(operation, ccs_mac_keywords[ccs_pp2mac[type]]))
			perm |= 1 << type;
	if (perm)
		return ccs_update_path2_acl(perm, param);
	for (type = 0; type < CCS_MAX_PATH_NUMBER_OPERATION; type++)
		if (ccs_permstr(operation, ccs_mac_keywords[ccs_pn2mac[type]]))
			perm |= 1 << type;
	if (perm)
		return ccs_update_path_number_acl(perm, param);
	for (type = 0; type < CCS_MAX_MKDEV_OPERATION; type++)
		if (ccs_permstr(operation,
				ccs_mac_keywords[ccs_pnnn2mac[type]]))
			perm |= 1 << type;
	if (perm)
		return ccs_update_mkdev_acl(perm, param);
	if (ccs_permstr(operation, ccs_mac_keywords[CCS_MAC_FILE_MOUNT]))
		return ccs_update_mount_acl(param);
	return -EINVAL;
}

/**
 * __ccs_mknod_permission - Check permission for vfs_mknod().
 *
 * @dentry: Pointer to "struct dentry".
 * @mnt:    Pointer to "struct vfsmount". Maybe NULL.
 * @mode:   Device type and permission.
 * @dev:    Device number for block or character device.
 *
 * Returns 0 on success, negative value otherwise.
 */
static int __ccs_mknod_permission(struct dentry *dentry, struct vfsmount *mnt,
				  const unsigned int mode, unsigned int dev)
{
	int error = 0;
	const unsigned int perm = mode & S_IALLUGO;
	switch (mode & S_IFMT) {
	case S_IFCHR:
		error = ccs_mkdev_perm(CCS_TYPE_MKCHAR, dentry, mnt, perm,
				       dev);
		break;
	case S_IFBLK:
		error = ccs_mkdev_perm(CCS_TYPE_MKBLOCK, dentry, mnt, perm,
				       dev);
		break;
	case S_IFIFO:
		error = ccs_path_number_perm(CCS_TYPE_MKFIFO, dentry, mnt,
					     perm);
		break;
	case S_IFSOCK:
		error = ccs_path_number_perm(CCS_TYPE_MKSOCK, dentry, mnt,
					     perm);
		break;
	case 0:
	case S_IFREG:
		error = ccs_path_number_perm(CCS_TYPE_CREATE, dentry, mnt,
					     perm);
		break;
	}
	return error;
}

/**
 * __ccs_mkdir_permission - Check permission for vfs_mkdir().
 *
 * @dentry: Pointer to "struct dentry".
 * @mnt:    Pointer to "struct vfsmount". Maybe NULL.
 * @mode:   Create mode.
 *
 * Returns 0 on success, negative value otherwise.
 */
static int __ccs_mkdir_permission(struct dentry *dentry, struct vfsmount *mnt,
				  unsigned int mode)
{
	return ccs_path_number_perm(CCS_TYPE_MKDIR, dentry, mnt, mode);
}

/**
 * __ccs_rmdir_permission - Check permission for vfs_rmdir().
 *
 * @dentry: Pointer to "struct dentry".
 * @mnt:    Pointer to "struct vfsmount". Maybe NULL.
 *
 * Returns 0 on success, negative value otherwise.
 */
static int __ccs_rmdir_permission(struct dentry *dentry, struct vfsmount *mnt)
{
	return ccs_path_perm(CCS_TYPE_RMDIR, dentry, mnt, NULL);
}

/**
 * __ccs_unlink_permission - Check permission for vfs_unlink().
 *
 * @dentry: Pointer to "struct dentry".
 * @mnt:    Pointer to "struct vfsmount". Maybe NULL.
 *
 * Returns 0 on success, negative value otherwise.
 */
static int __ccs_unlink_permission(struct dentry *dentry, struct vfsmount *mnt)
{
	return ccs_path_perm(CCS_TYPE_UNLINK, dentry, mnt, NULL);
}

/**
 * __ccs_getattr_permission - Check permission for vfs_getattr().
 *
 * @mnt:    Pointer to "struct vfsmount". Maybe NULL.
 * @dentry: Pointer to "struct dentry".
 *
 * Returns 0 on success, negative value otherwise.
 */
static int __ccs_getattr_permission(struct vfsmount *mnt,
				    struct dentry *dentry)
{
	return ccs_path_perm(CCS_TYPE_GETATTR, dentry, mnt, NULL);
}

/**
 * __ccs_symlink_permission - Check permission for vfs_symlink().
 *
 * @dentry: Pointer to "struct dentry".
 * @mnt:    Pointer to "struct vfsmount". Maybe NULL.
 * @from:   Content of symlink.
 *
 * Returns 0 on success, negative value otherwise.
 */
static int __ccs_symlink_permission(struct dentry *dentry,
				    struct vfsmount *mnt, const char *from)
{
	return ccs_path_perm(CCS_TYPE_SYMLINK, dentry, mnt, from);
}

/**
 * __ccs_truncate_permission - Check permission for notify_change().
 *
 * @dentry: Pointer to "struct dentry".
 * @mnt:    Pointer to "struct vfsmount". Maybe NULL.
 *
 * Returns 0 on success, negative value otherwise.
 */
static int __ccs_truncate_permission(struct dentry *dentry,
				     struct vfsmount *mnt)
{
	return ccs_path_perm(CCS_TYPE_TRUNCATE, dentry, mnt, NULL);
}

/**
 * __ccs_rename_permission - Check permission for vfs_rename().
 *
 * @old_dentry: Pointer to "struct dentry".
 * @new_dentry: Pointer to "struct dentry".
 * @mnt:        Pointer to "struct vfsmount". Maybe NULL.
 *
 * Returns 0 on success, negative value otherwise.
 */
static int __ccs_rename_permission(struct dentry *old_dentry,
				   struct dentry *new_dentry,
				   struct vfsmount *mnt)
{
	return ccs_path2_perm(CCS_TYPE_RENAME, old_dentry, mnt, new_dentry,
			      mnt);
}

/**
 * __ccs_link_permission - Check permission for vfs_link().
 *
 * @old_dentry: Pointer to "struct dentry".
 * @new_dentry: Pointer to "struct dentry".
 * @mnt:        Pointer to "struct vfsmount". Maybe NULL.
 *
 * Returns 0 on success, negative value otherwise.
 */
static int __ccs_link_permission(struct dentry *old_dentry,
				 struct dentry *new_dentry,
				 struct vfsmount *mnt)
{
	return ccs_path2_perm(CCS_TYPE_LINK, old_dentry, mnt, new_dentry, mnt);
}

#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 30)

/**
 * __ccs_open_exec_permission - Check permission for open_exec().
 *
 * @dentry: Pointer to "struct dentry".
 * @mnt:    Pointer to "struct vfsmount".
 *
 * Returns 0 on success, negative value otherwise.
 */
static int __ccs_open_exec_permission(struct dentry *dentry,
				      struct vfsmount *mnt)
{
	return (ccs_current_flags() & CCS_TASK_IS_IN_EXECVE) ?
		__ccs_open_permission(dentry, mnt, O_RDONLY + 1) : 0;
}

/**
 * __ccs_uselib_permission - Check permission for sys_uselib().
 *
 * @dentry: Pointer to "struct dentry".
 * @mnt:    Pointer to "struct vfsmount".
 *
 * Returns 0 on success, negative value otherwise.
 */
static int __ccs_uselib_permission(struct dentry *dentry, struct vfsmount *mnt)
{
	return __ccs_open_permission(dentry, mnt, O_RDONLY + 1);
}

#endif

#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 18) || (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 33) && defined(CONFIG_SYSCTL_SYSCALL))

/**
 * __ccs_parse_table - Check permission for parse_table().
 *
 * @name:   Pointer to "int __user".
 * @nlen:   Number of elements in @name.
 * @oldval: Pointer to "void __user".
 * @newval: Pointer to "void __user".
 * @table:  Pointer to "struct ctl_table".
 *
 * Returns 0 on success, negative value otherwise.
 *
 * Note that this function is racy because this function checks values in
 * userspace memory which could be changed after permission check.
 */
static int __ccs_parse_table(int __user *name, int nlen, void __user *oldval,
			     void __user *newval, struct ctl_table *table)
{
	int n;
	int error = -ENOMEM;
	int op = 0;
	struct ccs_path_info buf;
	char *buffer = NULL;
	struct ccs_request_info r;
	int idx;
	if (oldval)
		op |= 004;
	if (newval)
		op |= 002;
	if (!op) /* Neither read nor write */
		return 0;
	idx = ccs_read_lock();
	if (ccs_init_request_info(&r, CCS_MAC_FILE_OPEN)
	    == CCS_CONFIG_DISABLED) {
		error = 0;
		goto out;
	}
	buffer = kmalloc(PAGE_SIZE, CCS_GFP_FLAGS);
	if (!buffer)
		goto out;
	snprintf(buffer, PAGE_SIZE - 1, "proc:/sys");
repeat:
	if (!nlen) {
		error = -ENOTDIR;
		goto out;
	}
	if (get_user(n, name)) {
		error = -EFAULT;
		goto out;
	}
	for ( ; table->ctl_name
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 21)
		      || table->procname
#endif
		      ; table++) {
		int pos;
		const char *cp;
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 21)
		if (n != table->ctl_name && table->ctl_name != CTL_ANY)
			continue;
#else
		if (!n || n != table->ctl_name)
			continue;
#endif
		pos = strlen(buffer);
		cp = table->procname;
		error = -ENOMEM;
		if (cp) {
			int len = strlen(cp);
			if (len + 2 > PAGE_SIZE - 1)
				goto out;
			buffer[pos++] = '/';
			memmove(buffer + pos, cp, len + 1);
		} else {
			/* Assume nobody assigns "=\$=" for procname. */
			snprintf(buffer + pos, PAGE_SIZE - pos - 1,
				 "/=%d=", table->ctl_name);
			if (!memchr(buffer, '\0', PAGE_SIZE - 2))
				goto out;
		}
		if (!table->child)
			goto no_child;
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 21)
		if (!table->strategy)
			goto no_strategy;
		/* printk("sysctl='%s'\n", buffer); */
		buf.name = ccs_encode(buffer);
		if (!buf.name)
			goto out;
		ccs_fill_path_info(&buf);
		if (op & MAY_READ)
			error = ccs_path_permission(&r, CCS_TYPE_READ, &buf);
		else
			error = 0;
		if (!error && (op & MAY_WRITE))
			error = ccs_path_permission(&r, CCS_TYPE_WRITE, &buf);
		kfree(buf.name);
		if (error)
			goto out;
no_strategy:
#endif
		name++;
		nlen--;
		table = table->child;
		goto repeat;
no_child:
		/* printk("sysctl='%s'\n", buffer); */
		buf.name = ccs_encode(buffer);
		if (!buf.name)
			goto out;
		ccs_fill_path_info(&buf);
		if (op & MAY_READ)
			error = ccs_path_permission(&r, CCS_TYPE_READ, &buf);
		else
			error = 0;
		if (!error && (op & MAY_WRITE))
			error = ccs_path_permission(&r, CCS_TYPE_WRITE, &buf);
		kfree(buf.name);
		goto out;
	}
	error = -ENOTDIR;
out:
	ccs_read_unlock(idx);
	kfree(buffer);
	return error;
}

#endif

#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 24)

/**
 * ccs_old_pivot_root_permission - Check permission for pivot_root().
 *
 * @old_nd: Pointer to "struct nameidata".
 * @new_nd: Pointer to "struct nameidata".
 *
 * Returns 0 on success, negative value otherwise.
 */
static int ccs_old_pivot_root_permission(struct nameidata *old_nd,
					 struct nameidata *new_nd)
{
	struct path old_path = { old_nd->mnt, old_nd->dentry };
	struct path new_path = { new_nd->mnt, new_nd->dentry };
	return __ccs_pivot_root_permission(&old_path, &new_path);
}

/**
 * ccs_old_chroot_permission - Check permission for chroot().
 *
 * @nd: Pointer to "struct nameidata".
 *
 * Returns 0 on success, negative value otherwise.
 */
static int ccs_old_chroot_permission(struct nameidata *nd)
{
	struct path path = { nd->mnt, nd->dentry };
	return __ccs_chroot_permission(&path);
}

#endif

/**
 * ccs_file_init - Register file related hooks.
 *
 * Returns nothing.
 */
void __init ccs_file_init(void)
{
#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 32)
	ccsecurity_ops.save_open_mode = __ccs_save_open_mode;
	ccsecurity_ops.clear_open_mode = __ccs_clear_open_mode;
	ccsecurity_ops.open_permission = __ccs_open_permission;
#else
	ccsecurity_ops.open_permission = ccs_new_open_permission;
#endif
	ccsecurity_ops.fcntl_permission = __ccs_fcntl_permission;
	ccsecurity_ops.ioctl_permission = __ccs_ioctl_permission;
	ccsecurity_ops.chmod_permission = __ccs_chmod_permission;
	ccsecurity_ops.chown_permission = __ccs_chown_permission;
	ccsecurity_ops.getattr_permission = __ccs_getattr_permission;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 25)
	ccsecurity_ops.pivot_root_permission = __ccs_pivot_root_permission;
	ccsecurity_ops.chroot_permission = __ccs_chroot_permission;
#else
	ccsecurity_ops.pivot_root_permission = ccs_old_pivot_root_permission;
	ccsecurity_ops.chroot_permission = ccs_old_chroot_permission;
#endif
	ccsecurity_ops.umount_permission = __ccs_umount_permission;
	ccsecurity_ops.mknod_permission = __ccs_mknod_permission;
	ccsecurity_ops.mkdir_permission = __ccs_mkdir_permission;
	ccsecurity_ops.rmdir_permission = __ccs_rmdir_permission;
	ccsecurity_ops.unlink_permission = __ccs_unlink_permission;
	ccsecurity_ops.symlink_permission = __ccs_symlink_permission;
	ccsecurity_ops.truncate_permission = __ccs_truncate_permission;
	ccsecurity_ops.rename_permission = __ccs_rename_permission;
	ccsecurity_ops.link_permission = __ccs_link_permission;
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 30)
	ccsecurity_ops.open_exec_permission = __ccs_open_exec_permission;
	ccsecurity_ops.uselib_permission = __ccs_uselib_permission;
#endif
#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 18) || (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 33) && defined(CONFIG_SYSCTL_SYSCALL))
	ccsecurity_ops.parse_table = __ccs_parse_table;
#endif
};
