| From dd5514677ac66b350ddc1ecf7c3fab06e97a13c0 Mon Sep 17 00:00:00 2001 |
| From: Daniel Rosenberg <drosen@google.com> |
| Date: Tue, 10 Oct 2017 14:57:01 -0700 |
| Subject: [PATCH] CHROMIUM: drivers: Create pkglist for configuration |
| |
| This commit imports pkglist and fixes compilation errors |
| on 4.14: |
| |
| * Import pkglist: |
| This moves the configuration for sdcardfs into an |
| independent driver, allowing the code to be reused |
| for alternate approaches or implementations. It |
| also provides a way to swap out the implementation |
| if others are desired. |
| |
| (cherry picked from commit 12538f8144fbf39e7196f1838a998a937461dcc8) |
| Reviewed-on: https://chromium-review.googlesource.com/791973 |
| |
| * Fix-up: |
| drivers/pkglist/pkglist.c:169:10-11: WARNING: |
| return of 0/1 in function '__is_excluded' with return type bool |
| |
| (cherry picked from commit 70545796f3e48abd23e32cb376e932900c08c182) |
| Reviewed-on: https://chromium-review.googlesource.com/929010 |
| |
| * Port to 4.4: |
| - show() and store() are now part of the configfs_attribute. |
| - Generalize function definitions to use config_item. |
| - Combine the generation of packages_attr and extension_values_attr |
| |
| (cherry picked from commit 3fe75b8caaadd5648e883e13f4dae1b7983c6bb9) |
| Reviewed-on: https://chromium-review.googlesource.com/792494 |
| |
| * Port to 4.14: |
| - Add zero salt to init_name_hash. |
| - config_group->default_groups is now a list, use configfs_add_default_group. |
| - Add linux/cred.h for namepsace related functions. |
| |
| BUG=b:63876697 |
| TEST=compilation, mount esdfs |
| |
| Change-Id: I466bdb280d5a215a06f699b0c8c1dba0efac94c8 |
| Reviewed-on: https://chromium-review.googlesource.com/963406 |
| Commit-Ready: Sarthak Kukreti <sarthakkukreti@chromium.org> |
| Tested-by: Sarthak Kukreti <sarthakkukreti@chromium.org> |
| Reviewed-by: Gwendal Grignou <gwendal@chromium.org> |
| |
| Conflicts: |
| drivers/Kconfig |
| drivers/Makefile |
| |
| [rebase419(groeck): Context conflicts] |
| Signed-off-by: Guenter Roeck <groeck@chromium.org> |
| Conflicts: |
| drivers/Makefile |
| [rebase54(groeck): Resolved conflicts, |
| Added missing "#include <linux/uidgid.h>" to pkglist.h. |
| ] |
| Signed-off-by: Guenter Roeck <groeck@chromium.org> |
| [rebase510(groeck): Context conflicts] |
| Signed-off-by: Guenter Roeck <groeck@chromium.org> |
| |
| [rebase515(groeck): |
| Squashed: |
| FIXUP: CHROMIUM: drivers: Create pkglist for configuration |
| CHROMIUM: pkglist: Add dependency on CONFIG_FS |
| CHROMIUM: pkglist: Use pr_debug instead of pr_info |
| ] |
| |
| Signed-off-by: Guenter Roeck <groeck@chromium.org> |
| --- |
| Documentation/ABI/testing/configfs-sdcardfs | 53 ++ |
| drivers/Kconfig | 2 + |
| drivers/Makefile | 2 + |
| drivers/pkglist/Kconfig | 30 + |
| drivers/pkglist/Makefile | 3 + |
| drivers/pkglist/pkglist.c | 966 ++++++++++++++++++++ |
| drivers/pkglist/pkglist_none.c | 57 ++ |
| include/linux/pkglist.h | 38 + |
| 8 files changed, 1151 insertions(+) |
| create mode 100644 Documentation/ABI/testing/configfs-sdcardfs |
| create mode 100644 drivers/pkglist/Kconfig |
| create mode 100644 drivers/pkglist/Makefile |
| create mode 100644 drivers/pkglist/pkglist.c |
| create mode 100644 drivers/pkglist/pkglist_none.c |
| create mode 100644 include/linux/pkglist.h |
| |
| diff --git a/Documentation/ABI/testing/configfs-sdcardfs b/Documentation/ABI/testing/configfs-sdcardfs |
| new file mode 100644 |
| index 000000000000..40354c597346 |
| --- /dev/null |
| +++ b/Documentation/ABI/testing/configfs-sdcardfs |
| @@ -0,0 +1,53 @@ |
| +What: /config/sdcardfs/ |
| +Date: Nov 2017 |
| +KernelVersion: -- |
| +Description: This presents a configfs interface for Android's emulated |
| + sdcard layer. It relates the names of packages to their |
| + package ids, so that they can be given access to their app |
| + specific folders. It also allows you to set information used |
| + to track data usage for specific sorts of files by their |
| + extension. |
| + |
| + The attributes: |
| + |
| + remove_userid - Removes an Android user from all |
| + package exclude lists |
| + packages_gid.list - Displays the first page worth of |
| + packages, for debugging purposes |
| + |
| +What: /config/sdcardfs/<package>/ |
| +Date: Nov 2017 |
| +KernelVersion: -- |
| +Description: This is where package specific information is set. A package |
| + is associated with an id, and is accessible only to Android |
| + users that are not on the excluded_userids list. |
| + |
| + The attributes: |
| + |
| + appid - The package's id. |
| + excluded_userids - Write an Android user id here to |
| + not have that user see permissions |
| + that would indicate that app is |
| + installed. |
| + clear_userid - Remove a user from the excluded |
| + list |
| + |
| +What: /config/sdcardfs/extensions/ |
| +Date: Nov 2017 |
| +KernelVersion: -- |
| +Description: Configure what extensions have tracked storage. This is done |
| + using the quota feature of the underlying filesystem if |
| + supported. Create a directory for each group of extensions |
| + you wish to track together |
| + |
| +What: /config/sdcardfs/extensions/<group>/ |
| +Date: Nov 2017 |
| +KernelVersion: -- |
| +Description: This group represents a set of extensions whose storage usage |
| + are tracked as a unit. Create a directory for every extension |
| + you wish to include in the group. |
| + |
| + The attributes: |
| + |
| + ext_gid - Value to set the gid of files in the |
| + lower filesystem with the given gid to |
| diff --git a/drivers/Kconfig b/drivers/Kconfig |
| index 8d6cd5d08722..e3fde0f4bc79 100644 |
| --- a/drivers/Kconfig |
| +++ b/drivers/Kconfig |
| @@ -239,4 +239,6 @@ source "drivers/most/Kconfig" |
| |
| source "drivers/peci/Kconfig" |
| |
| +source "drivers/pkglist/Kconfig" |
| + |
| endmenu |
| diff --git a/drivers/Makefile b/drivers/Makefile |
| index 020780b6b4d2..6a4c6412b5b5 100644 |
| --- a/drivers/Makefile |
| +++ b/drivers/Makefile |
| @@ -188,3 +188,5 @@ obj-$(CONFIG_INTERCONNECT) += interconnect/ |
| obj-$(CONFIG_COUNTER) += counter/ |
| obj-$(CONFIG_MOST) += most/ |
| obj-$(CONFIG_PECI) += peci/ |
| + |
| +obj-$(CONFIG_PKGLIST) += pkglist/ |
| diff --git a/drivers/pkglist/Kconfig b/drivers/pkglist/Kconfig |
| new file mode 100644 |
| index 000000000000..5b6b8a37989a |
| --- /dev/null |
| +++ b/drivers/pkglist/Kconfig |
| @@ -0,0 +1,30 @@ |
| +config PKGLIST |
| + tristate "Package list for emulated 'SD card' file system for Android" |
| + depends on CONFIGFS_FS || !CONFIGFS_FS |
| + help |
| + Pkglist presents an interface for Android's emulated sdcard layer. |
| + It relates the names of packages to their package ids, so that they can be |
| + given access to their app specific folders. |
| + |
| + Additionally, pkglist allows configuring the gid assigned to the lower file |
| + outside of package specific directories for the purpose of tracking storage |
| + with quotas. |
| + |
| +choice |
| + prompt "Configuration options" |
| + depends on PKGLIST |
| + help |
| + Configuration options. This controls how you provide the emulated |
| + SD card layer with configuration information from userspace. |
| + |
| +config PKGLIST_USE_CONFIGFS |
| + bool "Use Configfs based pkglist" |
| + depends on CONFIGFS_FS |
| + help |
| + Use configfs based pkglist driver for configuration information. |
| + |
| +config PKGLIST_NO_CONFIG |
| + bool "None" |
| + help |
| + This does not allow configuration of sdcardfs. |
| +endchoice |
| diff --git a/drivers/pkglist/Makefile b/drivers/pkglist/Makefile |
| new file mode 100644 |
| index 000000000000..abea35f24aa6 |
| --- /dev/null |
| +++ b/drivers/pkglist/Makefile |
| @@ -0,0 +1,3 @@ |
| +obj-$(CONFIG_PKGLIST) += pkg.o |
| +pkg-$(CONFIG_PKGLIST_USE_CONFIGFS) += pkglist.o |
| +pkg-$(CONFIG_PKGLIST_NO_CONFIG) += pkglist_none.o |
| diff --git a/drivers/pkglist/pkglist.c b/drivers/pkglist/pkglist.c |
| new file mode 100644 |
| index 000000000000..0ddca00c5730 |
| --- /dev/null |
| +++ b/drivers/pkglist/pkglist.c |
| @@ -0,0 +1,966 @@ |
| +/* |
| + * Copyright (C) 2017 Google Inc., Author: Daniel Rosenberg <drosen@google.com> |
| + * |
| + * This program is free software; you can redistribute it and/or modify |
| + * it under the terms of the GNU General Public License version 2 as |
| + * published by the Free Software Foundation. |
| + */ |
| + |
| +#include <linux/module.h> |
| +#include <linux/hashtable.h> |
| +#include <linux/atomic.h> |
| +#include <linux/delay.h> |
| +#include <linux/slab.h> |
| +#include <linux/init.h> |
| +#include <linux/configfs.h> |
| +#include <linux/dcache.h> |
| +#include <linux/ctype.h> |
| +#include <linux/cred.h> |
| + |
| +#include <linux/pkglist.h> |
| + |
| +/* |
| + * This presents a configfs interface for Android's emulated sdcard layer. |
| + * It relates the names of packages to their package ids, so that they can be |
| + * given access to their app specific folders. |
| + * |
| + * To add a package, create a directory at the base level with the name of that |
| + * package. Within these folders, write to appid to set its id. |
| + * If an Android user should not know of an app's installation, write their |
| + * Android user id to excluded_userids. Write to clear_userid to remove users |
| + * from that list. |
| + * |
| + * remove_userid offers a way to remove all instances of a user from all exclude |
| + * lists. |
| + * |
| + * Additionally, pkglist allows configuring the gid assigned to the lower file |
| + * outside of package specific directories for the purpose of tracking storage |
| + * with quotas. |
| + * |
| + * To track files with a particular extension, create a folder inside extensions |
| + * for each class of thing you wish to track. Inside that directory, write the |
| + * gid you want to associate to the group to ext_gid, and make a directory for |
| + * extension you want to include. All are assumed to be case insensitive. |
| + * |
| + * ex: mkdir /config/[config_location]/extension/audio/ |
| + * echo 1055 > /config/[config_location]/extension/audio/ext_gid |
| + * mkdir /config/[config_location]/extension/audio/ |
| + * |
| + */ |
| + |
| +static char *pkglist_config_location = "sdcardfs"; |
| +module_param(pkglist_config_location, charp, 0); |
| +MODULE_PARM_DESC(pkglist_config_location, "Location of pkglist in configfs"); |
| + |
| +static struct kmem_cache *hashtable_entry_cachep; |
| + |
| +static DEFINE_HASHTABLE(package_to_appid, 8); |
| +static DEFINE_HASHTABLE(package_to_userid, 8); |
| +static DEFINE_HASHTABLE(ext_to_groupid, 8); |
| +static DEFINE_MUTEX(pkg_list_lock); |
| +static LIST_HEAD(pkglist_listeners); |
| + |
| +struct extensions_value { |
| + struct config_group group; |
| + kgid_t gid; |
| +}; |
| + |
| +struct extension_details { |
| + struct config_item item; |
| + struct hlist_node hlist; |
| + struct qstr name; |
| + struct extensions_value *value; |
| +}; |
| + |
| +struct hashtable_entry { |
| + struct hlist_node hlist; |
| + struct hlist_node dlist; /* for deletion cleanup */ |
| + struct qstr key; |
| + atomic_t value; |
| +}; |
| + |
| +static unsigned int full_name_case_hash(const unsigned char *name, |
| + unsigned int len) |
| +{ |
| + unsigned long hash = init_name_hash(0); |
| + |
| + while (len--) |
| + hash = partial_name_hash(tolower(*name++), hash); |
| + return end_name_hash(hash); |
| +} |
| + |
| +static inline void qstr_init(struct qstr *q, const char *name) |
| +{ |
| + q->name = name; |
| + q->len = strlen(q->name); |
| + q->hash = full_name_case_hash(q->name, q->len); |
| +} |
| + |
| +static inline int qstr_copy(const struct qstr *src, struct qstr *dest) |
| +{ |
| + dest->name = kstrdup(src->name, GFP_KERNEL); |
| + dest->hash_len = src->hash_len; |
| + return !!dest->name; |
| +} |
| + |
| +static kuid_t __get_appid(const struct qstr *key) |
| +{ |
| + struct hashtable_entry *hash_cur; |
| + unsigned int hash = key->hash; |
| + uid_t ret_id; |
| + |
| + rcu_read_lock(); |
| + hash_for_each_possible_rcu(package_to_appid, hash_cur, hlist, hash) { |
| + if (qstr_case_eq(key, &hash_cur->key)) { |
| + ret_id = atomic_read(&hash_cur->value); |
| + rcu_read_unlock(); |
| + return make_kuid(&init_user_ns, ret_id); |
| + } |
| + } |
| + rcu_read_unlock(); |
| + return INVALID_UID; |
| +} |
| + |
| +kuid_t pkglist_get_appid(const char *key) |
| +{ |
| + struct qstr q; |
| + |
| + qstr_init(&q, key); |
| + return __get_appid(&q); |
| +} |
| +EXPORT_SYMBOL_GPL(pkglist_get_appid); |
| + |
| +static kgid_t __get_ext_gid(const struct qstr *key) |
| +{ |
| + struct extension_details *hash_cur; |
| + unsigned int hash = key->hash; |
| + kgid_t ret_id; |
| + |
| + rcu_read_lock(); |
| + hash_for_each_possible_rcu(ext_to_groupid, hash_cur, hlist, hash) { |
| + if (qstr_case_eq(key, &hash_cur->name)) { |
| + ret_id = hash_cur->value->gid; |
| + rcu_read_unlock(); |
| + return ret_id; |
| + } |
| + } |
| + rcu_read_unlock(); |
| + return INVALID_GID; |
| +} |
| + |
| +kgid_t pkglist_get_ext_gid(const char *key) |
| +{ |
| + struct qstr q; |
| + |
| + qstr_init(&q, key); |
| + return __get_ext_gid(&q); |
| +} |
| +EXPORT_SYMBOL_GPL(pkglist_get_ext_gid); |
| + |
| +static bool __is_excluded(const struct qstr *app_name, uint32_t user) |
| +{ |
| + struct hashtable_entry *hash_cur; |
| + unsigned int hash = app_name->hash; |
| + |
| + rcu_read_lock(); |
| + hash_for_each_possible_rcu(package_to_userid, hash_cur, hlist, hash) { |
| + if (atomic_read(&hash_cur->value) == user && |
| + qstr_case_eq(app_name, &hash_cur->key)) { |
| + rcu_read_unlock(); |
| + return true; |
| + } |
| + } |
| + rcu_read_unlock(); |
| + return false; |
| +} |
| + |
| +bool pkglist_user_is_excluded(const char *key, uint32_t user) |
| +{ |
| + struct qstr q; |
| + |
| + qstr_init(&q, key); |
| + return __is_excluded(&q, user); |
| +} |
| +EXPORT_SYMBOL_GPL(pkglist_user_is_excluded); |
| + |
| +kuid_t pkglist_get_allowed_appid(const char *key, uint32_t user) |
| +{ |
| + struct qstr q; |
| + |
| + qstr_init(&q, key); |
| + if (!__is_excluded(&q, user)) |
| + return __get_appid(&q); |
| + else |
| + return INVALID_UID; |
| +} |
| +EXPORT_SYMBOL_GPL(pkglist_get_allowed_appid); |
| + |
| +static struct hashtable_entry *alloc_hashtable_entry(const struct qstr *key, |
| + uid_t value) |
| +{ |
| + struct hashtable_entry *ret = kmem_cache_alloc(hashtable_entry_cachep, |
| + GFP_KERNEL); |
| + if (!ret) |
| + return NULL; |
| + INIT_HLIST_NODE(&ret->dlist); |
| + INIT_HLIST_NODE(&ret->hlist); |
| + |
| + if (!qstr_copy(key, &ret->key)) { |
| + kmem_cache_free(hashtable_entry_cachep, ret); |
| + return NULL; |
| + } |
| + |
| + atomic_set(&ret->value, value); |
| + return ret; |
| +} |
| + |
| +static int insert_packagelist_appid_entry_locked(const struct qstr *key, |
| + kuid_t value) |
| +{ |
| + struct hashtable_entry *hash_cur; |
| + struct hashtable_entry *new_entry; |
| + unsigned int hash = key->hash; |
| + |
| + hash_for_each_possible_rcu(package_to_appid, hash_cur, hlist, hash) { |
| + if (qstr_case_eq(key, &hash_cur->key)) { |
| + atomic_set(&hash_cur->value, value.val); |
| + return 0; |
| + } |
| + } |
| + new_entry = alloc_hashtable_entry(key, value.val); |
| + if (!new_entry) |
| + return -ENOMEM; |
| + hash_add_rcu(package_to_appid, &new_entry->hlist, hash); |
| + return 0; |
| +} |
| + |
| +static int insert_ext_gid_entry_locked(struct extension_details *ed) |
| +{ |
| + struct extension_details *hash_cur; |
| + unsigned int hash = ed->name.hash; |
| + |
| + /* An extension can only belong to one gid */ |
| + hash_for_each_possible_rcu(ext_to_groupid, hash_cur, hlist, hash) { |
| + if (qstr_case_eq(&ed->name, &hash_cur->name)) |
| + return -EINVAL; |
| + } |
| + |
| + hash_add_rcu(ext_to_groupid, &ed->hlist, hash); |
| + return 0; |
| +} |
| + |
| +static int insert_userid_exclude_entry_locked(const struct qstr *key, |
| + unsigned int value) |
| +{ |
| + struct hashtable_entry *hash_cur; |
| + struct hashtable_entry *new_entry; |
| + unsigned int hash = key->hash; |
| + |
| + /* Only insert if not already present */ |
| + hash_for_each_possible_rcu(package_to_userid, hash_cur, hlist, hash) { |
| + if (atomic_read(&hash_cur->value) == value && |
| + qstr_case_eq(key, &hash_cur->key)) |
| + return 0; |
| + } |
| + new_entry = alloc_hashtable_entry(key, value); |
| + if (!new_entry) |
| + return -ENOMEM; |
| + hash_add_rcu(package_to_userid, &new_entry->hlist, hash); |
| + return 0; |
| +} |
| + |
| +static int insert_packagelist_entry(const struct qstr *key, kuid_t value) |
| +{ |
| + struct pkg_list *pkg; |
| + int err; |
| + |
| + mutex_lock(&pkg_list_lock); |
| + err = insert_packagelist_appid_entry_locked(key, value); |
| + if (!err) { |
| + list_for_each_entry(pkg, &pkglist_listeners, list) { |
| + pkg->update(BY_NAME, key, 0); |
| + } |
| + } |
| + mutex_unlock(&pkg_list_lock); |
| + |
| + return err; |
| +} |
| + |
| +static int insert_ext_gid_entry(struct extension_details *ed) |
| +{ |
| + int err; |
| + |
| + mutex_lock(&pkg_list_lock); |
| + err = insert_ext_gid_entry_locked(ed); |
| + mutex_unlock(&pkg_list_lock); |
| + |
| + return err; |
| +} |
| + |
| +static int insert_userid_exclude_entry(const struct qstr *key, uint32_t value) |
| +{ |
| + int err; |
| + struct pkg_list *pkg; |
| + |
| + mutex_lock(&pkg_list_lock); |
| + err = insert_userid_exclude_entry_locked(key, value); |
| + if (!err) { |
| + list_for_each_entry(pkg, &pkglist_listeners, list) { |
| + pkg->update(BY_NAME|BY_USERID, key, value); |
| + } |
| + } |
| + mutex_unlock(&pkg_list_lock); |
| + |
| + return err; |
| +} |
| + |
| +static void free_hashtable_entry(struct hashtable_entry *entry) |
| +{ |
| + kfree(entry->key.name); |
| + kmem_cache_free(hashtable_entry_cachep, entry); |
| +} |
| + |
| +static void remove_packagelist_entry_locked(const struct qstr *key) |
| +{ |
| + struct hashtable_entry *hash_cur; |
| + unsigned int hash = key->hash; |
| + struct hlist_node *h_t; |
| + HLIST_HEAD(free_list); |
| + |
| + hash_for_each_possible_rcu(package_to_userid, hash_cur, hlist, hash) { |
| + if (qstr_case_eq(key, &hash_cur->key)) { |
| + hash_del_rcu(&hash_cur->hlist); |
| + hlist_add_head(&hash_cur->dlist, &free_list); |
| + } |
| + } |
| + hash_for_each_possible_rcu(package_to_appid, hash_cur, hlist, hash) { |
| + if (qstr_case_eq(key, &hash_cur->key)) { |
| + hash_del_rcu(&hash_cur->hlist); |
| + hlist_add_head(&hash_cur->dlist, &free_list); |
| + break; |
| + } |
| + } |
| + synchronize_rcu(); |
| + hlist_for_each_entry_safe(hash_cur, h_t, &free_list, dlist) |
| + free_hashtable_entry(hash_cur); |
| +} |
| + |
| +static void remove_packagelist_entry(const struct qstr *key) |
| +{ |
| + struct pkg_list *pkg; |
| + |
| + mutex_lock(&pkg_list_lock); |
| + remove_packagelist_entry_locked(key); |
| + list_for_each_entry(pkg, &pkglist_listeners, list) { |
| + pkg->update(BY_NAME, key, 0); |
| + } |
| + mutex_unlock(&pkg_list_lock); |
| +} |
| + |
| +static void remove_ext_gid_entry_locked(struct extension_details *ed) |
| +{ |
| + struct extension_details *hash_cur; |
| + struct qstr *key = &ed->name; |
| + unsigned int hash = key->hash; |
| + |
| + hash_for_each_possible_rcu(ext_to_groupid, hash_cur, hlist, hash) { |
| + if (qstr_case_eq(key, &hash_cur->name) |
| + && hash_cur->value == ed->value) { |
| + hash_del_rcu(&hash_cur->hlist); |
| + synchronize_rcu(); |
| + break; |
| + } |
| + } |
| +} |
| + |
| +static void remove_ext_gid_entry(struct extension_details *ed) |
| +{ |
| + mutex_lock(&pkg_list_lock); |
| + remove_ext_gid_entry_locked(ed); |
| + mutex_unlock(&pkg_list_lock); |
| +} |
| + |
| +static void remove_userid_all_entry_locked(uint32_t userid) |
| +{ |
| + struct hashtable_entry *hash_cur; |
| + struct hlist_node *h_t; |
| + HLIST_HEAD(free_list); |
| + int i; |
| + |
| + hash_for_each_rcu(package_to_userid, i, hash_cur, hlist) { |
| + if (atomic_read(&hash_cur->value) == userid) { |
| + hash_del_rcu(&hash_cur->hlist); |
| + hlist_add_head(&hash_cur->dlist, &free_list); |
| + } |
| + } |
| + synchronize_rcu(); |
| + hlist_for_each_entry_safe(hash_cur, h_t, &free_list, dlist) { |
| + free_hashtable_entry(hash_cur); |
| + } |
| +} |
| + |
| +static void remove_userid_all_entry(uint32_t userid) |
| +{ |
| + struct pkg_list *pkg; |
| + |
| + mutex_lock(&pkg_list_lock); |
| + remove_userid_all_entry_locked(userid); |
| + |
| + list_for_each_entry(pkg, &pkglist_listeners, list) { |
| + pkg->update(BY_USERID, NULL, userid); |
| + } |
| + mutex_unlock(&pkg_list_lock); |
| +} |
| + |
| +static void remove_userid_exclude_entry_locked(const struct qstr *key, |
| + uint32_t userid) |
| +{ |
| + struct hashtable_entry *hash_cur; |
| + unsigned int hash = key->hash; |
| + |
| + hash_for_each_possible_rcu(package_to_userid, hash_cur, hlist, hash) { |
| + if (qstr_case_eq(key, &hash_cur->key) && |
| + atomic_read(&hash_cur->value) == userid) { |
| + hash_del_rcu(&hash_cur->hlist); |
| + synchronize_rcu(); |
| + free_hashtable_entry(hash_cur); |
| + break; |
| + } |
| + } |
| +} |
| + |
| +static void remove_userid_exclude_entry(const struct qstr *key, uint32_t userid) |
| +{ |
| + struct pkg_list *pkg; |
| + |
| + mutex_lock(&pkg_list_lock); |
| + remove_userid_exclude_entry_locked(key, userid); |
| + list_for_each_entry(pkg, &pkglist_listeners, list) { |
| + pkg->update(BY_NAME|BY_USERID, key, userid); |
| + } |
| + mutex_unlock(&pkg_list_lock); |
| +} |
| + |
| +static void packagelist_destroy(void) |
| +{ |
| + struct hashtable_entry *hash_cur; |
| + struct hlist_node *h_t; |
| + HLIST_HEAD(free_list); |
| + int i; |
| + |
| + mutex_lock(&pkg_list_lock); |
| + hash_for_each_rcu(package_to_appid, i, hash_cur, hlist) { |
| + hash_del_rcu(&hash_cur->hlist); |
| + hlist_add_head(&hash_cur->dlist, &free_list); |
| + } |
| + hash_for_each_rcu(package_to_userid, i, hash_cur, hlist) { |
| + hash_del_rcu(&hash_cur->hlist); |
| + hlist_add_head(&hash_cur->dlist, &free_list); |
| + } |
| + synchronize_rcu(); |
| + hlist_for_each_entry_safe(hash_cur, h_t, &free_list, dlist) |
| + free_hashtable_entry(hash_cur); |
| + mutex_unlock(&pkg_list_lock); |
| + pr_info("pkglist: destroyed pkglist\n"); |
| +} |
| + |
| +#define PACKAGE_DETAILS_ATTR(_pfx, _name) \ |
| +static struct configfs_attribute _pfx##attr_##_name = { \ |
| + .ca_name = __stringify(_name), \ |
| + .ca_mode = S_IRUGO | S_IWUGO, \ |
| + .ca_owner = THIS_MODULE, \ |
| + .show = _pfx##_name##_show, \ |
| + .store = _pfx##_name##_store, \ |
| +} |
| + |
| +#define PACKAGE_DETAILS_ATTR_RO(_pfx, _name) \ |
| +static struct configfs_attribute _pfx##attr_##_name = { \ |
| + .ca_name = __stringify(_name), \ |
| + .ca_mode = S_IRUGO, \ |
| + .ca_owner = THIS_MODULE, \ |
| + .show = _pfx##_name##_show, \ |
| +} |
| + |
| +#define PACKAGE_DETAILS_ATTR_WO(_pfx, _name) \ |
| +static struct configfs_attribute _pfx##attr_##_name = { \ |
| + .ca_name = __stringify(_name), \ |
| + .ca_mode = S_IWUGO, \ |
| + .ca_owner = THIS_MODULE, \ |
| + .store = _pfx##_name##_store, \ |
| +} |
| + |
| + |
| +struct package_details { |
| + struct config_item item; |
| + struct qstr name; |
| +}; |
| + |
| +static inline struct package_details *to_package_details( |
| + struct config_item *item) |
| +{ |
| + return item ? container_of(item, struct package_details, item) : NULL; |
| +} |
| + |
| +#define PACKAGE_DETAILS_ATTRIBUTE(name) (&package_details_attr_##name) |
| + |
| +static ssize_t package_details_appid_show(struct config_item *item, char *page) |
| +{ |
| + return scnprintf(page, PAGE_SIZE, "%u\n", from_kuid(current_user_ns(), |
| + __get_appid(&to_package_details(item)->name))); |
| +} |
| + |
| +static ssize_t package_details_appid_store(struct config_item *item, |
| + const char *page, size_t count) |
| +{ |
| + unsigned int tmp; |
| + int ret; |
| + kuid_t uid; |
| + |
| + ret = kstrtouint(page, 10, &tmp); |
| + if (ret) |
| + return ret; |
| + |
| + uid = make_kuid(current_user_ns(), tmp); |
| + |
| + ret = insert_packagelist_entry(&to_package_details(item)->name, uid); |
| + |
| + if (ret) |
| + return ret; |
| + |
| + return count; |
| +} |
| + |
| +static ssize_t package_details_excluded_userids_show(struct config_item *item, |
| + char *page) |
| +{ |
| + struct package_details *package_details = to_package_details(item); |
| + struct hashtable_entry *hash_cur; |
| + unsigned int hash = package_details->name.hash; |
| + int count = 0; |
| + |
| + rcu_read_lock(); |
| + hash_for_each_possible_rcu(package_to_userid, hash_cur, hlist, hash) { |
| + if (qstr_case_eq(&package_details->name, &hash_cur->key)) |
| + count += scnprintf(page + count, PAGE_SIZE - count, |
| + "%d ", atomic_read(&hash_cur->value)); |
| + } |
| + rcu_read_unlock(); |
| + if (count) |
| + count--; |
| + count += scnprintf(page + count, PAGE_SIZE - count, "\n"); |
| + return count; |
| +} |
| + |
| +static ssize_t package_details_excluded_userids_store(struct config_item *item, |
| + const char *page, size_t count) |
| +{ |
| + unsigned int tmp; |
| + int ret; |
| + |
| + ret = kstrtouint(page, 10, &tmp); |
| + if (ret) |
| + return ret; |
| + |
| + ret = insert_userid_exclude_entry(&to_package_details(item)->name, tmp); |
| + |
| + if (ret) |
| + return ret; |
| + |
| + return count; |
| +} |
| + |
| +static ssize_t package_details_clear_userid_store(struct config_item *item, |
| + const char *page, size_t count) |
| +{ |
| + unsigned int tmp; |
| + int ret; |
| + |
| + ret = kstrtouint(page, 10, &tmp); |
| + if (ret) |
| + return ret; |
| + remove_userid_exclude_entry(&to_package_details(item)->name, tmp); |
| + return count; |
| +} |
| + |
| +static void package_details_release(struct config_item *item) |
| +{ |
| + struct package_details *package_details = to_package_details(item); |
| + |
| + pr_debug("pkglist: removing %s\n", package_details->name.name); |
| + remove_packagelist_entry(&package_details->name); |
| + kfree(package_details->name.name); |
| + kfree(package_details); |
| +} |
| + |
| +PACKAGE_DETAILS_ATTR(package_details_, appid); |
| +PACKAGE_DETAILS_ATTR(package_details_, excluded_userids); |
| +PACKAGE_DETAILS_ATTR_WO(package_details_, clear_userid); |
| + |
| +static struct configfs_attribute *package_details_attrs[] = { |
| + PACKAGE_DETAILS_ATTRIBUTE(appid), |
| + PACKAGE_DETAILS_ATTRIBUTE(excluded_userids), |
| + PACKAGE_DETAILS_ATTRIBUTE(clear_userid), |
| + NULL, |
| +}; |
| + |
| +static struct configfs_item_operations package_details_item_ops = { |
| + .release = package_details_release, |
| +}; |
| + |
| +static struct config_item_type package_appid_type = { |
| + .ct_item_ops = &package_details_item_ops, |
| + .ct_attrs = package_details_attrs, |
| + .ct_owner = THIS_MODULE, |
| +}; |
| + |
| +static inline struct extensions_value *to_extensions_value( |
| + struct config_item *item) |
| +{ |
| + return item ? container_of(to_config_group(item), |
| + struct extensions_value, group) |
| + : NULL; |
| +} |
| + |
| +static inline struct extension_details *to_extension_details( |
| + struct config_item *item) |
| +{ |
| + return item ? container_of(item, struct extension_details, item) |
| + : NULL; |
| +} |
| + |
| +#define EXTENSIONS_VALUE_ATTRIBUTE(name) (&extensions_value_attr_##name) |
| + |
| +static void extension_details_release(struct config_item *item) |
| +{ |
| + struct extension_details *ed = to_extension_details(item); |
| + |
| + pr_debug("pkglist: No longer mapping %s files to gid %d\n", |
| + ed->name.name, |
| + from_kgid(current_user_ns(), ed->value->gid)); |
| + remove_ext_gid_entry(ed); |
| + kfree(ed->name.name); |
| + kfree(ed); |
| +} |
| + |
| +static struct configfs_item_operations extension_details_item_ops = { |
| + .release = extension_details_release, |
| +}; |
| + |
| +static ssize_t extensions_value_ext_gid_show( |
| + struct config_item *item, char *page) |
| +{ |
| + return scnprintf(page, PAGE_SIZE, "%u\n", |
| + from_kgid(current_user_ns(), to_extensions_value(item)->gid)); |
| +} |
| + |
| +static ssize_t extensions_value_ext_gid_store( |
| + struct config_item *item, |
| + const char *page, size_t count) |
| +{ |
| + unsigned int tmp; |
| + int ret; |
| + |
| + ret = kstrtouint(page, 10, &tmp); |
| + if (ret) |
| + return ret; |
| + |
| + to_extensions_value(item)->gid = make_kgid(current_user_ns(), tmp); |
| + |
| + return count; |
| +} |
| + |
| +PACKAGE_DETAILS_ATTR(extensions_value_, ext_gid); |
| + |
| +static struct configfs_attribute *extensions_value_attrs[] = { |
| + EXTENSIONS_VALUE_ATTRIBUTE(ext_gid), |
| + NULL, |
| +}; |
| + |
| +static struct config_item_type extension_details_type = { |
| + .ct_item_ops = &extension_details_item_ops, |
| + .ct_owner = THIS_MODULE, |
| +}; |
| + |
| +static struct config_item *extension_details_make_item( |
| + struct config_group *group, const char *name) |
| +{ |
| + struct extensions_value *extensions_value = |
| + to_extensions_value(&group->cg_item); |
| + struct extension_details *extension_details = |
| + kzalloc(sizeof(struct extension_details), GFP_KERNEL); |
| + const char *tmp; |
| + int ret; |
| + |
| + if (!extension_details) |
| + return ERR_PTR(-ENOMEM); |
| + |
| + tmp = kstrdup(name, GFP_KERNEL); |
| + if (!tmp) { |
| + kfree(extension_details); |
| + return ERR_PTR(-ENOMEM); |
| + } |
| + qstr_init(&extension_details->name, tmp); |
| + extension_details->value = extensions_value; |
| + ret = insert_ext_gid_entry(extension_details); |
| + |
| + if (ret) { |
| + kfree(extension_details->name.name); |
| + kfree(extension_details); |
| + return ERR_PTR(ret); |
| + } |
| + config_item_init_type_name(&extension_details->item, name, |
| + &extension_details_type); |
| + |
| + return &extension_details->item; |
| +} |
| + |
| +static struct configfs_group_operations extensions_value_group_ops = { |
| + .make_item = extension_details_make_item, |
| +}; |
| + |
| +static struct config_item_type extensions_name_type = { |
| + .ct_attrs = extensions_value_attrs, |
| + .ct_group_ops = &extensions_value_group_ops, |
| + .ct_owner = THIS_MODULE, |
| +}; |
| + |
| +static struct config_group *extensions_make_group(struct config_group *group, |
| + const char *name) |
| +{ |
| + struct extensions_value *extensions_value; |
| + unsigned int tmp; |
| + int ret; |
| + |
| + extensions_value = kzalloc(sizeof(struct extensions_value), GFP_KERNEL); |
| + if (!extensions_value) |
| + return ERR_PTR(-ENOMEM); |
| + /* For legacy reasons, if the name is a number, assume it's the gid*/ |
| + ret = kstrtouint(name, 10, &tmp); |
| + if (!ret) |
| + extensions_value->gid = make_kgid(current_user_ns(), tmp); |
| + |
| + config_group_init_type_name(&extensions_value->group, name, |
| + &extensions_name_type); |
| + return &extensions_value->group; |
| +} |
| + |
| +static void extensions_drop_group(struct config_group *group, |
| + struct config_item *item) |
| +{ |
| + struct extensions_value *value = to_extensions_value(item); |
| + |
| + pr_debug("pkglist: No longer mapping any files to gid %d\n", |
| + from_kgid(current_user_ns(), value->gid)); |
| + kfree(value); |
| +} |
| + |
| +static struct configfs_group_operations extensions_group_ops = { |
| + .make_group = extensions_make_group, |
| + .drop_item = extensions_drop_group, |
| +}; |
| + |
| +static struct config_item_type extensions_type = { |
| + .ct_group_ops = &extensions_group_ops, |
| + .ct_owner = THIS_MODULE, |
| +}; |
| + |
| +static struct config_group extension_group = { |
| + .cg_item = { |
| + .ci_namebuf = "extensions", |
| + .ci_type = &extensions_type, |
| + }, |
| +}; |
| + |
| +struct packages { |
| + struct configfs_subsystem subsystem; |
| +}; |
| + |
| +static inline struct packages *to_packages(struct config_item *item) |
| +{ |
| + return item ? container_of( |
| + to_configfs_subsystem(to_config_group(item)), |
| + struct packages, subsystem) : NULL; |
| +} |
| + |
| +static struct config_item *packages_make_item(struct config_group *group, |
| + const char *name) |
| +{ |
| + struct package_details *package_details; |
| + const char *tmp; |
| + |
| + package_details = kzalloc(sizeof(struct package_details), GFP_KERNEL); |
| + if (!package_details) |
| + return ERR_PTR(-ENOMEM); |
| + tmp = kstrdup(name, GFP_KERNEL); |
| + if (!tmp) { |
| + kfree(package_details); |
| + return ERR_PTR(-ENOMEM); |
| + } |
| + qstr_init(&package_details->name, tmp); |
| + config_item_init_type_name(&package_details->item, name, |
| + &package_appid_type); |
| + |
| + return &package_details->item; |
| +} |
| + |
| +static ssize_t packages_list_show(struct config_item *item, char *page) |
| +{ |
| + struct hashtable_entry *hash_cur_app; |
| + struct hashtable_entry *hash_cur_user; |
| + int i; |
| + int count = 0, written = 0; |
| + const char errormsg[] = "<truncated>\n"; |
| + unsigned int hash; |
| + |
| + rcu_read_lock(); |
| + hash_for_each_rcu(package_to_appid, i, hash_cur_app, hlist) { |
| + written = scnprintf(page + count, |
| + PAGE_SIZE - sizeof(errormsg) - count, |
| + "%s %d\n", |
| + hash_cur_app->key.name, |
| + atomic_read(&hash_cur_app->value)); |
| + hash = hash_cur_app->key.hash; |
| + hash_for_each_possible_rcu(package_to_userid, hash_cur_user, hlist, hash) { |
| + if (qstr_case_eq(&hash_cur_app->key, &hash_cur_user->key)) { |
| + written += scnprintf(page + count + written - 1, |
| + PAGE_SIZE - sizeof(errormsg) - count - written + 1, |
| + " %d\n", atomic_read(&hash_cur_user->value)) - 1; |
| + } |
| + } |
| + if (count + written == PAGE_SIZE - sizeof(errormsg) - 1) { |
| + count += scnprintf(page + count, PAGE_SIZE - count, errormsg); |
| + break; |
| + } |
| + count += written; |
| + } |
| + rcu_read_unlock(); |
| + |
| + return count; |
| +} |
| + |
| +static ssize_t packages_remove_userid_store(struct config_item *item, |
| + const char *page, size_t count) |
| +{ |
| + unsigned int tmp; |
| + int ret; |
| + |
| + ret = kstrtouint(page, 10, &tmp); |
| + if (ret) |
| + return ret; |
| + remove_userid_all_entry(tmp); |
| + return count; |
| +} |
| + |
| +static struct configfs_attribute packages_attr_packages_gid_list = { |
| + .ca_name = "packages_gid.list", |
| + .ca_mode = S_IRUGO, |
| + .ca_owner = THIS_MODULE, |
| + .show = packages_list_show, |
| +}; |
| +PACKAGE_DETAILS_ATTR_WO(packages_, remove_userid); |
| + |
| +static struct configfs_attribute *packages_attrs[] = { |
| + &packages_attr_packages_gid_list, |
| + &packages_attr_remove_userid, |
| + NULL, |
| +}; |
| + |
| +/* |
| + * Note that, since no extra work is required on ->drop_item(), |
| + * no ->drop_item() is provided. |
| + */ |
| +static struct configfs_group_operations packages_group_ops = { |
| + .make_item = packages_make_item, |
| +}; |
| + |
| +static struct config_item_type packages_type = { |
| + .ct_group_ops = &packages_group_ops, |
| + .ct_attrs = packages_attrs, |
| + .ct_owner = THIS_MODULE, |
| +}; |
| + |
| +static struct config_group *sd_default_groups[] = { |
| + &extension_group, |
| + NULL, |
| +}; |
| + |
| +static struct packages pkglist_packages = { |
| + .subsystem = { |
| + .su_group = { |
| + .cg_item = { |
| + .ci_type = &packages_type, |
| + }, |
| + }, |
| + }, |
| +}; |
| + |
| +static int configfs_pkglist_init(void) |
| +{ |
| + int ret, i; |
| + struct configfs_subsystem *subsys = &pkglist_packages.subsystem; |
| + config_item_set_name(&pkglist_packages.subsystem.su_group.cg_item, |
| + pkglist_config_location); |
| + config_group_init(&subsys->su_group); |
| + |
| + for (i = 0; sd_default_groups[i]; i++) { |
| + config_group_init(sd_default_groups[i]); |
| + configfs_add_default_group(sd_default_groups[i], &subsys->su_group); |
| + } |
| + mutex_init(&subsys->su_mutex); |
| + ret = configfs_register_subsystem(subsys); |
| + if (ret) { |
| + pr_err("Error %d while registering subsystem %s\n", ret, |
| + subsys->su_group.cg_item.ci_namebuf); |
| + } |
| + return ret; |
| +} |
| + |
| +static void configfs_pkglist_exit(void) |
| +{ |
| + configfs_unregister_subsystem(&pkglist_packages.subsystem); |
| +} |
| + |
| +void pkglist_register_update_listener(struct pkg_list *pkg) |
| +{ |
| + if (!pkg->update) |
| + return; |
| + mutex_lock(&pkg_list_lock); |
| + list_add(&pkg->list, &pkglist_listeners); |
| + mutex_unlock(&pkg_list_lock); |
| +} |
| +EXPORT_SYMBOL_GPL(pkglist_register_update_listener); |
| + |
| +void pkglist_unregister_update_listener(struct pkg_list *pkg) |
| +{ |
| + mutex_lock(&pkg_list_lock); |
| + list_del(&pkg->list); |
| + mutex_unlock(&pkg_list_lock); |
| +} |
| +EXPORT_SYMBOL_GPL(pkglist_unregister_update_listener); |
| + |
| +static int __init pkglist_init(void) |
| +{ |
| + hashtable_entry_cachep = |
| + kmem_cache_create("packagelist_hashtable_entry", |
| + sizeof(struct hashtable_entry), 0, 0, NULL); |
| + if (!hashtable_entry_cachep) { |
| + pr_err("pkglist: failed creating pkgl_hashtable entry slab cache\n"); |
| + return -ENOMEM; |
| + } |
| + |
| + return configfs_pkglist_init(); |
| +} |
| +module_init(pkglist_init); |
| + |
| +static void __exit pkglist_exit(void) |
| +{ |
| + configfs_pkglist_exit(); |
| + packagelist_destroy(); |
| + kmem_cache_destroy(hashtable_entry_cachep); |
| +} |
| + |
| +module_exit(pkglist_exit); |
| + |
| +MODULE_AUTHOR("Daniel Rosenberg, Google"); |
| +MODULE_DESCRIPTION("Configfs Pkglist implementation"); |
| +MODULE_LICENSE("GPL v2"); |
| diff --git a/drivers/pkglist/pkglist_none.c b/drivers/pkglist/pkglist_none.c |
| new file mode 100644 |
| index 000000000000..9e10fd114d69 |
| --- /dev/null |
| +++ b/drivers/pkglist/pkglist_none.c |
| @@ -0,0 +1,57 @@ |
| +/* |
| + * Copyright (C) 2017 Google Inc., Author: Daniel Rosenberg <drosen@google.com> |
| + * |
| + * This program is free software; you can redistribute it and/or modify |
| + * it under the terms of the GNU General Public License version 2 as |
| + * published by the Free Software Foundation. |
| + */ |
| + |
| +#include <linux/ctype.h> |
| +#include <linux/dcache.h> |
| +#include <linux/init.h> |
| +#include <linux/module.h> |
| +#include <linux/pkglist.h> |
| + |
| +kuid_t pkglist_get_appid(const char *key) |
| +{ |
| + return make_kuid(&init_user_ns, 0); |
| +} |
| +EXPORT_SYMBOL_GPL(pkglist_get_appid); |
| + |
| +kgid_t pkglist_get_ext_gid(const char *key) |
| +{ |
| + return make_kgid(&init_user_ns, 0); |
| +} |
| +EXPORT_SYMBOL_GPL(pkglist_get_ext_gid); |
| + |
| +bool pkglist_user_is_excluded(const char *key, uint32_t user) |
| +{ |
| + return false; |
| +} |
| +EXPORT_SYMBOL_GPL(pkglist_user_is_excluded); |
| + |
| +kuid_t pkglist_get_allowed_appid(const char *key, uint32_t user) |
| +{ |
| + return make_kuid(&init_user_ns, 0); |
| +} |
| +EXPORT_SYMBOL_GPL(pkglist_get_allowed_appid); |
| + |
| +void pkglist_register_update_listener(struct pkg_list *pkg) { } |
| +EXPORT_SYMBOL_GPL(pkglist_register_update_listener); |
| + |
| +void pkglist_unregister_update_listener(struct pkg_list *pkg) { } |
| +EXPORT_SYMBOL_GPL(pkglist_unregister_update_listener); |
| + |
| +static int __init pkglist_init(void) |
| +{ |
| + return 0; |
| +} |
| +module_init(pkglist_init); |
| + |
| +static void pkglist_exit(void) { } |
| + |
| +module_exit(pkglist_exit); |
| + |
| +MODULE_AUTHOR("Daniel Rosenberg, Google"); |
| +MODULE_DESCRIPTION("Empty Pkglist implementation"); |
| +MODULE_LICENSE("GPL v2"); |
| diff --git a/include/linux/pkglist.h b/include/linux/pkglist.h |
| new file mode 100644 |
| index 000000000000..8718d27ac171 |
| --- /dev/null |
| +++ b/include/linux/pkglist.h |
| @@ -0,0 +1,38 @@ |
| +#ifndef _PKGLIST_H_ |
| +#define _PKGLIST_H_ |
| + |
| +#include <linux/dcache.h> |
| +#include <linux/uidgid.h> |
| + |
| +#define QSTR_LITERAL(string) QSTR_INIT(string, sizeof(string)-1) |
| + |
| +static inline bool str_case_eq(const char *s1, const char *s2) |
| +{ |
| + return !strcasecmp(s1, s2); |
| +} |
| + |
| +static inline bool str_n_case_eq(const char *s1, const char *s2, size_t len) |
| +{ |
| + return !strncasecmp(s1, s2, len); |
| +} |
| + |
| +static inline bool qstr_case_eq(const struct qstr *q1, const struct qstr *q2) |
| +{ |
| + return q1->len == q2->len && str_case_eq(q1->name, q2->name); |
| +} |
| + |
| +#define BY_NAME BIT(0) |
| +#define BY_USERID BIT(1) |
| + |
| +struct pkg_list { |
| + struct list_head list; |
| + void (*update)(int flags, const struct qstr *name, uint32_t userid); |
| +}; |
| + |
| +kuid_t pkglist_get_appid(const char *key); |
| +kgid_t pkglist_get_ext_gid(const char *key); |
| +bool pkglist_user_is_excluded(const char *key, uint32_t user); |
| +kuid_t pkglist_get_allowed_appid(const char *key, uint32_t user); |
| +void pkglist_register_update_listener(struct pkg_list *pkg); |
| +void pkglist_unregister_update_listener(struct pkg_list *pkg); |
| +#endif |
| -- |
| 2.35.0 |
| |