diff mbox series

[1/4] libkmod-config: revamp kcmdline parsing into a state machine

Message ID 20210212094524.170861-1-lucas.demarchi@intel.com (mailing list archive)
State New, archived
Headers show
Series [1/4] libkmod-config: revamp kcmdline parsing into a state machine | expand

Commit Message

Lucas De Marchi Feb. 12, 2021, 9:45 a.m. UTC
The handling of spaces and quotes is becoming hard to maintain. Convert
the parser into a state machine so we can check all the states. This
should make it easier to fix a corner case we have right now:
The kernel also accepts a quote before the module name instead of the
value. But this additional is left for later. This is purely an
algorithm change with no behavior change.
---
 libkmod/libkmod-config.c | 86 ++++++++++++++++++++++++----------------
 1 file changed, 52 insertions(+), 34 deletions(-)
diff mbox series

Patch

diff --git a/libkmod/libkmod-config.c b/libkmod/libkmod-config.c
index 971f20b..d3cd10d 100644
--- a/libkmod/libkmod-config.c
+++ b/libkmod/libkmod-config.c
@@ -499,7 +499,14 @@  static int kmod_config_parse_kcmdline(struct kmod_config *config)
 	char buf[KCMD_LINE_SIZE];
 	int fd, err;
 	char *p, *modname,  *param = NULL, *value = NULL;
-	bool is_quoted = false, is_module = true;
+	bool is_quoted = false, iter = true;
+	enum state {
+		STATE_IGNORE,
+		STATE_MODNAME,
+		STATE_PARAM,
+		STATE_VALUE,
+		STATE_COMPLETE,
+	} state;
 
 	fd = open("/proc/cmdline", O_RDONLY|O_CLOEXEC);
 	if (fd < 0) {
@@ -516,54 +523,65 @@  static int kmod_config_parse_kcmdline(struct kmod_config *config)
 		return err;
 	}
 
-	for (p = buf, modname = buf; *p != '\0' && *p != '\n'; p++) {
-		if (*p == '"') {
+	state = STATE_MODNAME;
+	for (p = buf, modname = buf; iter; p++) {
+		switch (*p) {
+		case '"':
 			is_quoted = !is_quoted;
-
-			if (is_quoted) {
-				/* don't consider a module until closing quotes */
-				is_module = false;
-			} else if (param != NULL && value != NULL) {
+			break;
+		case '\0':
+		case '\n':
+			/* Stop iterating on new chars */
+			iter = false;
+			/* fall-through */
+		case ' ':
+			if (is_quoted && state == STATE_VALUE) {
+				/* no state change*/;
+			} else if (is_quoted) {
+				/* spaces are only allowed in the value part */
+				state = STATE_IGNORE;
+			} else if (state == STATE_VALUE || state == STATE_PARAM) {
+				*p = '\0';
+				state = STATE_COMPLETE;
+			} else {
 				/*
-				 * If we are indeed expecting a value and
-				 * closing quotes, then this can be considered
-				 * a valid option for a module
+				 * go to next option, ignoring any possible
+				 * partial match we have
 				 */
-				is_module = true;
+				modname = p + 1;
+				state = STATE_MODNAME;
 			}
-
-			continue;
-		}
-		if (is_quoted)
-			continue;
-
-		switch (*p) {
-		case ' ':
-			*p = '\0';
-			if (is_module)
-				kcmdline_parse_result(config, modname, param, value);
-			param = value = NULL;
-			modname = p + 1;
-			is_module = true;
 			break;
 		case '.':
-			if (param == NULL) {
+			if (state == STATE_MODNAME) {
 				*p = '\0';
 				param = p + 1;
+				state = STATE_PARAM;
+			} else if (state == STATE_PARAM) {
+				state = STATE_IGNORE;
 			}
 			break;
 		case '=':
-			if (param != NULL)
+			if (state == STATE_PARAM) {
+				/*
+				 * Don't set *p to '\0': the value var shadows
+				 * param
+				 */
 				value = p + 1;
-			else
-				is_module = false;
+				state = STATE_VALUE;
+			} else if (state == STATE_MODNAME) {
+				state = STATE_IGNORE;
+			}
 			break;
 		}
-	}
 
-	*p = '\0';
-	if (is_module)
-		kcmdline_parse_result(config, modname, param, value);
+		if (state == STATE_COMPLETE) {
+			kcmdline_parse_result(config, modname, param, value);
+			/* start over on next iteration */
+			modname = p + 1;
+			state = STATE_MODNAME;
+		}
+	}
 
 	return 0;
 }