|  | /* | 
|  | * cgroup_freezer.c -  control group freezer subsystem | 
|  | * | 
|  | * Copyright IBM Corporation, 2007 | 
|  | * | 
|  | * Author : Cedric Le Goater <clg@fr.ibm.com> | 
|  | * | 
|  | * This program is free software; you can redistribute it and/or modify it | 
|  | * under the terms of version 2.1 of the GNU Lesser General Public License | 
|  | * as published by the Free Software Foundation. | 
|  | * | 
|  | * This program is distributed in the hope that it would be useful, but | 
|  | * WITHOUT ANY WARRANTY; without even the implied warranty of | 
|  | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | 
|  | */ | 
|  |  | 
|  | #include <linux/export.h> | 
|  | #include <linux/slab.h> | 
|  | #include <linux/cgroup.h> | 
|  | #include <linux/fs.h> | 
|  | #include <linux/uaccess.h> | 
|  | #include <linux/freezer.h> | 
|  | #include <linux/seq_file.h> | 
|  |  | 
|  | /* | 
|  | * A cgroup is freezing if any FREEZING flags are set.  FREEZING_SELF is | 
|  | * set if "FROZEN" is written to freezer.state cgroupfs file, and cleared | 
|  | * for "THAWED".  FREEZING_PARENT is set if the parent freezer is FREEZING | 
|  | * for whatever reason.  IOW, a cgroup has FREEZING_PARENT set if one of | 
|  | * its ancestors has FREEZING_SELF set. | 
|  | */ | 
|  | enum freezer_state_flags { | 
|  | CGROUP_FREEZER_ONLINE	= (1 << 0), /* freezer is fully online */ | 
|  | CGROUP_FREEZING_SELF	= (1 << 1), /* this freezer is freezing */ | 
|  | CGROUP_FREEZING_PARENT	= (1 << 2), /* the parent freezer is freezing */ | 
|  | CGROUP_FROZEN		= (1 << 3), /* this and its descendants frozen */ | 
|  |  | 
|  | /* mask for all FREEZING flags */ | 
|  | CGROUP_FREEZING		= CGROUP_FREEZING_SELF | CGROUP_FREEZING_PARENT, | 
|  | }; | 
|  |  | 
|  | struct freezer { | 
|  | struct cgroup_subsys_state	css; | 
|  | unsigned int			state; | 
|  | spinlock_t			lock; | 
|  | }; | 
|  |  | 
|  | static inline struct freezer *cgroup_freezer(struct cgroup *cgroup) | 
|  | { | 
|  | return container_of(cgroup_subsys_state(cgroup, freezer_subsys_id), | 
|  | struct freezer, css); | 
|  | } | 
|  |  | 
|  | static inline struct freezer *task_freezer(struct task_struct *task) | 
|  | { | 
|  | return container_of(task_subsys_state(task, freezer_subsys_id), | 
|  | struct freezer, css); | 
|  | } | 
|  |  | 
|  | static struct freezer *parent_freezer(struct freezer *freezer) | 
|  | { | 
|  | struct cgroup *pcg = freezer->css.cgroup->parent; | 
|  |  | 
|  | if (pcg) | 
|  | return cgroup_freezer(pcg); | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | bool cgroup_freezing(struct task_struct *task) | 
|  | { | 
|  | bool ret; | 
|  |  | 
|  | rcu_read_lock(); | 
|  | ret = task_freezer(task)->state & CGROUP_FREEZING; | 
|  | rcu_read_unlock(); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * cgroups_write_string() limits the size of freezer state strings to | 
|  | * CGROUP_LOCAL_BUFFER_SIZE | 
|  | */ | 
|  | static const char *freezer_state_strs(unsigned int state) | 
|  | { | 
|  | if (state & CGROUP_FROZEN) | 
|  | return "FROZEN"; | 
|  | if (state & CGROUP_FREEZING) | 
|  | return "FREEZING"; | 
|  | return "THAWED"; | 
|  | }; | 
|  |  | 
|  | struct cgroup_subsys freezer_subsys; | 
|  |  | 
|  | static struct cgroup_subsys_state *freezer_css_alloc(struct cgroup *cgroup) | 
|  | { | 
|  | struct freezer *freezer; | 
|  |  | 
|  | freezer = kzalloc(sizeof(struct freezer), GFP_KERNEL); | 
|  | if (!freezer) | 
|  | return ERR_PTR(-ENOMEM); | 
|  |  | 
|  | spin_lock_init(&freezer->lock); | 
|  | return &freezer->css; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * freezer_css_online - commit creation of a freezer cgroup | 
|  | * @cgroup: cgroup being created | 
|  | * | 
|  | * We're committing to creation of @cgroup.  Mark it online and inherit | 
|  | * parent's freezing state while holding both parent's and our | 
|  | * freezer->lock. | 
|  | */ | 
|  | static int freezer_css_online(struct cgroup *cgroup) | 
|  | { | 
|  | struct freezer *freezer = cgroup_freezer(cgroup); | 
|  | struct freezer *parent = parent_freezer(freezer); | 
|  |  | 
|  | /* | 
|  | * The following double locking and freezing state inheritance | 
|  | * guarantee that @cgroup can never escape ancestors' freezing | 
|  | * states.  See cgroup_for_each_descendant_pre() for details. | 
|  | */ | 
|  | if (parent) | 
|  | spin_lock_irq(&parent->lock); | 
|  | spin_lock_nested(&freezer->lock, SINGLE_DEPTH_NESTING); | 
|  |  | 
|  | freezer->state |= CGROUP_FREEZER_ONLINE; | 
|  |  | 
|  | if (parent && (parent->state & CGROUP_FREEZING)) { | 
|  | freezer->state |= CGROUP_FREEZING_PARENT | CGROUP_FROZEN; | 
|  | atomic_inc(&system_freezing_cnt); | 
|  | } | 
|  |  | 
|  | spin_unlock(&freezer->lock); | 
|  | if (parent) | 
|  | spin_unlock_irq(&parent->lock); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * freezer_css_offline - initiate destruction of @cgroup | 
|  | * @cgroup: cgroup being destroyed | 
|  | * | 
|  | * @cgroup is going away.  Mark it dead and decrement system_freezing_count | 
|  | * if it was holding one. | 
|  | */ | 
|  | static void freezer_css_offline(struct cgroup *cgroup) | 
|  | { | 
|  | struct freezer *freezer = cgroup_freezer(cgroup); | 
|  |  | 
|  | spin_lock_irq(&freezer->lock); | 
|  |  | 
|  | if (freezer->state & CGROUP_FREEZING) | 
|  | atomic_dec(&system_freezing_cnt); | 
|  |  | 
|  | freezer->state = 0; | 
|  |  | 
|  | spin_unlock_irq(&freezer->lock); | 
|  | } | 
|  |  | 
|  | static void freezer_css_free(struct cgroup *cgroup) | 
|  | { | 
|  | kfree(cgroup_freezer(cgroup)); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Tasks can be migrated into a different freezer anytime regardless of its | 
|  | * current state.  freezer_attach() is responsible for making new tasks | 
|  | * conform to the current state. | 
|  | * | 
|  | * Freezer state changes and task migration are synchronized via | 
|  | * @freezer->lock.  freezer_attach() makes the new tasks conform to the | 
|  | * current state and all following state changes can see the new tasks. | 
|  | */ | 
|  | static void freezer_attach(struct cgroup *new_cgrp, struct cgroup_taskset *tset) | 
|  | { | 
|  | struct freezer *freezer = cgroup_freezer(new_cgrp); | 
|  | struct task_struct *task; | 
|  | bool clear_frozen = false; | 
|  |  | 
|  | spin_lock_irq(&freezer->lock); | 
|  |  | 
|  | /* | 
|  | * Make the new tasks conform to the current state of @new_cgrp. | 
|  | * For simplicity, when migrating any task to a FROZEN cgroup, we | 
|  | * revert it to FREEZING and let update_if_frozen() determine the | 
|  | * correct state later. | 
|  | * | 
|  | * Tasks in @tset are on @new_cgrp but may not conform to its | 
|  | * current state before executing the following - !frozen tasks may | 
|  | * be visible in a FROZEN cgroup and frozen tasks in a THAWED one. | 
|  | */ | 
|  | cgroup_taskset_for_each(task, new_cgrp, tset) { | 
|  | if (!(freezer->state & CGROUP_FREEZING)) { | 
|  | __thaw_task(task); | 
|  | } else { | 
|  | freeze_task(task); | 
|  | freezer->state &= ~CGROUP_FROZEN; | 
|  | clear_frozen = true; | 
|  | } | 
|  | } | 
|  |  | 
|  | spin_unlock_irq(&freezer->lock); | 
|  |  | 
|  | /* | 
|  | * Propagate FROZEN clearing upwards.  We may race with | 
|  | * update_if_frozen(), but as long as both work bottom-up, either | 
|  | * update_if_frozen() sees child's FROZEN cleared or we clear the | 
|  | * parent's FROZEN later.  No parent w/ !FROZEN children can be | 
|  | * left FROZEN. | 
|  | */ | 
|  | while (clear_frozen && (freezer = parent_freezer(freezer))) { | 
|  | spin_lock_irq(&freezer->lock); | 
|  | freezer->state &= ~CGROUP_FROZEN; | 
|  | clear_frozen = freezer->state & CGROUP_FREEZING; | 
|  | spin_unlock_irq(&freezer->lock); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void freezer_fork(struct task_struct *task) | 
|  | { | 
|  | struct freezer *freezer; | 
|  |  | 
|  | rcu_read_lock(); | 
|  | freezer = task_freezer(task); | 
|  |  | 
|  | /* | 
|  | * The root cgroup is non-freezable, so we can skip the | 
|  | * following check. | 
|  | */ | 
|  | if (!freezer->css.cgroup->parent) | 
|  | goto out; | 
|  |  | 
|  | spin_lock_irq(&freezer->lock); | 
|  | if (freezer->state & CGROUP_FREEZING) | 
|  | freeze_task(task); | 
|  | spin_unlock_irq(&freezer->lock); | 
|  | out: | 
|  | rcu_read_unlock(); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * update_if_frozen - update whether a cgroup finished freezing | 
|  | * @cgroup: cgroup of interest | 
|  | * | 
|  | * Once FREEZING is initiated, transition to FROZEN is lazily updated by | 
|  | * calling this function.  If the current state is FREEZING but not FROZEN, | 
|  | * this function checks whether all tasks of this cgroup and the descendant | 
|  | * cgroups finished freezing and, if so, sets FROZEN. | 
|  | * | 
|  | * The caller is responsible for grabbing RCU read lock and calling | 
|  | * update_if_frozen() on all descendants prior to invoking this function. | 
|  | * | 
|  | * Task states and freezer state might disagree while tasks are being | 
|  | * migrated into or out of @cgroup, so we can't verify task states against | 
|  | * @freezer state here.  See freezer_attach() for details. | 
|  | */ | 
|  | static void update_if_frozen(struct cgroup *cgroup) | 
|  | { | 
|  | struct freezer *freezer = cgroup_freezer(cgroup); | 
|  | struct cgroup *pos; | 
|  | struct cgroup_iter it; | 
|  | struct task_struct *task; | 
|  |  | 
|  | WARN_ON_ONCE(!rcu_read_lock_held()); | 
|  |  | 
|  | spin_lock_irq(&freezer->lock); | 
|  |  | 
|  | if (!(freezer->state & CGROUP_FREEZING) || | 
|  | (freezer->state & CGROUP_FROZEN)) | 
|  | goto out_unlock; | 
|  |  | 
|  | /* are all (live) children frozen? */ | 
|  | cgroup_for_each_child(pos, cgroup) { | 
|  | struct freezer *child = cgroup_freezer(pos); | 
|  |  | 
|  | if ((child->state & CGROUP_FREEZER_ONLINE) && | 
|  | !(child->state & CGROUP_FROZEN)) | 
|  | goto out_unlock; | 
|  | } | 
|  |  | 
|  | /* are all tasks frozen? */ | 
|  | cgroup_iter_start(cgroup, &it); | 
|  |  | 
|  | while ((task = cgroup_iter_next(cgroup, &it))) { | 
|  | if (freezing(task)) { | 
|  | /* | 
|  | * freezer_should_skip() indicates that the task | 
|  | * should be skipped when determining freezing | 
|  | * completion.  Consider it frozen in addition to | 
|  | * the usual frozen condition. | 
|  | */ | 
|  | if (!frozen(task) && !freezer_should_skip(task)) | 
|  | goto out_iter_end; | 
|  | } | 
|  | } | 
|  |  | 
|  | freezer->state |= CGROUP_FROZEN; | 
|  | out_iter_end: | 
|  | cgroup_iter_end(cgroup, &it); | 
|  | out_unlock: | 
|  | spin_unlock_irq(&freezer->lock); | 
|  | } | 
|  |  | 
|  | static int freezer_read(struct cgroup *cgroup, struct cftype *cft, | 
|  | struct seq_file *m) | 
|  | { | 
|  | struct cgroup *pos; | 
|  |  | 
|  | rcu_read_lock(); | 
|  |  | 
|  | /* update states bottom-up */ | 
|  | cgroup_for_each_descendant_post(pos, cgroup) | 
|  | update_if_frozen(pos); | 
|  | update_if_frozen(cgroup); | 
|  |  | 
|  | rcu_read_unlock(); | 
|  |  | 
|  | seq_puts(m, freezer_state_strs(cgroup_freezer(cgroup)->state)); | 
|  | seq_putc(m, '\n'); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void freeze_cgroup(struct freezer *freezer) | 
|  | { | 
|  | struct cgroup *cgroup = freezer->css.cgroup; | 
|  | struct cgroup_iter it; | 
|  | struct task_struct *task; | 
|  |  | 
|  | cgroup_iter_start(cgroup, &it); | 
|  | while ((task = cgroup_iter_next(cgroup, &it))) | 
|  | freeze_task(task); | 
|  | cgroup_iter_end(cgroup, &it); | 
|  | } | 
|  |  | 
|  | static void unfreeze_cgroup(struct freezer *freezer) | 
|  | { | 
|  | struct cgroup *cgroup = freezer->css.cgroup; | 
|  | struct cgroup_iter it; | 
|  | struct task_struct *task; | 
|  |  | 
|  | cgroup_iter_start(cgroup, &it); | 
|  | while ((task = cgroup_iter_next(cgroup, &it))) | 
|  | __thaw_task(task); | 
|  | cgroup_iter_end(cgroup, &it); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * freezer_apply_state - apply state change to a single cgroup_freezer | 
|  | * @freezer: freezer to apply state change to | 
|  | * @freeze: whether to freeze or unfreeze | 
|  | * @state: CGROUP_FREEZING_* flag to set or clear | 
|  | * | 
|  | * Set or clear @state on @cgroup according to @freeze, and perform | 
|  | * freezing or thawing as necessary. | 
|  | */ | 
|  | static void freezer_apply_state(struct freezer *freezer, bool freeze, | 
|  | unsigned int state) | 
|  | { | 
|  | /* also synchronizes against task migration, see freezer_attach() */ | 
|  | lockdep_assert_held(&freezer->lock); | 
|  |  | 
|  | if (!(freezer->state & CGROUP_FREEZER_ONLINE)) | 
|  | return; | 
|  |  | 
|  | if (freeze) { | 
|  | if (!(freezer->state & CGROUP_FREEZING)) | 
|  | atomic_inc(&system_freezing_cnt); | 
|  | freezer->state |= state; | 
|  | freeze_cgroup(freezer); | 
|  | } else { | 
|  | bool was_freezing = freezer->state & CGROUP_FREEZING; | 
|  |  | 
|  | freezer->state &= ~state; | 
|  |  | 
|  | if (!(freezer->state & CGROUP_FREEZING)) { | 
|  | if (was_freezing) | 
|  | atomic_dec(&system_freezing_cnt); | 
|  | freezer->state &= ~CGROUP_FROZEN; | 
|  | unfreeze_cgroup(freezer); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * freezer_change_state - change the freezing state of a cgroup_freezer | 
|  | * @freezer: freezer of interest | 
|  | * @freeze: whether to freeze or thaw | 
|  | * | 
|  | * Freeze or thaw @freezer according to @freeze.  The operations are | 
|  | * recursive - all descendants of @freezer will be affected. | 
|  | */ | 
|  | static void freezer_change_state(struct freezer *freezer, bool freeze) | 
|  | { | 
|  | struct cgroup *pos; | 
|  |  | 
|  | /* update @freezer */ | 
|  | spin_lock_irq(&freezer->lock); | 
|  | freezer_apply_state(freezer, freeze, CGROUP_FREEZING_SELF); | 
|  | spin_unlock_irq(&freezer->lock); | 
|  |  | 
|  | /* | 
|  | * Update all its descendants in pre-order traversal.  Each | 
|  | * descendant will try to inherit its parent's FREEZING state as | 
|  | * CGROUP_FREEZING_PARENT. | 
|  | */ | 
|  | rcu_read_lock(); | 
|  | cgroup_for_each_descendant_pre(pos, freezer->css.cgroup) { | 
|  | struct freezer *pos_f = cgroup_freezer(pos); | 
|  | struct freezer *parent = parent_freezer(pos_f); | 
|  |  | 
|  | /* | 
|  | * Our update to @parent->state is already visible which is | 
|  | * all we need.  No need to lock @parent.  For more info on | 
|  | * synchronization, see freezer_post_create(). | 
|  | */ | 
|  | spin_lock_irq(&pos_f->lock); | 
|  | freezer_apply_state(pos_f, parent->state & CGROUP_FREEZING, | 
|  | CGROUP_FREEZING_PARENT); | 
|  | spin_unlock_irq(&pos_f->lock); | 
|  | } | 
|  | rcu_read_unlock(); | 
|  | } | 
|  |  | 
|  | static int freezer_write(struct cgroup *cgroup, struct cftype *cft, | 
|  | const char *buffer) | 
|  | { | 
|  | bool freeze; | 
|  |  | 
|  | if (strcmp(buffer, freezer_state_strs(0)) == 0) | 
|  | freeze = false; | 
|  | else if (strcmp(buffer, freezer_state_strs(CGROUP_FROZEN)) == 0) | 
|  | freeze = true; | 
|  | else | 
|  | return -EINVAL; | 
|  |  | 
|  | freezer_change_state(cgroup_freezer(cgroup), freeze); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static u64 freezer_self_freezing_read(struct cgroup *cgroup, struct cftype *cft) | 
|  | { | 
|  | struct freezer *freezer = cgroup_freezer(cgroup); | 
|  |  | 
|  | return (bool)(freezer->state & CGROUP_FREEZING_SELF); | 
|  | } | 
|  |  | 
|  | static u64 freezer_parent_freezing_read(struct cgroup *cgroup, struct cftype *cft) | 
|  | { | 
|  | struct freezer *freezer = cgroup_freezer(cgroup); | 
|  |  | 
|  | return (bool)(freezer->state & CGROUP_FREEZING_PARENT); | 
|  | } | 
|  |  | 
|  | static struct cftype files[] = { | 
|  | { | 
|  | .name = "state", | 
|  | .flags = CFTYPE_NOT_ON_ROOT, | 
|  | .read_seq_string = freezer_read, | 
|  | .write_string = freezer_write, | 
|  | }, | 
|  | { | 
|  | .name = "self_freezing", | 
|  | .flags = CFTYPE_NOT_ON_ROOT, | 
|  | .read_u64 = freezer_self_freezing_read, | 
|  | }, | 
|  | { | 
|  | .name = "parent_freezing", | 
|  | .flags = CFTYPE_NOT_ON_ROOT, | 
|  | .read_u64 = freezer_parent_freezing_read, | 
|  | }, | 
|  | { }	/* terminate */ | 
|  | }; | 
|  |  | 
|  | struct cgroup_subsys freezer_subsys = { | 
|  | .name		= "freezer", | 
|  | .css_alloc	= freezer_css_alloc, | 
|  | .css_online	= freezer_css_online, | 
|  | .css_offline	= freezer_css_offline, | 
|  | .css_free	= freezer_css_free, | 
|  | .subsys_id	= freezer_subsys_id, | 
|  | .attach		= freezer_attach, | 
|  | .fork		= freezer_fork, | 
|  | .base_cftypes	= files, | 
|  | }; |