@@ -93,6 +93,8 @@ enum {
CFTYPE_NO_PREFIX = (1 << 3), /* (DON'T USE FOR NEW FILES) no subsys prefix */
CFTYPE_WORLD_WRITABLE = (1 << 4), /* (DON'T USE FOR NEW FILES) S_IWUGO */
+ CFTYPE_SHARES_FILE = (1 << 5), /* shares file w/ other cfts */
+
/* internal flags, do not use outside cgroup core proper */
__CFTYPE_ONLY_ON_DFL = (1 << 16), /* only on default hierarchy */
__CFTYPE_NOT_ON_DFL = (1 << 17), /* not on default hierarchy */
@@ -528,6 +530,13 @@ struct cftype {
*/
struct cgroup_subsys *ss; /* NULL for cgroup core files */
struct list_head node; /* anchored at ss->cfts */
+
+ /*
+ * List of cftypes that are sharing the same file. It allows the hook
+ * functions of the cftypes in the list to be called together.
+ */
+ struct list_head share_node;
+
struct kernfs_ops *kf_ops;
int (*open)(struct kernfs_open_file *of);
@@ -1549,11 +1549,15 @@ struct cgroup *cgroup_kn_lock_live(struct kernfs_node *kn, bool drain_offline)
return NULL;
}
-static void cgroup_rm_file(struct cgroup *cgrp, const struct cftype *cft)
+static void cgroup_rm_file(struct cgroup *cgrp, struct cftype *cft)
{
char name[CGROUP_FILE_NAME_MAX];
+ struct kernfs_node *kn = kernfs_find_and_get(cgrp->kn,
+ cgroup_file_name(cgrp, cft, name));
+ struct cftype *cfts = kn->priv;
lockdep_assert_held(&cgroup_mutex);
+ kernfs_put(kn);
if (cft->file_offset) {
struct cgroup_subsys_state *css = cgroup_css(cgrp, cft->ss);
@@ -1566,7 +1570,19 @@ static void cgroup_rm_file(struct cgroup *cgrp, const struct cftype *cft)
del_timer_sync(&cfile->notify_timer);
}
- kernfs_remove_by_name(cgrp->kn, cgroup_file_name(cgrp, cft, name));
+ /* Delete the file only if it's used by one cftype */
+ if (list_empty(&cft->share_node) || atomic_read(&kn->count) == 1) {
+ kernfs_remove(kn);
+ } else {
+ /*
+ * Update the "priv" pointer of the kernfs_node if the cftype
+ * that first created the file is removed.
+ */
+ if (cft == cfts)
+ kn->priv = list_next_entry(cft, share_node);
+
+ kernfs_put(kn);
+ }
}
/**
@@ -3437,6 +3453,7 @@ static int cgroup_file_open(struct kernfs_open_file *of)
{
struct cftype *cft = of->kn->priv;
+
if (cft->open)
return cft->open(of);
return 0;
@@ -3585,6 +3602,22 @@ static int cgroup_add_file(struct cgroup_subsys_state *css, struct cgroup *cgrp,
#ifdef CONFIG_DEBUG_LOCK_ALLOC
key = &cft->lockdep_key;
#endif
+
+ if (cft->flags & CFTYPE_SHARES_FILE) {
+ /* kn->count keeps track of how many cftypes share kn */
+ kn = kernfs_find_and_get(cgrp->kn,
+ cgroup_file_name(cgrp, cft, name));
+ if (kn) {
+ struct cftype *cfts = kn->priv;
+
+ if (cfts->flags & CFTYPE_SHARES_FILE)
+ goto out_set_cfile;
+ else
+ kernfs_put(kn);
+
+ }
+ }
+
kn = __kernfs_create_file(cgrp->kn, cgroup_file_name(cgrp, cft, name),
cgroup_file_mode(cft),
GLOBAL_ROOT_UID, GLOBAL_ROOT_GID,
@@ -3599,6 +3632,7 @@ static int cgroup_add_file(struct cgroup_subsys_state *css, struct cgroup *cgrp,
return ret;
}
+out_set_cfile:
if (cft->file_offset) {
struct cgroup_file *cfile = (void *)css + cft->file_offset;
@@ -3696,11 +3730,46 @@ static void cgroup_exit_cftypes(struct cftype *cfts)
cft->kf_ops = NULL;
cft->ss = NULL;
+ list_del(&cft->share_node);
+
/* revert flags set by cgroup core while adding @cfts */
cft->flags &= ~(__CFTYPE_ONLY_ON_DFL | __CFTYPE_NOT_ON_DFL);
}
}
+/*
+ * Link a cftype that wants to share a file to the list of cftypes that are
+ * using it.
+ *
+ * The conditions for a cftype to be put in an existing list of cftypes and
+ * thus start to share a file are:
+ * - to have the flag CFTYPE_SHARES_FILE set;
+ * - to have all flags coincide with the flags of the other cftypes in the
+ * list;
+ * - to not have a seq_start hook: there is no consistent way to show
+ * portions of a file once multiple cftypes are attached to it, and thus
+ * more than one seq_show() is invoked.
+ *
+ * Once two or more cftypes are linked together, the file only points
+ * to the first of them.
+ */
+static void cgroup_link_cftype(struct cgroup_subsys *ss, struct cftype *cft)
+{
+ struct cftype *cfts;
+
+ list_for_each_entry(cfts, &ss->cfts, node) {
+ struct cftype *c;
+
+ for (c = cfts; c->name[0] != '\0'; c++)
+ if (c != cft && !(c->flags ^ cft->flags) &&
+ !(c->seq_start || cft->seq_start) &&
+ !strcmp(c->name, cft->name)) {
+ list_add(&cft->share_node, &c->share_node);
+ return;
+ }
+ }
+}
+
static int cgroup_init_cftypes(struct cgroup_subsys *ss, struct cftype *cfts)
{
struct cftype *cft;
@@ -3730,6 +3799,11 @@ static int cgroup_init_cftypes(struct cgroup_subsys *ss, struct cftype *cfts)
cft->kf_ops = kf_ops;
cft->ss = ss;
+
+ INIT_LIST_HEAD(&cft->share_node);
+
+ if (cft->flags & CFTYPE_SHARES_FILE)
+ cgroup_link_cftype(ss, cft);
}
return 0;