TOMOYO: Add pathname grouping support.

This patch adds pathname grouping support, which is useful for grouping
pathnames that cannot be represented using /\{dir\}/ pattern.

Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: James Morris <jmorris@namei.org>
diff --git a/security/tomoyo/Makefile b/security/tomoyo/Makefile
index 60a9e20..4fb3903 100644
--- a/security/tomoyo/Makefile
+++ b/security/tomoyo/Makefile
@@ -1 +1 @@
-obj-y = common.o realpath.o tomoyo.o domain.o file.o gc.o
+obj-y = common.o realpath.o tomoyo.o domain.o file.o gc.o path_group.o
diff --git a/security/tomoyo/common.c b/security/tomoyo/common.c
index 62e089c..b5dbdc9 100644
--- a/security/tomoyo/common.c
+++ b/security/tomoyo/common.c
@@ -76,6 +76,49 @@
 				const int buffer_len);
 
 /**
+ * tomoyo_parse_name_union - Parse a tomoyo_name_union.
+ *
+ * @filename: Name or name group.
+ * @ptr:      Pointer to "struct tomoyo_name_union".
+ *
+ * Returns true on success, false otherwise.
+ */
+bool tomoyo_parse_name_union(const char *filename,
+			     struct tomoyo_name_union *ptr)
+{
+	if (!tomoyo_is_correct_path(filename, 0, 0, 0))
+		return false;
+	if (filename[0] == '@') {
+		ptr->group = tomoyo_get_path_group(filename + 1);
+		ptr->is_group = true;
+		return ptr->group != NULL;
+	}
+	ptr->filename = tomoyo_get_name(filename);
+	ptr->is_group = false;
+	return ptr->filename != NULL;
+}
+
+/**
+ * tomoyo_print_name_union - Print a tomoyo_name_union.
+ *
+ * @head: Pointer to "struct tomoyo_io_buffer".
+ * @ptr:  Pointer to "struct tomoyo_name_union".
+ *
+ * Returns true on success, false otherwise.
+ */
+static bool tomoyo_print_name_union(struct tomoyo_io_buffer *head,
+				 const struct tomoyo_name_union *ptr)
+{
+	int pos = head->read_avail;
+	if (pos && head->read_buf[pos - 1] == ' ')
+		head->read_avail--;
+	if (ptr->is_group)
+		return tomoyo_io_printf(head, " @%s",
+					ptr->group->group_name->name);
+	return tomoyo_io_printf(head, " %s", ptr->filename->name);
+}
+
+/**
  * tomoyo_is_byte_range - Check whether the string isa \ooo style octal value.
  *
  * @str: Pointer to the string.
@@ -172,6 +215,33 @@
 }
 
 /**
+ * tomoyo_tokenize - Tokenize string.
+ *
+ * @buffer: The line to tokenize.
+ * @w:      Pointer to "char *".
+ * @size:   Sizeof @w .
+ *
+ * Returns true on success, false otherwise.
+ */
+bool tomoyo_tokenize(char *buffer, char *w[], size_t size)
+{
+	int count = size / sizeof(char *);
+	int i;
+	for (i = 0; i < count; i++)
+		w[i] = "";
+	for (i = 0; i < count; i++) {
+		char *cp = strchr(buffer, ' ');
+		if (cp)
+			*cp = '\0';
+		w[i] = buffer;
+		if (!cp)
+			break;
+		buffer = cp + 1;
+	}
+	return i < count || !*buffer;
+}
+
+/**
  * tomoyo_is_correct_path - Validate a pathname.
  * @filename:     The pathname to check.
  * @start_type:   Should the pathname start with '/'?
@@ -1368,21 +1438,20 @@
 {
 	int pos;
 	u8 bit;
-	const char *filename;
 	const u32 perm = ptr->perm | (((u32) ptr->perm_high) << 16);
 
-	filename = ptr->filename->name;
 	for (bit = head->read_bit; bit < TOMOYO_MAX_PATH_OPERATION; bit++) {
-		const char *msg;
 		if (!(perm & (1 << bit)))
 			continue;
 		/* Print "read/write" instead of "read" and "write". */
 		if ((bit == TOMOYO_TYPE_READ || bit == TOMOYO_TYPE_WRITE)
 		    && (perm & (1 << TOMOYO_TYPE_READ_WRITE)))
 			continue;
-		msg = tomoyo_path2keyword(bit);
 		pos = head->read_avail;
-		if (!tomoyo_io_printf(head, "allow_%s %s\n", msg, filename))
+		if (!tomoyo_io_printf(head, "allow_%s ",
+				      tomoyo_path2keyword(bit)) ||
+		    !tomoyo_print_name_union(head, &ptr->name) ||
+		    !tomoyo_io_printf(head, "\n"))
 			goto out;
 	}
 	head->read_bit = 0;
@@ -1405,21 +1474,18 @@
 				   struct tomoyo_path2_acl *ptr)
 {
 	int pos;
-	const char *filename1;
-	const char *filename2;
 	const u8 perm = ptr->perm;
 	u8 bit;
 
-	filename1 = ptr->filename1->name;
-	filename2 = ptr->filename2->name;
 	for (bit = head->read_bit; bit < TOMOYO_MAX_PATH2_OPERATION; bit++) {
-		const char *msg;
 		if (!(perm & (1 << bit)))
 			continue;
-		msg = tomoyo_path22keyword(bit);
 		pos = head->read_avail;
-		if (!tomoyo_io_printf(head, "allow_%s %s %s\n", msg,
-				      filename1, filename2))
+		if (!tomoyo_io_printf(head, "allow_%s ",
+				      tomoyo_path22keyword(bit)) ||
+		    !tomoyo_print_name_union(head, &ptr->name1) ||
+		    !tomoyo_print_name_union(head, &ptr->name2) ||
+		    !tomoyo_io_printf(head, "\n"))
 			goto out;
 	}
 	head->read_bit = 0;
@@ -1682,6 +1748,8 @@
 		return tomoyo_write_pattern_policy(data, is_delete);
 	if (tomoyo_str_starts(&data, TOMOYO_KEYWORD_DENY_REWRITE))
 		return tomoyo_write_no_rewrite_policy(data, is_delete);
+	if (tomoyo_str_starts(&data, TOMOYO_KEYWORD_PATH_GROUP))
+		return tomoyo_write_path_group_policy(data, is_delete);
 	return -EINVAL;
 }
 
@@ -1738,6 +1806,12 @@
 			head->read_var2 = NULL;
 			head->read_step = 9;
 		case 9:
+			if (!tomoyo_read_path_group_policy(head))
+				break;
+			head->read_var1 = NULL;
+			head->read_var2 = NULL;
+			head->read_step = 10;
+		case 10:
 			head->read_eof = true;
 			break;
 		default:
diff --git a/security/tomoyo/common.h b/security/tomoyo/common.h
index c95f486..9f1ae5e 100644
--- a/security/tomoyo/common.h
+++ b/security/tomoyo/common.h
@@ -54,6 +54,7 @@
 #define TOMOYO_KEYWORD_KEEP_DOMAIN               "keep_domain "
 #define TOMOYO_KEYWORD_NO_INITIALIZE_DOMAIN      "no_initialize_domain "
 #define TOMOYO_KEYWORD_NO_KEEP_DOMAIN            "no_keep_domain "
+#define TOMOYO_KEYWORD_PATH_GROUP                "path_group "
 #define TOMOYO_KEYWORD_SELECT                    "select "
 #define TOMOYO_KEYWORD_USE_PROFILE               "use_profile "
 #define TOMOYO_KEYWORD_IGNORE_GLOBAL_ALLOW_READ  "ignore_global_allow_read"
@@ -204,6 +205,27 @@
 	char barrier2[16]; /* Safeguard for overrun. */
 };
 
+struct tomoyo_name_union {
+	const struct tomoyo_path_info *filename;
+	struct tomoyo_path_group *group;
+	u8 is_group;
+};
+
+/* Structure for "path_group" directive. */
+struct tomoyo_path_group {
+	struct list_head list;
+	const struct tomoyo_path_info *group_name;
+	struct list_head member_list;
+	atomic_t users;
+};
+
+/* Structure for "path_group" directive. */
+struct tomoyo_path_group_member {
+	struct list_head list;
+	bool is_deleted;
+	const struct tomoyo_path_info *member_name;
+};
+
 /*
  * tomoyo_acl_info is a structure which is used for holding
  *
@@ -274,7 +296,7 @@
  *
  *  (1) "head" which is a "struct tomoyo_acl_info".
  *  (2) "perm" which is a bitmask of permitted operations.
- *  (3) "filename" is the pathname.
+ *  (3) "name" is the pathname.
  *
  * Directives held by this structure are "allow_read/write", "allow_execute",
  * "allow_read", "allow_write", "allow_create", "allow_unlink", "allow_mkdir",
@@ -287,8 +309,7 @@
 	struct tomoyo_acl_info head; /* type = TOMOYO_TYPE_PATH_ACL */
 	u8 perm_high;
 	u16 perm;
-	/* Pointer to single pathname. */
-	const struct tomoyo_path_info *filename;
+	struct tomoyo_name_union name;
 };
 
 /*
@@ -298,8 +319,8 @@
  *
  *  (1) "head" which is a "struct tomoyo_acl_info".
  *  (2) "perm" which is a bitmask of permitted operations.
- *  (3) "filename1" is the source/old pathname.
- *  (4) "filename2" is the destination/new pathname.
+ *  (3) "name1" is the source/old pathname.
+ *  (4) "name2" is the destination/new pathname.
  *
  * Directives held by this structure are "allow_rename", "allow_link" and
  * "allow_pivot_root".
@@ -307,10 +328,8 @@
 struct tomoyo_path2_acl {
 	struct tomoyo_acl_info head; /* type = TOMOYO_TYPE_PATH2_ACL */
 	u8 perm;
-	/* Pointer to single pathname. */
-	const struct tomoyo_path_info *filename1;
-	/* Pointer to single pathname. */
-	const struct tomoyo_path_info *filename2;
+	struct tomoyo_name_union name1;
+	struct tomoyo_name_union name2;
 };
 
 /*
@@ -514,6 +533,9 @@
 
 /********** Function prototypes. **********/
 
+/* Check whether the given name matches the given name_union. */
+bool tomoyo_compare_name_union(const struct tomoyo_path_info *name,
+			       const struct tomoyo_name_union *ptr);
 /* Check whether the domain has too many ACL entries to hold. */
 bool tomoyo_domain_quota_is_ok(struct tomoyo_domain_info * const domain);
 /* Transactional sprintf() for policy dump. */
@@ -526,6 +548,12 @@
 			    const s8 pattern_type, const s8 end_type);
 /* Check whether the token can be a domainname. */
 bool tomoyo_is_domain_def(const unsigned char *buffer);
+bool tomoyo_parse_name_union(const char *filename,
+			     struct tomoyo_name_union *ptr);
+/* Check whether the given filename matches the given path_group. */
+bool tomoyo_path_matches_group(const struct tomoyo_path_info *pathname,
+			       const struct tomoyo_path_group *group,
+			       const bool may_use_pattern);
 /* Check whether the given filename matches the given pattern. */
 bool tomoyo_path_matches_pattern(const struct tomoyo_path_info *filename,
 				 const struct tomoyo_path_info *pattern);
@@ -540,10 +568,14 @@
 bool tomoyo_read_domain_keeper_policy(struct tomoyo_io_buffer *head);
 /* Read "file_pattern" entry in exception policy. */
 bool tomoyo_read_file_pattern(struct tomoyo_io_buffer *head);
+/* Read "path_group" entry in exception policy. */
+bool tomoyo_read_path_group_policy(struct tomoyo_io_buffer *head);
 /* Read "allow_read" entry in exception policy. */
 bool tomoyo_read_globally_readable_policy(struct tomoyo_io_buffer *head);
 /* Read "deny_rewrite" entry in exception policy. */
 bool tomoyo_read_no_rewrite_policy(struct tomoyo_io_buffer *head);
+/* Tokenize a line. */
+bool tomoyo_tokenize(char *buffer, char *w[], size_t size);
 /* Write domain policy violation warning message to console? */
 bool tomoyo_verbose_mode(const struct tomoyo_domain_info *domain);
 /* Convert double path operation to operation name. */
@@ -580,12 +612,18 @@
 int tomoyo_write_no_rewrite_policy(char *data, const bool is_delete);
 /* Create "file_pattern" entry in exception policy. */
 int tomoyo_write_pattern_policy(char *data, const bool is_delete);
+/* Create "path_group" entry in exception policy. */
+int tomoyo_write_path_group_policy(char *data, const bool is_delete);
 /* Find a domain by the given name. */
 struct tomoyo_domain_info *tomoyo_find_domain(const char *domainname);
 /* Find or create a domain by the given name. */
 struct tomoyo_domain_info *tomoyo_find_or_assign_new_domain(const char *
 							    domainname,
 							    const u8 profile);
+
+/* Allocate memory for "struct tomoyo_path_group". */
+struct tomoyo_path_group *tomoyo_get_path_group(const char *group_name);
+
 /* Check mode for specified functionality. */
 unsigned int tomoyo_check_flags(const struct tomoyo_domain_info *domain,
 				const u8 index);
@@ -642,6 +680,9 @@
 int tomoyo_check_rewrite_permission(struct file *filp);
 int tomoyo_find_next_domain(struct linux_binprm *bprm);
 
+/* Drop refcount on tomoyo_name_union. */
+void tomoyo_put_name_union(struct tomoyo_name_union *ptr);
+
 /* Run garbage collector. */
 void tomoyo_run_gc(void);
 
@@ -655,6 +696,7 @@
 /* The list for "struct tomoyo_domain_info". */
 extern struct list_head tomoyo_domain_list;
 
+extern struct list_head tomoyo_path_group_list;
 extern struct list_head tomoyo_domain_initializer_list;
 extern struct list_head tomoyo_domain_keeper_list;
 extern struct list_head tomoyo_alias_list;
@@ -725,6 +767,12 @@
 	}
 }
 
+static inline void tomoyo_put_path_group(struct tomoyo_path_group *group)
+{
+	if (group)
+		atomic_dec(&group->users);
+}
+
 static inline struct tomoyo_domain_info *tomoyo_domain(void)
 {
 	return current_cred()->security;
@@ -736,6 +784,34 @@
 	return task_cred_xxx(task, security);
 }
 
+static inline bool tomoyo_is_same_acl_head(const struct tomoyo_acl_info *p1,
+					   const struct tomoyo_acl_info *p2)
+{
+	return p1->type == p2->type;
+}
+
+static inline bool tomoyo_is_same_name_union
+(const struct tomoyo_name_union *p1, const struct tomoyo_name_union *p2)
+{
+	return p1->filename == p2->filename && p1->group == p2->group &&
+		p1->is_group == p2->is_group;
+}
+
+static inline bool tomoyo_is_same_path_acl(const struct tomoyo_path_acl *p1,
+					   const struct tomoyo_path_acl *p2)
+{
+	return tomoyo_is_same_acl_head(&p1->head, &p2->head) &&
+		tomoyo_is_same_name_union(&p1->name, &p2->name);
+}
+
+static inline bool tomoyo_is_same_path2_acl(const struct tomoyo_path2_acl *p1,
+					    const struct tomoyo_path2_acl *p2)
+{
+	return tomoyo_is_same_acl_head(&p1->head, &p2->head) &&
+		tomoyo_is_same_name_union(&p1->name1, &p2->name1) &&
+		tomoyo_is_same_name_union(&p1->name2, &p2->name2);
+}
+
 static inline bool tomoyo_is_same_domain_initializer_entry
 (const struct tomoyo_domain_initializer_entry *p1,
  const struct tomoyo_domain_initializer_entry *p2)
diff --git a/security/tomoyo/file.c b/security/tomoyo/file.c
index 6651cac..1c6f823 100644
--- a/security/tomoyo/file.c
+++ b/security/tomoyo/file.c
@@ -45,6 +45,37 @@
 	[TOMOYO_TYPE_PIVOT_ROOT] = "pivot_root",
 };
 
+void tomoyo_put_name_union(struct tomoyo_name_union *ptr)
+{
+	if (!ptr)
+		return;
+	if (ptr->is_group)
+		tomoyo_put_path_group(ptr->group);
+	else
+		tomoyo_put_name(ptr->filename);
+}
+
+bool tomoyo_compare_name_union(const struct tomoyo_path_info *name,
+			       const struct tomoyo_name_union *ptr)
+{
+	if (ptr->is_group)
+		return tomoyo_path_matches_group(name, ptr->group, 1);
+	return tomoyo_path_matches_pattern(name, ptr->filename);
+}
+
+static bool tomoyo_compare_name_union_pattern(const struct tomoyo_path_info
+					      *name,
+					      const struct tomoyo_name_union
+					      *ptr, const bool may_use_pattern)
+{
+	if (ptr->is_group)
+		return tomoyo_path_matches_group(name, ptr->group,
+						 may_use_pattern);
+	if (may_use_pattern || !ptr->filename->is_patterned)
+		return tomoyo_path_matches_pattern(name, ptr->filename);
+	return false;
+}
+
 /**
  * tomoyo_path2keyword - Get the name of single path operation.
  *
@@ -637,13 +668,9 @@
 			if (!(acl->perm_high & (perm >> 16)))
 				continue;
 		}
-		if (may_use_pattern || !acl->filename->is_patterned) {
-			if (!tomoyo_path_matches_pattern(filename,
-							 acl->filename))
-				continue;
-		} else {
+		if (!tomoyo_compare_name_union_pattern(filename, &acl->name,
+                                                       may_use_pattern))
 			continue;
-		}
 		error = 0;
 		break;
 	}
@@ -817,19 +844,14 @@
 		e.perm |= tomoyo_rw_mask;
 	if (!domain)
 		return -EINVAL;
-	if (!tomoyo_is_correct_path(filename, 0, 0, 0))
+	if (!tomoyo_parse_name_union(filename, &e.name))
 		return -EINVAL;
-	e.filename = tomoyo_get_name(filename);
-	if (!e.filename)
-		return -ENOMEM;
 	if (mutex_lock_interruptible(&tomoyo_policy_lock))
 		goto out;
 	list_for_each_entry_rcu(ptr, &domain->acl_info_list, list) {
 		struct tomoyo_path_acl *acl =
 			container_of(ptr, struct tomoyo_path_acl, head);
-		if (ptr->type != TOMOYO_TYPE_PATH_ACL)
-			continue;
-		if (acl->filename != e.filename)
+		if (!tomoyo_is_same_path_acl(acl, &e))
 			continue;
 		if (is_delete) {
 			if (perm <= 0xFFFF)
@@ -864,7 +886,7 @@
 	}
 	mutex_unlock(&tomoyo_policy_lock);
  out:
-	tomoyo_put_name(e.filename);
+	tomoyo_put_name_union(&e.name);
 	return error;
 }
 
@@ -896,22 +918,15 @@
 
 	if (!domain)
 		return -EINVAL;
-	if (!tomoyo_is_correct_path(filename1, 0, 0, 0) ||
-	    !tomoyo_is_correct_path(filename2, 0, 0, 0))
-		return -EINVAL;
-	e.filename1 = tomoyo_get_name(filename1);
-	e.filename2 = tomoyo_get_name(filename2);
-	if (!e.filename1 || !e.filename2)
+	if (!tomoyo_parse_name_union(filename1, &e.name1) ||
+	    !tomoyo_parse_name_union(filename2, &e.name2))
 		goto out;
 	if (mutex_lock_interruptible(&tomoyo_policy_lock))
 		goto out;
 	list_for_each_entry_rcu(ptr, &domain->acl_info_list, list) {
 		struct tomoyo_path2_acl *acl =
 			container_of(ptr, struct tomoyo_path2_acl, head);
-		if (ptr->type != TOMOYO_TYPE_PATH2_ACL)
-			continue;
-		if (acl->filename1 != e.filename1 ||
-		    acl->filename2 != e.filename2)
+		if (!tomoyo_is_same_path2_acl(acl, &e))
 			continue;
 		if (is_delete)
 			acl->perm &= ~perm;
@@ -931,8 +946,8 @@
 	}
 	mutex_unlock(&tomoyo_policy_lock);
  out:
-	tomoyo_put_name(e.filename1);
-	tomoyo_put_name(e.filename2);
+	tomoyo_put_name_union(&e.name1);
+	tomoyo_put_name_union(&e.name2);
 	return error;
 }
 
@@ -985,9 +1000,9 @@
 		acl = container_of(ptr, struct tomoyo_path2_acl, head);
 		if (!(acl->perm & perm))
 			continue;
-		if (!tomoyo_path_matches_pattern(filename1, acl->filename1))
+		if (!tomoyo_compare_name_union(filename1, &acl->name1))
 			continue;
-		if (!tomoyo_path_matches_pattern(filename2, acl->filename2))
+		if (!tomoyo_compare_name_union(filename2, &acl->name2))
 			continue;
 		error = 0;
 		break;
diff --git a/security/tomoyo/gc.c b/security/tomoyo/gc.c
index 245bf42..b9cc71b 100644
--- a/security/tomoyo/gc.c
+++ b/security/tomoyo/gc.c
@@ -12,6 +12,8 @@
 #include <linux/slab.h>
 
 enum tomoyo_gc_id {
+	TOMOYO_ID_PATH_GROUP,
+	TOMOYO_ID_PATH_GROUP_MEMBER,
 	TOMOYO_ID_DOMAIN_INITIALIZER,
 	TOMOYO_ID_DOMAIN_KEEPER,
 	TOMOYO_ID_ALIAS,
@@ -91,15 +93,15 @@
 		{
 			struct tomoyo_path_acl *entry
 				= container_of(acl, typeof(*entry), head);
-			tomoyo_put_name(entry->filename);
+			tomoyo_put_name_union(&entry->name);
 		}
 		break;
 	case TOMOYO_TYPE_PATH2_ACL:
 		{
 			struct tomoyo_path2_acl *entry
 				= container_of(acl, typeof(*entry), head);
-			tomoyo_put_name(entry->filename1);
-			tomoyo_put_name(entry->filename2);
+			tomoyo_put_name_union(&entry->name1);
+			tomoyo_put_name_union(&entry->name2);
 		}
 		break;
 	default:
@@ -149,6 +151,17 @@
 {
 }
 
+static void tomoyo_del_path_group_member(struct tomoyo_path_group_member
+					 *member)
+{
+	tomoyo_put_name(member->member_name);
+}
+
+static void tomoyo_del_path_group(struct tomoyo_path_group *group)
+{
+	tomoyo_put_name(group->group_name);
+}
+
 static void tomoyo_collect_entry(void)
 {
 	if (mutex_lock_interruptible(&tomoyo_policy_lock))
@@ -293,6 +306,29 @@
 			}
 		}
 	}
+	{
+		struct tomoyo_path_group *group;
+		list_for_each_entry_rcu(group, &tomoyo_path_group_list, list) {
+			struct tomoyo_path_group_member *member;
+			list_for_each_entry_rcu(member, &group->member_list,
+						list) {
+				if (!member->is_deleted)
+					continue;
+				if (tomoyo_add_to_gc(TOMOYO_ID_PATH_GROUP_MEMBER,
+						     member))
+					list_del_rcu(&member->list);
+				else
+					break;
+			}
+			if (!list_empty(&group->member_list) ||
+			    atomic_read(&group->users))
+				continue;
+			if (tomoyo_add_to_gc(TOMOYO_ID_PATH_GROUP, group))
+				list_del_rcu(&group->list);
+			else
+				break;
+		}
+	}
 	mutex_unlock(&tomoyo_policy_lock);
 }
 
@@ -334,6 +370,12 @@
 			if (!tomoyo_del_domain(p->element))
 				continue;
 			break;
+		case TOMOYO_ID_PATH_GROUP_MEMBER:
+			tomoyo_del_path_group_member(p->element);
+			break;
+		case TOMOYO_ID_PATH_GROUP:
+			tomoyo_del_path_group(p->element);
+			break;
 		default:
 			printk(KERN_WARNING "Unknown type\n");
 			break;
diff --git a/security/tomoyo/path_group.c b/security/tomoyo/path_group.c
new file mode 100644
index 0000000..c988041
--- /dev/null
+++ b/security/tomoyo/path_group.c
@@ -0,0 +1,172 @@
+/*
+ * security/tomoyo/path_group.c
+ *
+ * Copyright (C) 2005-2009  NTT DATA CORPORATION
+ */
+
+#include <linux/slab.h>
+#include "common.h"
+/* The list for "struct ccs_path_group". */
+LIST_HEAD(tomoyo_path_group_list);
+
+/**
+ * tomoyo_get_path_group - Allocate memory for "struct tomoyo_path_group".
+ *
+ * @group_name: The name of pathname group.
+ *
+ * Returns pointer to "struct tomoyo_path_group" on success, NULL otherwise.
+ */
+struct tomoyo_path_group *tomoyo_get_path_group(const char *group_name)
+{
+	struct tomoyo_path_group *entry = NULL;
+	struct tomoyo_path_group *group = NULL;
+	const struct tomoyo_path_info *saved_group_name;
+	int error = -ENOMEM;
+	if (!tomoyo_is_correct_path(group_name, 0, 0, 0) ||
+	    !group_name[0])
+		return NULL;
+	saved_group_name = tomoyo_get_name(group_name);
+	if (!saved_group_name)
+		return NULL;
+	entry = kzalloc(sizeof(*entry), GFP_NOFS);
+	if (mutex_lock_interruptible(&tomoyo_policy_lock))
+		goto out;
+	list_for_each_entry_rcu(group, &tomoyo_path_group_list, list) {
+		if (saved_group_name != group->group_name)
+			continue;
+		atomic_inc(&group->users);
+		error = 0;
+		break;
+	}
+	if (error && tomoyo_memory_ok(entry)) {
+		INIT_LIST_HEAD(&entry->member_list);
+		entry->group_name = saved_group_name;
+		saved_group_name = NULL;
+		atomic_set(&entry->users, 1);
+		list_add_tail_rcu(&entry->list, &tomoyo_path_group_list);
+		group = entry;
+		entry = NULL;
+		error = 0;
+	}
+	mutex_unlock(&tomoyo_policy_lock);
+ out:
+	tomoyo_put_name(saved_group_name);
+	kfree(entry);
+	return !error ? group : NULL;
+}
+
+/**
+ * tomoyo_write_path_group_policy - Write "struct tomoyo_path_group" list.
+ *
+ * @data:      String to parse.
+ * @is_delete: True if it is a delete request.
+ *
+ * Returns 0 on success, nagative value otherwise.
+ */
+int tomoyo_write_path_group_policy(char *data, const bool is_delete)
+{
+	struct tomoyo_path_group *group;
+	struct tomoyo_path_group_member *member;
+	struct tomoyo_path_group_member e = { };
+	int error = is_delete ? -ENOENT : -ENOMEM;
+	char *w[2];
+	if (!tomoyo_tokenize(data, w, sizeof(w)) || !w[1][0])
+		return -EINVAL;
+	group = tomoyo_get_path_group(w[0]);
+	if (!group)
+		return -ENOMEM;
+	e.member_name = tomoyo_get_name(w[1]);
+	if (!e.member_name)
+		goto out;
+	if (mutex_lock_interruptible(&tomoyo_policy_lock))
+		goto out;
+	list_for_each_entry_rcu(member, &group->member_list, list) {
+		if (member->member_name != e.member_name)
+			continue;
+		member->is_deleted = is_delete;
+		error = 0;
+		break;
+	}
+	if (!is_delete && error) {
+		struct tomoyo_path_group_member *entry =
+			tomoyo_commit_ok(&e, sizeof(e));
+		if (entry) {
+			list_add_tail_rcu(&entry->list, &group->member_list);
+			error = 0;
+		}
+	}
+	mutex_unlock(&tomoyo_policy_lock);
+ out:
+	tomoyo_put_name(e.member_name);
+	tomoyo_put_path_group(group);
+	return error;
+}
+
+/**
+ * tomoyo_read_path_group_policy - Read "struct tomoyo_path_group" list.
+ *
+ * @head: Pointer to "struct tomoyo_io_buffer".
+ *
+ * Returns true on success, false otherwise.
+ *
+ * Caller holds tomoyo_read_lock().
+ */
+bool tomoyo_read_path_group_policy(struct tomoyo_io_buffer *head)
+{
+	struct list_head *gpos;
+	struct list_head *mpos;
+	list_for_each_cookie(gpos, head->read_var1, &tomoyo_path_group_list) {
+		struct tomoyo_path_group *group;
+		group = list_entry(gpos, struct tomoyo_path_group, list);
+		list_for_each_cookie(mpos, head->read_var2,
+				     &group->member_list) {
+			struct tomoyo_path_group_member *member;
+			member = list_entry(mpos,
+					    struct tomoyo_path_group_member,
+					    list);
+			if (member->is_deleted)
+				continue;
+			if (!tomoyo_io_printf(head, TOMOYO_KEYWORD_PATH_GROUP
+					      "%s %s\n",
+					      group->group_name->name,
+					      member->member_name->name))
+				return false;
+		}
+	}
+	return true;
+}
+
+/**
+ * tomoyo_path_matches_group - Check whether the given pathname matches members of the given pathname group.
+ *
+ * @pathname:        The name of pathname.
+ * @group:           Pointer to "struct tomoyo_path_group".
+ * @may_use_pattern: True if wild card is permitted.
+ *
+ * Returns true if @pathname matches pathnames in @group, false otherwise.
+ *
+ * Caller holds tomoyo_read_lock().
+ */
+bool tomoyo_path_matches_group(const struct tomoyo_path_info *pathname,
+			       const struct tomoyo_path_group *group,
+			       const bool may_use_pattern)
+{
+	struct tomoyo_path_group_member *member;
+	bool matched = false;
+	list_for_each_entry_rcu(member, &group->member_list, list) {
+		if (member->is_deleted)
+			continue;
+		if (!member->member_name->is_patterned) {
+			if (tomoyo_pathcmp(pathname, member->member_name))
+				continue;
+		} else if (may_use_pattern) {
+			if (!tomoyo_path_matches_pattern(pathname,
+							 member->member_name))
+				continue;
+		} else
+			continue;
+		matched = true;
+		break;
+	}
+	return matched;
+}