diff options
Diffstat (limited to 'drivers/android/binderfs.c')
-rw-r--r-- | drivers/android/binderfs.c | 290 |
1 files changed, 255 insertions, 35 deletions
diff --git a/drivers/android/binderfs.c b/drivers/android/binderfs.c index e773f45d19d9..e2580e5316a2 100644 --- a/drivers/android/binderfs.c +++ b/drivers/android/binderfs.c @@ -48,45 +48,23 @@ static dev_t binderfs_dev; static DEFINE_MUTEX(binderfs_minors_mutex); static DEFINE_IDA(binderfs_minors); -/** - * binderfs_mount_opts - mount options for binderfs - * @max: maximum number of allocatable binderfs binder devices - */ -struct binderfs_mount_opts { - int max; -}; - enum { Opt_max, + Opt_stats_mode, Opt_err }; +enum binderfs_stats_mode { + STATS_NONE, + STATS_GLOBAL, +}; + static const match_table_t tokens = { { Opt_max, "max=%d" }, + { Opt_stats_mode, "stats=%s" }, { Opt_err, NULL } }; -/** - * binderfs_info - information about a binderfs mount - * @ipc_ns: The ipc namespace the binderfs mount belongs to. - * @control_dentry: This records the dentry of this binderfs mount - * binder-control device. - * @root_uid: uid that needs to be used when a new binder device is - * created. - * @root_gid: gid that needs to be used when a new binder device is - * created. - * @mount_opts: The mount options in use. - * @device_count: The current number of allocated binder devices. - */ -struct binderfs_info { - struct ipc_namespace *ipc_ns; - struct dentry *control_dentry; - kuid_t root_uid; - kgid_t root_gid; - struct binderfs_mount_opts mount_opts; - int device_count; -}; - static inline struct binderfs_info *BINDERFS_I(const struct inode *inode) { return inode->i_sb->s_fs_info; @@ -186,8 +164,7 @@ static int binderfs_binder_device_create(struct inode *ref_inode, req->major = MAJOR(binderfs_dev); req->minor = minor; - ret = copy_to_user(userp, req, sizeof(*req)); - if (ret) { + if (userp && copy_to_user(userp, req, sizeof(*req))) { ret = -EFAULT; goto err; } @@ -272,7 +249,7 @@ static void binderfs_evict_inode(struct inode *inode) clear_inode(inode); - if (!device) + if (!S_ISCHR(inode->i_mode) || !device) return; mutex_lock(&binderfs_minors_mutex); @@ -291,8 +268,9 @@ static void binderfs_evict_inode(struct inode *inode) static int binderfs_parse_mount_opts(char *data, struct binderfs_mount_opts *opts) { - char *p; + char *p, *stats; opts->max = BINDERFS_MAX_MINOR; + opts->stats_mode = STATS_NONE; while ((p = strsep(&data, ",")) != NULL) { substring_t args[MAX_OPT_ARGS]; @@ -312,6 +290,22 @@ static int binderfs_parse_mount_opts(char *data, opts->max = max_devices; break; + case Opt_stats_mode: + if (!capable(CAP_SYS_ADMIN)) + return -EINVAL; + + stats = match_strdup(&args[0]); + if (!stats) + return -ENOMEM; + + if (strcmp(stats, "global") != 0) { + kfree(stats); + return -EINVAL; + } + + opts->stats_mode = STATS_GLOBAL; + kfree(stats); + break; default: pr_err("Invalid mount options\n"); return -EINVAL; @@ -323,8 +317,21 @@ static int binderfs_parse_mount_opts(char *data, static int binderfs_remount(struct super_block *sb, int *flags, char *data) { + int prev_stats_mode, ret; struct binderfs_info *info = sb->s_fs_info; - return binderfs_parse_mount_opts(data, &info->mount_opts); + + prev_stats_mode = info->mount_opts.stats_mode; + ret = binderfs_parse_mount_opts(data, &info->mount_opts); + if (ret) + return ret; + + if (prev_stats_mode != info->mount_opts.stats_mode) { + pr_err("Binderfs stats mode cannot be changed during a remount\n"); + info->mount_opts.stats_mode = prev_stats_mode; + return -EINVAL; + } + + return 0; } static int binderfs_show_mount_opts(struct seq_file *seq, struct dentry *root) @@ -334,6 +341,8 @@ static int binderfs_show_mount_opts(struct seq_file *seq, struct dentry *root) info = root->d_sb->s_fs_info; if (info->mount_opts.max <= BINDERFS_MAX_MINOR) seq_printf(seq, ",max=%d", info->mount_opts.max); + if (info->mount_opts.stats_mode == STATS_GLOBAL) + seq_printf(seq, ",stats=global"); return 0; } @@ -462,11 +471,192 @@ static const struct inode_operations binderfs_dir_inode_operations = { .unlink = binderfs_unlink, }; +static struct inode *binderfs_make_inode(struct super_block *sb, int mode) +{ + struct inode *ret; + + ret = new_inode(sb); + if (ret) { + ret->i_ino = iunique(sb, BINDERFS_MAX_MINOR + INODE_OFFSET); + ret->i_mode = mode; + ret->i_atime = ret->i_mtime = ret->i_ctime = current_time(ret); + } + return ret; +} + +static struct dentry *binderfs_create_dentry(struct dentry *parent, + const char *name) +{ + struct dentry *dentry; + + dentry = lookup_one_len(name, parent, strlen(name)); + if (IS_ERR(dentry)) + return dentry; + + /* Return error if the file/dir already exists. */ + if (d_really_is_positive(dentry)) { + dput(dentry); + return ERR_PTR(-EEXIST); + } + + return dentry; +} + +void binderfs_remove_file(struct dentry *dentry) +{ + struct inode *parent_inode; + + parent_inode = d_inode(dentry->d_parent); + inode_lock(parent_inode); + if (simple_positive(dentry)) { + dget(dentry); + simple_unlink(parent_inode, dentry); + d_delete(dentry); + dput(dentry); + } + inode_unlock(parent_inode); +} + +struct dentry *binderfs_create_file(struct dentry *parent, const char *name, + const struct file_operations *fops, + void *data) +{ + struct dentry *dentry; + struct inode *new_inode, *parent_inode; + struct super_block *sb; + + parent_inode = d_inode(parent); + inode_lock(parent_inode); + + dentry = binderfs_create_dentry(parent, name); + if (IS_ERR(dentry)) + goto out; + + sb = parent_inode->i_sb; + new_inode = binderfs_make_inode(sb, S_IFREG | 0444); + if (!new_inode) { + dput(dentry); + dentry = ERR_PTR(-ENOMEM); + goto out; + } + + new_inode->i_fop = fops; + new_inode->i_private = data; + d_instantiate(dentry, new_inode); + fsnotify_create(parent_inode, dentry); + +out: + inode_unlock(parent_inode); + return dentry; +} + +static struct dentry *binderfs_create_dir(struct dentry *parent, + const char *name) +{ + struct dentry *dentry; + struct inode *new_inode, *parent_inode; + struct super_block *sb; + + parent_inode = d_inode(parent); + inode_lock(parent_inode); + + dentry = binderfs_create_dentry(parent, name); + if (IS_ERR(dentry)) + goto out; + + sb = parent_inode->i_sb; + new_inode = binderfs_make_inode(sb, S_IFDIR | 0755); + if (!new_inode) { + dput(dentry); + dentry = ERR_PTR(-ENOMEM); + goto out; + } + + new_inode->i_fop = &simple_dir_operations; + new_inode->i_op = &simple_dir_inode_operations; + + set_nlink(new_inode, 2); + d_instantiate(dentry, new_inode); + inc_nlink(parent_inode); + fsnotify_mkdir(parent_inode, dentry); + +out: + inode_unlock(parent_inode); + return dentry; +} + +static int init_binder_logs(struct super_block *sb) +{ + struct dentry *binder_logs_root_dir, *dentry, *proc_log_dir; + struct binderfs_info *info; + int ret = 0; + + binder_logs_root_dir = binderfs_create_dir(sb->s_root, + "binder_logs"); + if (IS_ERR(binder_logs_root_dir)) { + ret = PTR_ERR(binder_logs_root_dir); + goto out; + } + + dentry = binderfs_create_file(binder_logs_root_dir, "stats", + &binder_stats_fops, NULL); + if (IS_ERR(dentry)) { + ret = PTR_ERR(dentry); + goto out; + } + + dentry = binderfs_create_file(binder_logs_root_dir, "state", + &binder_state_fops, NULL); + if (IS_ERR(dentry)) { + ret = PTR_ERR(dentry); + goto out; + } + + dentry = binderfs_create_file(binder_logs_root_dir, "transactions", + &binder_transactions_fops, NULL); + if (IS_ERR(dentry)) { + ret = PTR_ERR(dentry); + goto out; + } + + dentry = binderfs_create_file(binder_logs_root_dir, + "transaction_log", + &binder_transaction_log_fops, + &binder_transaction_log); + if (IS_ERR(dentry)) { + ret = PTR_ERR(dentry); + goto out; + } + + dentry = binderfs_create_file(binder_logs_root_dir, + "failed_transaction_log", + &binder_transaction_log_fops, + &binder_transaction_log_failed); + if (IS_ERR(dentry)) { + ret = PTR_ERR(dentry); + goto out; + } + + proc_log_dir = binderfs_create_dir(binder_logs_root_dir, "proc"); + if (IS_ERR(proc_log_dir)) { + ret = PTR_ERR(proc_log_dir); + goto out; + } + info = sb->s_fs_info; + info->proc_log_dir = proc_log_dir; + +out: + return ret; +} + static int binderfs_fill_super(struct super_block *sb, void *data, int silent) { int ret; struct binderfs_info *info; struct inode *inode = NULL; + struct binderfs_device device_info = { 0 }; + const char *name; + size_t len; sb->s_blocksize = PAGE_SIZE; sb->s_blocksize_bits = PAGE_SHIFT; @@ -521,7 +711,25 @@ static int binderfs_fill_super(struct super_block *sb, void *data, int silent) if (!sb->s_root) return -ENOMEM; - return binderfs_binder_ctl_create(sb); + ret = binderfs_binder_ctl_create(sb); + if (ret) + return ret; + + name = binder_devices_param; + for (len = strcspn(name, ","); len > 0; len = strcspn(name, ",")) { + strscpy(device_info.name, name, len + 1); + ret = binderfs_binder_device_create(inode, NULL, &device_info); + if (ret) + return ret; + name += len; + if (*name == ',') + name++; + } + + if (info->mount_opts.stats_mode == STATS_GLOBAL) + return init_binder_logs(sb); + + return 0; } static struct dentry *binderfs_mount(struct file_system_type *fs_type, @@ -553,6 +761,18 @@ static struct file_system_type binder_fs_type = { int __init init_binderfs(void) { int ret; + const char *name; + size_t len; + + /* Verify that the default binderfs device names are valid. */ + name = binder_devices_param; + for (len = strcspn(name, ","); len > 0; len = strcspn(name, ",")) { + if (len > BINDERFS_MAX_NAME) + return -E2BIG; + name += len; + if (*name == ',') + name++; + } /* Allocate new major number for binderfs. */ ret = alloc_chrdev_region(&binderfs_dev, 0, BINDERFS_MAX_MINOR, |