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,  |