From b34050fadb86c55d2066ec78a9d05e9809ff5812 Mon Sep 17 00:00:00 2001 From: Miguel Ojeda Date: Tue, 27 Feb 2018 23:09:52 +0100 Subject: [PATCH] auxdisplay: charlcd: Fix and clean up handling of x/y commands 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 Cc: Geert Uytterhoeven Cc: Andy Shevchenko Cc: Robert Abel Signed-off-by: Miguel Ojeda --- drivers/auxdisplay/charlcd.c | 95 +++++++++++++++++++++++++++++------- 1 file changed, 78 insertions(+), 17 deletions(-) diff --git a/drivers/auxdisplay/charlcd.c b/drivers/auxdisplay/charlcd.c index 674ffbae1c65..e92f3e25f51d 100644 --- a/drivers/auxdisplay/charlcd.c +++ b/drivers/auxdisplay/charlcd.c @@ -11,6 +11,7 @@ */ #include +#include #include #include #include @@ -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 (, ). + * - "x1;" returns (1, ). + * - "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; } -- 2.30.2