cgroup: introduce struct cfent
This patch adds cfent (cgroup file entry) which is the association
between a cgroup and a file.  This is in-cgroup representation of
files under a cgroup directory.  This simplifies walking walking
cgroup files and thus cgroup_clear_directory(), which is now
implemented in two parts - cgroup_rm_file() and a loop around it.
cgroup_rm_file() will be used to implement cftype removal and cfent is
scheduled to serve cgroup specific per-file data (e.g. for sysfs-like
"sever" semantics).
v2: - cfe was freed from cgroup_rm_file() which led to use-after-free
      if the file had openers at the time of removal.  Moved to
      cgroup_diput().
    - cgroup_clear_directory() triggered WARN_ON_ONCE() if d_subdirs
      wasn't empty after removing all files.  This triggered
      spuriously if some files were open during directory clearing.
      Removed.
v3: - In cgroup_diput(), WARN_ONCE(!list_empty(&cfe->node)) could be
      spuriously triggered for root cgroups because they don't go
      through cgroup_clear_directory() on unmount.  Don't trigger WARN
      for root cgroups.
Signed-off-by: Tejun Heo <tj@kernel.org>
Acked-by: Li Zefan <lizf@cn.fujitsu.com>
Cc: Glauber Costa <glommer@parallels.com>
	
	
This commit is contained in:
		
					parent
					
						
							
								f6ea93723d
							
						
					
				
			
			
				commit
				
					
						05ef1d7c4a
					
				
			
		
					 2 changed files with 75 additions and 33 deletions
				
			
		|  | @ -175,6 +175,7 @@ struct cgroup { | ||||||
| 	 */ | 	 */ | ||||||
| 	struct list_head sibling;	/* my parent's children */ | 	struct list_head sibling;	/* my parent's children */ | ||||||
| 	struct list_head children;	/* my children */ | 	struct list_head children;	/* my children */ | ||||||
|  | 	struct list_head files;		/* my files */ | ||||||
| 
 | 
 | ||||||
| 	struct cgroup *parent;		/* my parent */ | 	struct cgroup *parent;		/* my parent */ | ||||||
| 	struct dentry __rcu *dentry;	/* cgroup fs entry, RCU protected */ | 	struct dentry __rcu *dentry;	/* cgroup fs entry, RCU protected */ | ||||||
|  |  | ||||||
							
								
								
									
										107
									
								
								kernel/cgroup.c
									
										
									
									
									
								
							
							
						
						
									
										107
									
								
								kernel/cgroup.c
									
										
									
									
									
								
							|  | @ -147,6 +147,15 @@ struct cgroupfs_root { | ||||||
|  */ |  */ | ||||||
| static struct cgroupfs_root rootnode; | static struct cgroupfs_root rootnode; | ||||||
| 
 | 
 | ||||||
|  | /*
 | ||||||
|  |  * cgroupfs file entry, pointed to from leaf dentry->d_fsdata. | ||||||
|  |  */ | ||||||
|  | struct cfent { | ||||||
|  | 	struct list_head		node; | ||||||
|  | 	struct dentry			*dentry; | ||||||
|  | 	struct cftype			*type; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| /*
 | /*
 | ||||||
|  * CSS ID -- ID per subsys's Cgroup Subsys State(CSS). used only when |  * CSS ID -- ID per subsys's Cgroup Subsys State(CSS). used only when | ||||||
|  * cgroup_subsys->use_id != 0. |  * cgroup_subsys->use_id != 0. | ||||||
|  | @ -287,11 +296,16 @@ static inline struct cgroup *__d_cgrp(struct dentry *dentry) | ||||||
| 	return dentry->d_fsdata; | 	return dentry->d_fsdata; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static inline struct cftype *__d_cft(struct dentry *dentry) | static inline struct cfent *__d_cfe(struct dentry *dentry) | ||||||
| { | { | ||||||
| 	return dentry->d_fsdata; | 	return dentry->d_fsdata; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static inline struct cftype *__d_cft(struct dentry *dentry) | ||||||
|  | { | ||||||
|  | 	return __d_cfe(dentry)->type; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| /* the list of cgroups eligible for automatic release. Protected by
 | /* the list of cgroups eligible for automatic release. Protected by
 | ||||||
|  * release_list_lock */ |  * release_list_lock */ | ||||||
| static LIST_HEAD(release_list); | static LIST_HEAD(release_list); | ||||||
|  | @ -877,6 +891,14 @@ static void cgroup_diput(struct dentry *dentry, struct inode *inode) | ||||||
| 		BUG_ON(!list_empty(&cgrp->pidlists)); | 		BUG_ON(!list_empty(&cgrp->pidlists)); | ||||||
| 
 | 
 | ||||||
| 		kfree_rcu(cgrp, rcu_head); | 		kfree_rcu(cgrp, rcu_head); | ||||||
|  | 	} else { | ||||||
|  | 		struct cfent *cfe = __d_cfe(dentry); | ||||||
|  | 		struct cgroup *cgrp = dentry->d_parent->d_fsdata; | ||||||
|  | 
 | ||||||
|  | 		WARN_ONCE(!list_empty(&cfe->node) && | ||||||
|  | 			  cgrp != &cgrp->root->top_cgroup, | ||||||
|  | 			  "cfe still linked for %s\n", cfe->type->name); | ||||||
|  | 		kfree(cfe); | ||||||
| 	} | 	} | ||||||
| 	iput(inode); | 	iput(inode); | ||||||
| } | } | ||||||
|  | @ -895,34 +917,36 @@ static void remove_dir(struct dentry *d) | ||||||
| 	dput(parent); | 	dput(parent); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void cgroup_clear_directory(struct dentry *dentry) | static int cgroup_rm_file(struct cgroup *cgrp, const struct cftype *cft) | ||||||
| { | { | ||||||
| 	struct list_head *node; | 	struct cfent *cfe; | ||||||
| 
 | 
 | ||||||
| 	BUG_ON(!mutex_is_locked(&dentry->d_inode->i_mutex)); | 	lockdep_assert_held(&cgrp->dentry->d_inode->i_mutex); | ||||||
| 	spin_lock(&dentry->d_lock); | 	lockdep_assert_held(&cgroup_mutex); | ||||||
| 	node = dentry->d_subdirs.next; |  | ||||||
| 	while (node != &dentry->d_subdirs) { |  | ||||||
| 		struct dentry *d = list_entry(node, struct dentry, d_u.d_child); |  | ||||||
| 
 | 
 | ||||||
| 		spin_lock_nested(&d->d_lock, DENTRY_D_LOCK_NESTED); | 	list_for_each_entry(cfe, &cgrp->files, node) { | ||||||
| 		list_del_init(node); | 		struct dentry *d = cfe->dentry; | ||||||
| 		if (d->d_inode) { | 
 | ||||||
| 			/* This should never be called on a cgroup
 | 		if (cft && cfe->type != cft) | ||||||
| 			 * directory with child cgroups */ | 			continue; | ||||||
| 			BUG_ON(d->d_inode->i_mode & S_IFDIR); | 
 | ||||||
| 			dget_dlock(d); | 		dget(d); | ||||||
| 			spin_unlock(&d->d_lock); | 		d_delete(d); | ||||||
| 			spin_unlock(&dentry->d_lock); | 		simple_unlink(d->d_inode, d); | ||||||
| 			d_delete(d); | 		list_del_init(&cfe->node); | ||||||
| 			simple_unlink(dentry->d_inode, d); | 		dput(d); | ||||||
| 			dput(d); | 
 | ||||||
| 			spin_lock(&dentry->d_lock); | 		return 0; | ||||||
| 		} else |  | ||||||
| 			spin_unlock(&d->d_lock); |  | ||||||
| 		node = dentry->d_subdirs.next; |  | ||||||
| 	} | 	} | ||||||
| 	spin_unlock(&dentry->d_lock); | 	return -ENOENT; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void cgroup_clear_directory(struct dentry *dir) | ||||||
|  | { | ||||||
|  | 	struct cgroup *cgrp = __d_cgrp(dir); | ||||||
|  | 
 | ||||||
|  | 	while (!list_empty(&cgrp->files)) | ||||||
|  | 		cgroup_rm_file(cgrp, NULL); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /*
 | /*
 | ||||||
|  | @ -1352,6 +1376,7 @@ static void init_cgroup_housekeeping(struct cgroup *cgrp) | ||||||
| { | { | ||||||
| 	INIT_LIST_HEAD(&cgrp->sibling); | 	INIT_LIST_HEAD(&cgrp->sibling); | ||||||
| 	INIT_LIST_HEAD(&cgrp->children); | 	INIT_LIST_HEAD(&cgrp->children); | ||||||
|  | 	INIT_LIST_HEAD(&cgrp->files); | ||||||
| 	INIT_LIST_HEAD(&cgrp->css_sets); | 	INIT_LIST_HEAD(&cgrp->css_sets); | ||||||
| 	INIT_LIST_HEAD(&cgrp->release_list); | 	INIT_LIST_HEAD(&cgrp->release_list); | ||||||
| 	INIT_LIST_HEAD(&cgrp->pidlists); | 	INIT_LIST_HEAD(&cgrp->pidlists); | ||||||
|  | @ -2619,7 +2644,9 @@ static int cgroup_add_file(struct cgroup *cgrp, struct cgroup_subsys *subsys, | ||||||
| 			   const struct cftype *cft) | 			   const struct cftype *cft) | ||||||
| { | { | ||||||
| 	struct dentry *dir = cgrp->dentry; | 	struct dentry *dir = cgrp->dentry; | ||||||
|  | 	struct cgroup *parent = __d_cgrp(dir); | ||||||
| 	struct dentry *dentry; | 	struct dentry *dentry; | ||||||
|  | 	struct cfent *cfe; | ||||||
| 	int error; | 	int error; | ||||||
| 	umode_t mode; | 	umode_t mode; | ||||||
| 	char name[MAX_CGROUP_TYPE_NAMELEN + MAX_CFTYPE_NAME + 2] = { 0 }; | 	char name[MAX_CGROUP_TYPE_NAMELEN + MAX_CFTYPE_NAME + 2] = { 0 }; | ||||||
|  | @ -2635,17 +2662,31 @@ static int cgroup_add_file(struct cgroup *cgrp, struct cgroup_subsys *subsys, | ||||||
| 		strcat(name, "."); | 		strcat(name, "."); | ||||||
| 	} | 	} | ||||||
| 	strcat(name, cft->name); | 	strcat(name, cft->name); | ||||||
|  | 
 | ||||||
| 	BUG_ON(!mutex_is_locked(&dir->d_inode->i_mutex)); | 	BUG_ON(!mutex_is_locked(&dir->d_inode->i_mutex)); | ||||||
|  | 
 | ||||||
|  | 	cfe = kzalloc(sizeof(*cfe), GFP_KERNEL); | ||||||
|  | 	if (!cfe) | ||||||
|  | 		return -ENOMEM; | ||||||
|  | 
 | ||||||
| 	dentry = lookup_one_len(name, dir, strlen(name)); | 	dentry = lookup_one_len(name, dir, strlen(name)); | ||||||
| 	if (!IS_ERR(dentry)) { | 	if (IS_ERR(dentry)) { | ||||||
| 		mode = cgroup_file_mode(cft); |  | ||||||
| 		error = cgroup_create_file(dentry, mode | S_IFREG, |  | ||||||
| 						cgrp->root->sb); |  | ||||||
| 		if (!error) |  | ||||||
| 			dentry->d_fsdata = (void *)cft; |  | ||||||
| 		dput(dentry); |  | ||||||
| 	} else |  | ||||||
| 		error = PTR_ERR(dentry); | 		error = PTR_ERR(dentry); | ||||||
|  | 		goto out; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	mode = cgroup_file_mode(cft); | ||||||
|  | 	error = cgroup_create_file(dentry, mode | S_IFREG, cgrp->root->sb); | ||||||
|  | 	if (!error) { | ||||||
|  | 		cfe->type = (void *)cft; | ||||||
|  | 		cfe->dentry = dentry; | ||||||
|  | 		dentry->d_fsdata = cfe; | ||||||
|  | 		list_add_tail(&cfe->node, &parent->files); | ||||||
|  | 		cfe = NULL; | ||||||
|  | 	} | ||||||
|  | 	dput(dentry); | ||||||
|  | out: | ||||||
|  | 	kfree(cfe); | ||||||
| 	return error; | 	return error; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Tejun Heo
				Tejun Heo