auxdisplay: charlcd: Fix and clean up handling of x/y commands
authorMiguel Ojeda <miguel.ojeda.sandonis@gmail.com>
Tue, 27 Feb 2018 22:09:52 +0000 (23:09 +0100)
committerMiguel Ojeda <miguel.ojeda.sandonis@gmail.com>
Thu, 12 Apr 2018 17:02:44 +0000 (19:02 +0200)
The current version is not parsing multiple x/y commands as the code
originally intended. On top of that, kstrtoul() expects
NULL-terminated strings. Finally, the code does two passes over
the string.

Some explanations about the supported syntax are added as well.

Cc: Willy Tarreau <w@1wt.eu>
Cc: Geert Uytterhoeven <geert@linux-m68k.org>
Cc: Andy Shevchenko <andy.shevchenko@gmail.com>
Cc: Robert Abel <rabel@robertabel.eu>
Signed-off-by: Miguel Ojeda <miguel.ojeda.sandonis@gmail.com>
drivers/auxdisplay/charlcd.c

index 674ffbae1c656f24def9a222e7ea77735367bd70..e92f3e25f51d51c5e8c7bbbbc4d6d19e9440f2d5 100644 (file)
@@ -11,6 +11,7 @@
  */
 
 #include <linux/atomic.h>
+#include <linux/ctype.h>
 #include <linux/delay.h>
 #include <linux/fs.h>
 #include <linux/miscdevice.h>
@@ -293,6 +294,79 @@ static int charlcd_init_display(struct charlcd *lcd)
        return 0;
 }
 
+/*
+ * Parses an unsigned integer from a string, until a non-digit character
+ * is found. The empty string is not accepted. No overflow checks are done.
+ *
+ * Returns whether the parsing was successful. Only in that case
+ * the output parameters are written to.
+ *
+ * TODO: If the kernel adds an inplace version of kstrtoul(), this function
+ * could be easily replaced by that.
+ */
+static bool parse_n(const char *s, unsigned long *res, const char **next_s)
+{
+       if (!isdigit(*s))
+               return false;
+
+       *res = 0;
+       while (isdigit(*s)) {
+               *res = *res * 10 + (*s - '0');
+               ++s;
+       }
+
+       *next_s = s;
+       return true;
+}
+
+/*
+ * Parses a movement command of the form "(.*);", where the group can be
+ * any number of subcommands of the form "(x|y)[0-9]+".
+ *
+ * Returns whether the command is valid. The position arguments are
+ * only written if the parsing was successful.
+ *
+ * For instance:
+ *   - ";"          returns (<original x>, <original y>).
+ *   - "x1;"        returns (1, <original y>).
+ *   - "y2x1;"      returns (1, 2).
+ *   - "x12y34x56;" returns (56, 34).
+ *   - ""           fails.
+ *   - "x"          fails.
+ *   - "x;"         fails.
+ *   - "x1"         fails.
+ *   - "xy12;"      fails.
+ *   - "x12yy12;"   fails.
+ *   - "xx"         fails.
+ */
+static bool parse_xy(const char *s, unsigned long *x, unsigned long *y)
+{
+       unsigned long new_x = *x;
+       unsigned long new_y = *y;
+
+       for (;;) {
+               if (!*s)
+                       return false;
+
+               if (*s == ';')
+                       break;
+
+               if (*s == 'x') {
+                       if (!parse_n(s + 1, &new_x, &s))
+                               return false;
+               } else if (*s == 'y') {
+                       if (!parse_n(s + 1, &new_y, &s))
+                               return false;
+               } else {
+                       return false;
+               }
+       }
+
+       *x = new_x;
+       *y = new_y;
+       return true;
+}
+
 /*
  * These are the file operation function for user access to /dev/lcd
  * This function can also be called from inside the kernel, by
@@ -471,24 +545,11 @@ static inline int handle_lcd_special_code(struct charlcd *lcd)
        }
        case 'x':       /* gotoxy : LxXXX[yYYY]; */
        case 'y':       /* gotoxy : LyYYY[xXXX]; */
-               if (!strchr(esc, ';'))
-                       break;
-
-               while (*esc) {
-                       if (*esc == 'x') {
-                               esc++;
-                               if (kstrtoul(esc, 10, &priv->addr.x) < 0)
-                                       break;
-                       } else if (*esc == 'y') {
-                               esc++;
-                               if (kstrtoul(esc, 10, &priv->addr.y) < 0)
-                                       break;
-                       } else {
-                               break;
-                       }
-               }
+               /* If the command is valid, move to the new address */
+               if (parse_xy(esc, &priv->addr.x, &priv->addr.y))
+                       charlcd_gotoxy(lcd);
 
-               charlcd_gotoxy(lcd);
+               /* Regardless of its validity, mark as processed */
                processed = 1;
                break;
        }