u-boot-brain/cmd/setexpr.c
Simon Glass 9528229f22 setexpr: Correct dropping of final unmatched string
At present the 'nlen' variable increases with each loop. If the previous
loop had back references, then subsequent loops without back references
use the wrong value of nlen. The value is larger, meaning that the string
terminator from nbuf is copied along to the main buffer, thus terminating
the string prematurely.

This leads to the final result being truncated, e.g. missing the last
(unmatched) part of the string. So "match match tail" become
"replaced replaced" instead of "replaced replaced tail".

Fix this by resetting nlen to the correct value each time around the lop.

Fixes: 855f18ea0e ("setexpr: add regex substring matching and substitution")
Signed-off-by: Simon Glass <sjg@chromium.org>
2020-12-01 10:33:38 -05:00

428 lines
8.7 KiB
C

// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright 2008 Freescale Semiconductor, Inc.
* Copyright 2013 Wolfgang Denk <wd@denx.de>
*/
/*
* This file provides a shell like 'expr' function to return.
*/
#include <common.h>
#include <config.h>
#include <command.h>
#include <env.h>
#include <log.h>
#include <mapmem.h>
static ulong get_arg(char *s, int w)
{
/*
* If the parameter starts with a '*' then assume it is a pointer to
* the value we want.
*/
if (s[0] == '*') {
ulong *p;
ulong addr;
ulong val;
addr = simple_strtoul(&s[1], NULL, 16);
switch (w) {
case 1:
p = map_sysmem(addr, sizeof(uchar));
val = (ulong)*(uchar *)p;
unmap_sysmem(p);
return val;
case 2:
p = map_sysmem(addr, sizeof(ushort));
val = (ulong)*(ushort *)p;
unmap_sysmem(p);
return val;
case 4:
p = map_sysmem(addr, sizeof(u32));
val = *(u32 *)p;
unmap_sysmem(p);
return val;
default:
p = map_sysmem(addr, sizeof(ulong));
val = *p;
unmap_sysmem(p);
return val;
}
} else {
return simple_strtoul(s, NULL, 16);
}
}
#ifdef CONFIG_REGEX
#include <slre.h>
/*
* memstr - Find the first substring in memory
* @s1: The string to be searched
* @s2: The string to search for
*
* Similar to and based on strstr(),
* but strings do not need to be NUL terminated.
*/
static char *memstr(const char *s1, int l1, const char *s2, int l2)
{
if (!l2)
return (char *)s1;
while (l1 >= l2) {
l1--;
if (!memcmp(s1, s2, l2))
return (char *)s1;
s1++;
}
return NULL;
}
/**
* substitute() - Substitute part of one string with another
*
* This updates @string so that the first occurrence of @old is replaced with
* @new
*
* @string: String buffer containing string to update at the start
* @slen: Pointer to current string length, updated on success
* @ssize: Size of string buffer
* @old: Old string to find in the buffer (no terminator needed)
* @olen: Length of @old excluding terminator
* @new: New string to replace @old with
* @nlen: Length of @new excluding terminator
* @return pointer to immediately after the copied @new in @string, or NULL if
* no replacement took place
*/
static char *substitute(char *string, int *slen, int ssize,
const char *old, int olen, const char *new, int nlen)
{
char *p = memstr(string, *slen, old, olen);
if (p == NULL)
return NULL;
debug("## Match at pos %ld: match len %d, subst len %d\n",
(long)(p - string), olen, nlen);
/* make sure replacement matches */
if (*slen + nlen - olen > ssize) {
printf("## error: substitution buffer overflow\n");
return NULL;
}
/* move tail if needed */
if (olen != nlen) {
int tail, len;
len = (olen > nlen) ? olen : nlen;
tail = ssize - (p + len - string);
debug("## tail len %d\n", tail);
memmove(p + nlen, p + olen, tail);
}
/* insert substitute */
memcpy(p, new, nlen);
*slen += nlen - olen;
return p + nlen;
}
int setexpr_regex_sub(char *data, uint data_size, char *nbuf, uint nbuf_size,
const char *r, const char *s, bool global)
{
struct slre slre;
char *datap = data;
int res, len, nlen, loop;
if (slre_compile(&slre, r) == 0) {
printf("Error compiling regex: %s\n", slre.err_str);
return 1;
}
len = strlen(data);
for (loop = 0;; loop++) {
struct cap caps[slre.num_caps + 2];
const char *old;
char *np;
int i, olen;
(void) memset(caps, 0, sizeof(caps));
res = slre_match(&slre, datap, len, caps);
debug("Result: %d\n", res);
for (i = 0; i < slre.num_caps; i++) {
if (caps[i].len > 0) {
debug("Substring %d: [%.*s]\n", i,
caps[i].len, caps[i].ptr);
}
}
if (res == 0) {
if (loop == 0) {
printf("%s: No match\n", data);
return 1;
} else {
break;
}
}
debug("## MATCH ## %s\n", data);
if (!s)
return 1;
old = caps[0].ptr;
olen = caps[0].len;
nlen = strlen(s);
if (nlen + 1 >= nbuf_size) {
printf("## error: pattern buffer overflow: have %d, need %d\n",
nbuf_size, nlen + 1);
return 1;
}
strcpy(nbuf, s);
debug("## SUBST(1) ## %s\n", nbuf);
/*
* Handle back references
*
* Support for \0 ... \9, where \0 is the
* whole matched pattern (similar to &).
*
* Implementation is a bit simpleminded as
* backrefs are substituted sequentially, one
* by one. This will lead to somewhat
* unexpected results if the replacement
* strings contain any \N strings then then
* may get substitued, too. We accept this
* restriction for the sake of simplicity.
*/
for (i = 0; i < 10; ++i) {
char backref[2] = {
'\\',
'0',
};
if (caps[i].len == 0)
break;
backref[1] += i;
debug("## BACKREF %d: replace \"%.*s\" by \"%.*s\" in \"%s\"\n",
i,
2, backref,
caps[i].len, caps[i].ptr,
nbuf);
for (np = nbuf;;) {
char *p = memstr(np, nlen, backref, 2);
if (p == NULL)
break;
np = substitute(np, &nlen,
nbuf_size,
backref, 2,
caps[i].ptr, caps[i].len);
if (np == NULL)
return 1;
}
}
debug("## SUBST(2) ## %s\n", nbuf);
datap = substitute(datap, &len, data_size, old, olen,
nbuf, nlen);
if (datap == NULL)
return 1;
debug("## REMAINDER: %s\n", datap);
debug("## RESULT: %s\n", data);
if (!global)
break;
}
debug("## FINAL (now env_set()) : %s\n", data);
return 0;
}
#define SLRE_BUFSZ 16384
#define SLRE_PATSZ 4096
/*
* Perform regex operations on a environment variable
*
* Returns 0 if OK, 1 in case of errors.
*/
static int regex_sub_var(const char *name, const char *r, const char *s,
const char *t, int global)
{
struct slre slre;
char data[SLRE_BUFSZ];
char nbuf[SLRE_PATSZ];
const char *value;
int len;
int ret;
if (!name)
return 1;
if (slre_compile(&slre, r) == 0) {
printf("Error compiling regex: %s\n", slre.err_str);
return 1;
}
if (!t) {
value = env_get(name);
if (!value) {
printf("## Error: variable \"%s\" not defined\n", name);
return 1;
}
t = value;
}
debug("REGEX on %s=%s\n", name, t);
debug("REGEX=\"%s\", SUBST=\"%s\", GLOBAL=%d\n", r, s ? s : "<NULL>",
global);
len = strlen(t);
if (len + 1 > SLRE_BUFSZ) {
printf("## error: subst buffer overflow: have %d, need %d\n",
SLRE_BUFSZ, len + 1);
return 1;
}
strcpy(data, t);
ret = setexpr_regex_sub(data, SLRE_BUFSZ, nbuf, SLRE_PATSZ, r, s,
global);
if (ret)
return 1;
printf("%s=%s\n", name, data);
return env_set(name, data);
}
#endif
static int do_setexpr(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[])
{
ulong a, b;
ulong value;
int w;
/*
* We take 3, 5, or 6 arguments:
* 3 : setexpr name value
* 5 : setexpr name val1 op val2
* setexpr name [g]sub r s
* 6 : setexpr name [g]sub r s t
*/
/* > 6 already tested by max command args */
if ((argc < 3) || (argc == 4))
return CMD_RET_USAGE;
w = cmd_get_data_size(argv[0], 4);
a = get_arg(argv[2], w);
/* plain assignment: "setexpr name value" */
if (argc == 3) {
env_set_hex(argv[1], a);
return 0;
}
/* 5 or 6 args (6 args only with [g]sub) */
#ifdef CONFIG_REGEX
/*
* rexep handling: "setexpr name [g]sub r s [t]"
* with 5 args, "t" will be NULL
*/
if (strcmp(argv[2], "gsub") == 0)
return regex_sub_var(argv[1], argv[3], argv[4], argv[5], 1);
if (strcmp(argv[2], "sub") == 0)
return regex_sub_var(argv[1], argv[3], argv[4], argv[5], 0);
#endif
/* standard operators: "setexpr name val1 op val2" */
if (argc != 5)
return CMD_RET_USAGE;
if (strlen(argv[3]) != 1)
return CMD_RET_USAGE;
b = get_arg(argv[4], w);
switch (argv[3][0]) {
case '|':
value = a | b;
break;
case '&':
value = a & b;
break;
case '+':
value = a + b;
break;
case '^':
value = a ^ b;
break;
case '-':
value = a - b;
break;
case '*':
value = a * b;
break;
case '/':
value = a / b;
break;
case '%':
value = a % b;
break;
default:
printf("invalid op\n");
return 1;
}
env_set_hex(argv[1], value);
return 0;
}
U_BOOT_CMD(
setexpr, 6, 0, do_setexpr,
"set environment variable as the result of eval expression",
"[.b, .w, .l] name [*]value1 <op> [*]value2\n"
" - set environment variable 'name' to the result of the evaluated\n"
" expression specified by <op>. <op> can be &, |, ^, +, -, *, /, %\n"
" size argument is only meaningful if value1 and/or value2 are\n"
" memory addresses (*)\n"
"setexpr[.b, .w, .l] name [*]value\n"
" - load a value into a variable"
#ifdef CONFIG_REGEX
"\n"
"setexpr name gsub r s [t]\n"
" - For each substring matching the regular expression <r> in the\n"
" string <t>, substitute the string <s>. The result is\n"
" assigned to <name>. If <t> is not supplied, use the old\n"
" value of <name>\n"
"setexpr name sub r s [t]\n"
" - Just like gsub(), but replace only the first matching substring"
#endif
);