[PATCH 3/7] cgroup: introduce cgroup_parse_percentage

From: Song Liu
Date: Mon Apr 08 2019 - 17:47:13 EST


This patch introduces a helper to parse percentage string to long
integer, with user selected scale:

long cgroup_parse_percentage(char *tok, unsigned long base)

Valid tok could be integer 0 to 100, decimal 0.00 to 100.00, or "max".
A tok of "max"is same as "100".

Base is the desire output scale for input "1".

Signed-off-by: Song Liu <songliubraving@xxxxxx>
---
include/linux/cgroup.h | 1 +
kernel/cgroup/cgroup.c | 37 +++++++++++++++++++++++++++++++++++++
2 files changed, 38 insertions(+)

diff --git a/include/linux/cgroup.h b/include/linux/cgroup.h
index 81f58b4a5418..b28f8a41c970 100644
--- a/include/linux/cgroup.h
+++ b/include/linux/cgroup.h
@@ -110,6 +110,7 @@ int cgroup_add_dfl_cftypes(struct cgroup_subsys *ss, struct cftype *cfts);
int cgroup_add_legacy_cftypes(struct cgroup_subsys *ss, struct cftype *cfts);
int cgroup_rm_cftypes(struct cftype *cfts);
void cgroup_file_notify(struct cgroup_file *cfile);
+long cgroup_parse_percentage(char *tok, unsigned long base);

int task_cgroup_path(struct task_struct *task, char *buf, size_t buflen);
int cgroupstats_build(struct cgroupstats *stats, struct dentry *dentry);
diff --git a/kernel/cgroup/cgroup.c b/kernel/cgroup/cgroup.c
index b0df96132476..2e48840ff613 100644
--- a/kernel/cgroup/cgroup.c
+++ b/kernel/cgroup/cgroup.c
@@ -3967,6 +3967,43 @@ void cgroup_file_notify(struct cgroup_file *cfile)
spin_unlock_irqrestore(&cgroup_file_kn_lock, flags);
}

+/**
+ * cgroup_parse_percentage - parse percentage number
+ * @tok: input string contains the token. Valid values are "00.00" to
+ * "100.00", or "max"
+ * @base: number "1" in desired output scale
+ *
+ * Returns:
+ * @base * 100 for "max";
+ * @base * <0.00 to 100.00> for valid inputs;
+ * -EINVAL for invalid input.
+ */
+long cgroup_parse_percentage(char *tok, unsigned long base)
+{
+ unsigned long val_int, val_frag;
+
+ if (strcmp(tok, "max") == 0) {
+ return base * 100;
+ } else if (sscanf(tok, "%lu.%02lu", &val_int, &val_frag) == 2) {
+ /* xx.1 yields val_frag = 1, while it should be 10 */
+ if (val_frag < 10 && strstr(tok, ".0") == NULL)
+ val_frag *= 10;
+ goto calculate_output;
+ } else if (sscanf(tok, "%lu", &val_int) == 1) {
+ val_frag = 0;
+ goto calculate_output;
+ } else {
+ return -EINVAL;
+ }
+
+calculate_output:
+ if (val_int > 100 || (val_int == 100 && val_frag > 0))
+ return -EINVAL;
+
+ /* round up val_frag by 0.5, to avoid repeated rounding down */
+ return (val_int * base) + div_u64((val_frag * 10 + 5) * base, 1000);
+}
+
/**
* css_next_child - find the next child of a given css
* @pos: the current position (%NULL to initiate traversal)
--
2.17.1