Linux Test Project
 help / color / mirror / Atom feed
From: Cyril Hrubis <chrubis@suse.cz>
To: ltp@lists.linux.it
Subject: [LTP] [PATCH v4 2/6] Add new CGroups APIs
Date: Thu, 29 Apr 2021 17:13:40 +0200	[thread overview]
Message-ID: <YIrNJBgt6hjwsaUj@yuki> (raw)
In-Reply-To: <20210428142719.8065-3-rpalethorpe@suse.com>

Hi!
I've did a careful review of the code and found a few minor things I've
pointed out below. Overall the code looks really great at this point.

>  
> -void tst_cgroup_umount(const char *cgroup_dir)
> +static void cgroup_group_init(struct tst_cgroup_group *cg,
> +			      const char *group_name)
>  {
> -	char *cgroup_new_dir;
> +	memset(cg, 0, sizeof(*cg));
> +	cg->group_name = group_name;

Here we store a pointer to a string that was passed by the caller, which
will cause invalid access if user passed a string on a stack.

I guess that the easiest fix would be to add an 32 char array to the
tst_cgroup_group and strcpy() the string there. We would have to check
that the string is short enough, but that should be it.

> +}
> +
> +static void cgroup_group_add_dir(struct tst_cgroup_group *cg,
> +				 struct cgroup_dir *dir)
> +{
> +	const struct cgroup_ctrl *ctrl;
> +	int i;
> +
> +	if (dir->dir_root->ver == TST_CGROUP_V2)
> +		cg->dirs_by_ctrl[0] = dir;
>  
> -	cgroup_new_dir = tst_cgroup_get_path(cgroup_dir);
> -	tst_cgroupN_umount(cgroup_dir, cgroup_new_dir);
> -	tst_cgroup_del_path(cgroup_dir);
> +	for_each_ctrl(ctrl) {
> +		if (has_ctrl(dir->ctrl_field, ctrl))
> +			cg->dirs_by_ctrl[ctrl->ctrl_indx] = dir;
> +	}
> +
> +	for (i = 0; cg->dirs[i]; i++);
> +	cg->dirs[i] = dir;
>  }
>  
> -void tst_cgroup_set_knob(const char *cgroup_dir, const char *knob, long value)
> +struct tst_cgroup_group *
> +tst_cgroup_group_mk(const struct tst_cgroup_group *parent,
> +		    const char *group_name)
>  {
> -	char *cgroup_new_dir;
> -	char knob_path[PATH_MAX];
> +	struct tst_cgroup_group *cg;
> +	struct cgroup_dir *const *dir;
> +	struct cgroup_dir *new_dir;
> +
> +	cg = SAFE_MALLOC(sizeof(*cg));
> +	cgroup_group_init(cg, group_name);
>  
> -	cgroup_new_dir = tst_cgroup_get_path(cgroup_dir);
> -	sprintf(knob_path, "%s/%s", cgroup_new_dir, knob);
> -	SAFE_FILE_PRINTF(knob_path, "%ld", value);
> +	for_each_dir(parent, 0, dir) {
> +		new_dir = SAFE_MALLOC(sizeof(*new_dir));
> +		cgroup_dir_mk(*dir, group_name, new_dir);
> +		cgroup_group_add_dir(cg, new_dir);
> +	}
> +
> +	return cg;
>  }
>  
> -void tst_cgroup_move_current(const char *cgroup_dir)
> +struct tst_cgroup_group *tst_cgroup_group_rm(struct tst_cgroup_group *cg)
>  {
> -	if (tst_cg_ver & TST_CGROUP_V1)
> -		tst_cgroup_set_knob(cgroup_dir, "tasks", getpid());
> +	struct cgroup_dir **dir;
> +
> +	for_each_dir(cg, 0, dir) {
> +		close((*dir)->dir_fd);
> +		SAFE_UNLINKAT((*dir)->dir_parent->dir_fd,
> +			      (*dir)->dir_name,
> +			      AT_REMOVEDIR);
> +		free(*dir);
> +	}
>  
> -	if (tst_cg_ver & TST_CGROUP_V2)
> -		tst_cgroup_set_knob(cgroup_dir, "cgroup.procs", getpid());
> +	free(cg);
> +	return NULL;
>  }
>  
> -void tst_cgroup_mem_set_maxbytes(const char *cgroup_dir, long memsz)
> +/* Traverse the item tree to find an item. */

This is no longer called item. Maybe we should just remove this comment.

> +static const struct cgroup_file *cgroup_file_find(const char *file,
> +						  const int lineno,
> +						  const char *file_name)
>  {
> -	if (tst_cg_ver & TST_CGROUP_V1)
> -		tst_cgroup_set_knob(cgroup_dir, "memory.limit_in_bytes", memsz);
> +	const struct cgroup_file *cfile;
> +	const struct cgroup_ctrl *ctrl;
> +	char ctrl_name[32];
> +	const char *mem_name;
> +	size_t len = MIN(sizeof(ctrl_name) - 1, strcspn(file_name, "."));

Aren't all the v2 names in the format controller.knob? Can't we just
TBROK if there is '.' in the file_name?

> +	memcpy(ctrl_name, file_name, len);
> +	ctrl_name[len] = '\0';
> +
> +	ctrl = cgroup_find_ctrl(ctrl_name);
> +
> +	if (!ctrl) {
> +		tst_brk_(file, lineno, TBROK,
> +			 "Did not find controller '%s'\n", ctrl_name);
> +		return NULL;
> +	}
> +
> +	file_name += len + 1;
> +
> +	for (cfile = ctrl->files; cfile->file_name; cfile++) {
> +		mem_name = cfile->file_name + len + 1;
> +
> +		if (!strcmp(file_name, mem_name))
> +			break;

Can't we just compare the whole strings here? The prefix is constant and
the same for both, so we may as well avoid this string trickery and just
compare the file_name against cfile->file_name.

> +	}
> +
> +	if (!cfile->file_name) {
> +		tst_brk_(file, lineno, TBROK,
> +			 "Did not find '%s' in '%s'\n",
> +			 file_name, ctrl->ctrl_name);
> +		return NULL;
> +	}
>  
> -	if (tst_cg_ver & TST_CGROUP_V2)
> -		tst_cgroup_set_knob(cgroup_dir, "memory.max", memsz);
> +	return cfile;
>  }
>  
> -int tst_cgroup_mem_swapacct_enabled(const char *cgroup_dir)
> +enum tst_cgroup_ver tst_cgroup_ver(const char *file, const int lineno,
> +				    const struct tst_cgroup_group *cg,
> +				    const char *ctrl_name)
>  {
> -	char *cgroup_new_dir;
> -	char knob_path[PATH_MAX];
> +	const struct cgroup_ctrl *const ctrl = cgroup_find_ctrl(ctrl_name);
> +	const struct cgroup_dir *dir;
> +
> +	if (!strcmp(ctrl_name, "cgroup")) {
> +		tst_brk_(file, lineno,
> +			 TBROK,
> +			 "cgroup may be present on both V1 and V2 hierarchies");
> +		return 0;
> +	}
>  
> -	cgroup_new_dir = tst_cgroup_get_path(cgroup_dir);
> +	if (!ctrl) {
> +		tst_brk_(file, lineno,
> +			 TBROK, "Unknown controller '%s'", ctrl_name);
> +		return 0;
> +	}
>  
> -	if (tst_cg_ver & TST_CGROUP_V1) {
> -		sprintf(knob_path, "%s/%s",
> -				cgroup_new_dir, "/memory.memsw.limit_in_bytes");
> +	dir = cg->dirs_by_ctrl[ctrl->ctrl_indx];
>  
> -		if ((access(knob_path, F_OK) == -1)) {
> -			if (errno == ENOENT)
> -				tst_res(TCONF, "memcg swap accounting is disabled");
> -			else
> -				tst_brk(TBROK | TERRNO, "failed to access %s", knob_path);
> -		} else {
> -			return 1;
> -		}
> +	if (!dir) {
> +		tst_brk_(file, lineno,
> +			 TBROK, "%s controller not attached to CGroup %s",
> +			 ctrl_name, cg->group_name);
> +		return 0;
>  	}
>  
> -	if (tst_cg_ver & TST_CGROUP_V2) {
> -		sprintf(knob_path, "%s/%s",
> -				cgroup_new_dir, "/memory.swap.max");
> +	return dir->dir_root->ver;
> +}
>  
> -		if ((access(knob_path, F_OK) == -1)) {
> -			if (errno == ENOENT)
> -				tst_res(TCONF, "memcg swap accounting is disabled");
> -			else
> -				tst_brk(TBROK | TERRNO, "failed to access %s", knob_path);
> -		} else {
> -			return 1;
> -		}
> +static const char *cgroup_file_alias(const struct cgroup_file *const cfile,
> +				     const struct cgroup_dir *const dir)
> +{
> +	if (dir->dir_root->ver != TST_CGROUP_V1)
> +		return cfile->file_name;
> +
> +	if (cfile->ctrl_indx == CTRL_CPUSET &&
> +	    dir->dir_root->no_cpuset_prefix &&
> +	    cfile->file_name_v1) {
> +		return strchr(cfile->file_name_v1, '.') + 1;
>  	}

This is quite ugly but I do not think that I can come up with better
solutution.

> -	return 0;
> +	return cfile->file_name_v1;
>  }
>  
> -void tst_cgroup_mem_set_maxswap(const char *cgroup_dir, long memsz)
> +int safe_cgroup_has(const char *file, const int lineno,
> +		    const struct tst_cgroup_group *cg, const char *file_name)
>  {
> -	if (tst_cg_ver & TST_CGROUP_V1)
> -		tst_cgroup_set_knob(cgroup_dir, "memory.memsw.limit_in_bytes", memsz);
> +	const struct cgroup_file *const cfile =
> +		cgroup_file_find(file, lineno, file_name);
> +	struct cgroup_dir *const *dir;
> +	const char *alias;
>  
> -	if (tst_cg_ver & TST_CGROUP_V2)
> -		tst_cgroup_set_knob(cgroup_dir, "memory.swap.max", memsz);
> +	if (!cfile)
> +		return 0;
> +
> +	for_each_dir(cg, cfile->ctrl_indx, dir) {
> +		if (!(alias = cgroup_file_alias(cfile, *dir)))
> +		    continue;
> +
> +		if (!faccessat((*dir)->dir_fd, file_name, F_OK, 0))
> +			return 1;
> +
> +		if (errno == ENOENT)
> +			continue;
> +
> +		tst_brk_(file, lineno, TBROK | TERRNO,
> +			 "faccessat(%d<%s>, %s, F_OK, 0)",
> +			 (*dir)->dir_fd, tst_decode_fd((*dir)->dir_fd), alias);
> +	}
> +
> +	return 0;
>  }
>  
> -void tst_cgroup_cpuset_read_files(const char *cgroup_dir, const char *filename,
> -	char *retbuf, size_t retbuf_sz)
> +static struct tst_cgroup_group *cgroup_group_from_roots(size_t tree_off)
>  {
> -	int fd;
> -	char *cgroup_new_dir;
> -	char knob_path[PATH_MAX];
> +	struct cgroup_root *root;
> +	struct cgroup_dir *dir;
> +	struct tst_cgroup_group *cg;
>  
> -	cgroup_new_dir = tst_cgroup_get_path(cgroup_dir);
> +	cg = tst_alloc(sizeof(*cg));
> +	cgroup_group_init(cg, NULL);
>  
> -	/*
> -	 * try either '/dev/cpuset/XXXX' or '/dev/cpuset/cpuset.XXXX'
> -	 * please see Documentation/cgroups/cpusets.txt from kernel src
> -	 * for details
> -	 */
> -	sprintf(knob_path, "%s/%s", cgroup_new_dir, filename);
> -	fd = open(knob_path, O_RDONLY);
> -	if (fd == -1) {
> -		if (errno == ENOENT) {
> -			sprintf(knob_path, "%s/cpuset.%s",
> -					cgroup_new_dir, filename);
> -			fd = SAFE_OPEN(knob_path, O_RDONLY);
> -		} else
> -			tst_brk(TBROK | TERRNO, "open %s", knob_path);
> +	for_each_root(root) {
> +		dir = (typeof(dir))(((char *)root) + tree_off);
> +
> +		if (dir->ctrl_field)
> +			cgroup_group_add_dir(cg, dir);
>  	}
>  
> -	memset(retbuf, 0, retbuf_sz);
> -	if (read(fd, retbuf, retbuf_sz) < 0)
> -		tst_brk(TBROK | TERRNO, "read %s", knob_path);
> +	if (cg->dirs[0]) {
> +		cg->group_name = cg->dirs[0]->dir_name;
> +		return cg;
> +	}
> +
> +	tst_brk(TBROK,
> +		"No CGroups found; maybe you forgot to call tst_cgroup_require?");
>  
> -	close(fd);
> +	return cg;
>  }
>  
> -void tst_cgroup_cpuset_write_files(const char *cgroup_dir, const char *filename, const char *buf)
> +const struct tst_cgroup_group *tst_cgroup_get_test_group(void)
>  {
> -	int fd;
> -	char *cgroup_new_dir;
> -	char knob_path[PATH_MAX];
> +	return cgroup_group_from_roots(offsetof(struct cgroup_root, test_dir));
> +}
>  
> -	cgroup_new_dir = tst_cgroup_get_path(cgroup_dir);
> +const struct tst_cgroup_group *tst_cgroup_get_drain_group(void)
> +{
> +	return cgroup_group_from_roots(offsetof(struct cgroup_root, drain_dir));
> +}
>  
> -	/*
> -	 * try either '/dev/cpuset/XXXX' or '/dev/cpuset/cpuset.XXXX'
> -	 * please see Documentation/cgroups/cpusets.txt from kernel src
> -	 * for details
> -	 */
> -	sprintf(knob_path, "%s/%s", cgroup_new_dir, filename);
> -	fd = open(knob_path, O_WRONLY);
> -	if (fd == -1) {
> -		if (errno == ENOENT) {
> -			sprintf(knob_path, "%s/cpuset.%s", cgroup_new_dir, filename);
> -			fd = SAFE_OPEN(knob_path, O_WRONLY);
> -		} else
> -			tst_brk(TBROK | TERRNO, "open %s", knob_path);
> +ssize_t safe_cgroup_read(const char *file, const int lineno,
> +			 const struct tst_cgroup_group *cg, const char *file_name,
> +			 char *out, size_t len)
> +{
> +	const struct cgroup_file *const cfile =
> +		cgroup_file_find(file, lineno, file_name);
> +	struct cgroup_dir *const *dir;
> +	const char *alias;
> +	size_t prev_len = 0;
> +	char prev_buf[BUFSIZ];
> +
> +	for_each_dir(cg, cfile->ctrl_indx, dir) {
> +		if (!(alias = cgroup_file_alias(cfile, *dir)))
> +			continue;
> +
> +		if (prev_len)
> +			memcpy(prev_buf, out, prev_len);
> +
> +		TEST(safe_file_readat(file, lineno,
> +				      (*dir)->dir_fd, alias, out, len));
> +		if (TST_RET < 0)
> +			continue;
> +
> +		if (prev_len && memcmp(out, prev_buf, prev_len)) {
> +			tst_brk_(file, lineno, TBROK,
> +				 "%s has different value across roots",
> +				 file_name);
> +			break;
> +		}
> +
> +		prev_len = MIN(sizeof(prev_buf), (size_t)TST_RET);
>  	}
>  
> -	SAFE_WRITE(1, fd, buf, strlen(buf));
> +	out[MAX(TST_RET, 0)] = '\0';
>  
> -	close(fd);
> +	return TST_RET;
> +}
> +
> +void safe_cgroup_printf(const char *file, const int lineno,
> +			const struct tst_cgroup_group *cg,
> +			const char *file_name,
> +			const char *fmt, ...)
> +{
> +	const struct cgroup_file *const cfile =
> +		cgroup_file_find(file, lineno, file_name);
> +	struct cgroup_dir *const *dir;
> +	const char *alias;
> +	va_list va;
> +
> +	for_each_dir(cg, cfile->ctrl_indx, dir) {

So this is a loop because we have to write to all instances of the
generic files such as "tasks" to move the the process consistently
across the hierarchies, right?

Maybe this deserves a comment at the for_each_dir() macro definition.

> +		if (!(alias = cgroup_file_alias(cfile, *dir)))
> +		    continue;
> +
> +		va_start(va, fmt);
> +		safe_file_vprintfat(file, lineno,
> +				    (*dir)->dir_fd, alias, fmt, va);
> +		va_end(va);
> +	}
> +}
> +
> +void safe_cgroup_scanf(const char *file, const int lineno,
> +		       const struct tst_cgroup_group *cg, const char *file_name,
> +		       const char *fmt, ...)
> +{
> +	va_list va;
> +	char buf[BUFSIZ];
> +	ssize_t len = safe_cgroup_read(file, lineno,
> +				       cg, file_name, buf, sizeof(buf));
> +
> +	if (len < 1)
> +		return;
> +
> +	va_start(va, fmt);
> +	if (vsscanf(buf, fmt, va) < 1) {
> +		tst_brk_(file, lineno, TBROK | TERRNO,
> +			 "'%s': vsscanf('%s', '%s', ...)", file_name, buf, fmt);
> +	}
> +	va_end(va);

This does not allow user to handle if we convert less than requested
number of conversions including zero. Either we have to return the number of
conversions from this function, or we have to count the conversions in
the fmt and TBROK if we got less as we do in safe_file_ops.c with
count_scanf_conversions() function.

>  }
> -- 
> 2.31.1
> 
> 
> -- 
> Mailing list info: https://lists.linux.it/listinfo/ltp

-- 
Cyril Hrubis
chrubis@suse.cz

  reply	other threads:[~2021-04-29 15:13 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-04-28 14:27 [LTP] [PATCH v4 0/6] CGroup API rewrite Richard Palethorpe
2021-04-28 14:27 ` [LTP] [PATCH v4 1/6] API: Add safe openat, printfat, readat and unlinkat Richard Palethorpe
2021-04-28 17:48   ` Petr Vorel
2021-04-28 14:27 ` [LTP] [PATCH v4 2/6] Add new CGroups APIs Richard Palethorpe
2021-04-29 15:13   ` Cyril Hrubis [this message]
2021-04-28 14:27 ` [LTP] [PATCH v4 3/6] Add new CGroups API library tests Richard Palethorpe
2021-04-28 14:27 ` [LTP] [PATCH v4 4/6] docs: Update CGroups API Richard Palethorpe
2021-04-28 14:27 ` [LTP] [PATCH v4 5/6] mem: Convert tests to new " Richard Palethorpe
2021-04-28 14:27 ` [LTP] [PATCH v4 6/6] madvise06: Convert " Richard Palethorpe

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=YIrNJBgt6hjwsaUj@yuki \
    --to=chrubis@suse.cz \
    --cc=ltp@lists.linux.it \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox