xref: /kernel/linux/linux-6.6/lib/argv_split.c (revision 62306a36)
162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Helper function for splitting a string into an argv-like array.
462306a36Sopenharmony_ci */
562306a36Sopenharmony_ci
662306a36Sopenharmony_ci#include <linux/kernel.h>
762306a36Sopenharmony_ci#include <linux/ctype.h>
862306a36Sopenharmony_ci#include <linux/string.h>
962306a36Sopenharmony_ci#include <linux/slab.h>
1062306a36Sopenharmony_ci#include <linux/export.h>
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_cistatic int count_argc(const char *str)
1362306a36Sopenharmony_ci{
1462306a36Sopenharmony_ci	int count = 0;
1562306a36Sopenharmony_ci	bool was_space;
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci	for (was_space = true; *str; str++) {
1862306a36Sopenharmony_ci		if (isspace(*str)) {
1962306a36Sopenharmony_ci			was_space = true;
2062306a36Sopenharmony_ci		} else if (was_space) {
2162306a36Sopenharmony_ci			was_space = false;
2262306a36Sopenharmony_ci			count++;
2362306a36Sopenharmony_ci		}
2462306a36Sopenharmony_ci	}
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci	return count;
2762306a36Sopenharmony_ci}
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci/**
3062306a36Sopenharmony_ci * argv_free - free an argv
3162306a36Sopenharmony_ci * @argv: the argument vector to be freed
3262306a36Sopenharmony_ci *
3362306a36Sopenharmony_ci * Frees an argv and the strings it points to.
3462306a36Sopenharmony_ci */
3562306a36Sopenharmony_civoid argv_free(char **argv)
3662306a36Sopenharmony_ci{
3762306a36Sopenharmony_ci	argv--;
3862306a36Sopenharmony_ci	kfree(argv[0]);
3962306a36Sopenharmony_ci	kfree(argv);
4062306a36Sopenharmony_ci}
4162306a36Sopenharmony_ciEXPORT_SYMBOL(argv_free);
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci/**
4462306a36Sopenharmony_ci * argv_split - split a string at whitespace, returning an argv
4562306a36Sopenharmony_ci * @gfp: the GFP mask used to allocate memory
4662306a36Sopenharmony_ci * @str: the string to be split
4762306a36Sopenharmony_ci * @argcp: returned argument count
4862306a36Sopenharmony_ci *
4962306a36Sopenharmony_ci * Returns: an array of pointers to strings which are split out from
5062306a36Sopenharmony_ci * @str.  This is performed by strictly splitting on white-space; no
5162306a36Sopenharmony_ci * quote processing is performed.  Multiple whitespace characters are
5262306a36Sopenharmony_ci * considered to be a single argument separator.  The returned array
5362306a36Sopenharmony_ci * is always NULL-terminated.  Returns NULL on memory allocation
5462306a36Sopenharmony_ci * failure.
5562306a36Sopenharmony_ci *
5662306a36Sopenharmony_ci * The source string at `str' may be undergoing concurrent alteration via
5762306a36Sopenharmony_ci * userspace sysctl activity (at least).  The argv_split() implementation
5862306a36Sopenharmony_ci * attempts to handle this gracefully by taking a local copy to work on.
5962306a36Sopenharmony_ci */
6062306a36Sopenharmony_cichar **argv_split(gfp_t gfp, const char *str, int *argcp)
6162306a36Sopenharmony_ci{
6262306a36Sopenharmony_ci	char *argv_str;
6362306a36Sopenharmony_ci	bool was_space;
6462306a36Sopenharmony_ci	char **argv, **argv_ret;
6562306a36Sopenharmony_ci	int argc;
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci	argv_str = kstrndup(str, KMALLOC_MAX_SIZE - 1, gfp);
6862306a36Sopenharmony_ci	if (!argv_str)
6962306a36Sopenharmony_ci		return NULL;
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci	argc = count_argc(argv_str);
7262306a36Sopenharmony_ci	argv = kmalloc_array(argc + 2, sizeof(*argv), gfp);
7362306a36Sopenharmony_ci	if (!argv) {
7462306a36Sopenharmony_ci		kfree(argv_str);
7562306a36Sopenharmony_ci		return NULL;
7662306a36Sopenharmony_ci	}
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_ci	*argv = argv_str;
7962306a36Sopenharmony_ci	argv_ret = ++argv;
8062306a36Sopenharmony_ci	for (was_space = true; *argv_str; argv_str++) {
8162306a36Sopenharmony_ci		if (isspace(*argv_str)) {
8262306a36Sopenharmony_ci			was_space = true;
8362306a36Sopenharmony_ci			*argv_str = 0;
8462306a36Sopenharmony_ci		} else if (was_space) {
8562306a36Sopenharmony_ci			was_space = false;
8662306a36Sopenharmony_ci			*argv++ = argv_str;
8762306a36Sopenharmony_ci		}
8862306a36Sopenharmony_ci	}
8962306a36Sopenharmony_ci	*argv = NULL;
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci	if (argcp)
9262306a36Sopenharmony_ci		*argcp = argc;
9362306a36Sopenharmony_ci	return argv_ret;
9462306a36Sopenharmony_ci}
9562306a36Sopenharmony_ciEXPORT_SYMBOL(argv_split);
96