]> git.ipfire.org Git - thirdparty/kmod.git/commitdiff
libkmod-config: revamp kcmdline parsing into a state machine
authorLucas De Marchi <lucas.demarchi@intel.com>
Fri, 12 Feb 2021 09:45:21 +0000 (01:45 -0800)
committerLucas De Marchi <lucas.demarchi@intel.com>
Mon, 15 Feb 2021 19:53:38 +0000 (11:53 -0800)
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.

Tested-by: Jessica Yu <jeyu@kernel.org>
libkmod/libkmod-config.c

index 971f20b8a352406f3bbdeba12cd684266e0dc171..d3cd10d42a10890b6898d48351cdc720330cf0ab 100644 (file)
@@ -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;
 }