472 lines
11 KiB
C
472 lines
11 KiB
C
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <limits.h>
|
|
#include <errno.h>
|
|
#include <assert.h>
|
|
|
|
#include "strntol.h"
|
|
#include "pattern.h"
|
|
#include "../minmax.h"
|
|
#include "../oslib/strcasestr.h"
|
|
|
|
/**
|
|
* parse_string() - parses string in double quotes, like "abc"
|
|
* @beg - string input
|
|
* @out - output buffer where parsed number should be put
|
|
* @out_len - length of the output buffer
|
|
* @filled - pointer where number of bytes successfully
|
|
* parsed will be put
|
|
*
|
|
* Returns the end pointer where parsing has been stopped.
|
|
* In case of parsing error or lack of bytes in output buffer
|
|
* NULL will be returned.
|
|
*/
|
|
static const char *parse_string(const char *beg, char *out,
|
|
unsigned int out_len,
|
|
unsigned int *filled)
|
|
{
|
|
const char *end;
|
|
|
|
if (!out_len)
|
|
return NULL;
|
|
|
|
assert(*beg == '"');
|
|
beg++;
|
|
end = strchr(beg, '"');
|
|
if (!end)
|
|
return NULL;
|
|
if (end - beg > out_len)
|
|
return NULL;
|
|
|
|
memcpy(out, beg, end - beg);
|
|
*filled = end - beg;
|
|
|
|
/* Catch up quote */
|
|
return end + 1;
|
|
}
|
|
|
|
/**
|
|
* parse_number() - parses numbers
|
|
* @beg - string input
|
|
* @out - output buffer where parsed number should be put
|
|
* @out_len - length of the output buffer
|
|
* @filled - pointer where number of bytes successfully
|
|
* parsed will be put
|
|
*
|
|
* Supports decimals in the range [INT_MIN, INT_MAX] and
|
|
* hexidecimals of any size, which should be started with
|
|
* prefix 0x or 0X.
|
|
*
|
|
* Returns the end pointer where parsing has been stopped.
|
|
* In case of parsing error or lack of bytes in output buffer
|
|
* NULL will be returned.
|
|
*/
|
|
static const char *parse_number(const char *beg, char *out,
|
|
unsigned int out_len,
|
|
unsigned int *filled)
|
|
{
|
|
const char *end;
|
|
unsigned int val;
|
|
long lval;
|
|
int num, i;
|
|
|
|
if (!out_len)
|
|
return NULL;
|
|
|
|
num = 0;
|
|
sscanf(beg, "0%*[xX]%*[0-9a-fA-F]%n", &num);
|
|
if (num == 0) {
|
|
/* Here we are trying to parse decimal */
|
|
|
|
char *_end;
|
|
|
|
/* Looking ahead */
|
|
_end = strcasestr(beg, "0x");
|
|
if (_end)
|
|
num = _end - beg;
|
|
if (num)
|
|
lval = strntol(beg, num, &_end, 10);
|
|
else
|
|
lval = strtol(beg, &_end, 10);
|
|
if (beg == _end || lval > INT_MAX || lval < INT_MIN)
|
|
return NULL;
|
|
end = _end;
|
|
i = 0;
|
|
if (!lval) {
|
|
num = 0;
|
|
out[i] = 0x00;
|
|
i = 1;
|
|
} else {
|
|
val = (unsigned int)lval;
|
|
for (; val && out_len; out_len--, i++, val >>= 8)
|
|
out[i] = val & 0xff;
|
|
if (val)
|
|
return NULL;
|
|
}
|
|
} else {
|
|
assert(num > 2);
|
|
|
|
/* Catch up 0x prefix */
|
|
num -= 2;
|
|
beg += 2;
|
|
|
|
/* Look back, handle this combined string: 0xff0x14 */
|
|
if (beg[num] && !strncasecmp(&beg[num - 1], "0x", 2))
|
|
num--;
|
|
|
|
end = beg + num;
|
|
|
|
for (i = 0; num && out_len;
|
|
out_len--, i++, num -= 2, beg += 2) {
|
|
const char *fmt;
|
|
|
|
fmt = (num & 1 ? "%1hhx" : "%2hhx");
|
|
sscanf(beg, fmt, &out[i]);
|
|
if (num & 1) {
|
|
num++;
|
|
beg--;
|
|
}
|
|
}
|
|
if (num)
|
|
return NULL;
|
|
}
|
|
|
|
*filled = i;
|
|
return end;
|
|
|
|
}
|
|
|
|
/**
|
|
* parse_format() - parses formats, like %o, etc
|
|
* @in - string input
|
|
* @out - output buffer where space for format should be reserved
|
|
* @parsed - number of bytes which were already parsed so far
|
|
* @out_len - length of the output buffer
|
|
* @fmt_desc - format descritor array, what we expect to find
|
|
* @fmt_desc_sz - size of the format descritor array
|
|
* @fmt - format array, the output
|
|
* @fmt_sz - size of format array
|
|
*
|
|
* This function tries to find formats, e.g.:
|
|
* %o - offset of the block
|
|
*
|
|
* In case of successfull parsing it fills the format param
|
|
* with proper offset and the size of the expected value, which
|
|
* should be pasted into buffer using the format 'func' callback.
|
|
*
|
|
* Returns the end pointer where parsing has been stopped.
|
|
* In case of parsing error or lack of bytes in output buffer
|
|
* NULL will be returned.
|
|
*/
|
|
static const char *parse_format(const char *in, char *out, unsigned int parsed,
|
|
unsigned int out_len, unsigned int *filled,
|
|
const struct pattern_fmt_desc *fmt_desc,
|
|
unsigned int fmt_desc_sz,
|
|
struct pattern_fmt *fmt, unsigned int fmt_sz)
|
|
{
|
|
int i;
|
|
struct pattern_fmt *f = NULL;
|
|
unsigned int len = 0;
|
|
|
|
if (!out_len || !fmt_desc || !fmt_desc_sz || !fmt || !fmt_sz)
|
|
return NULL;
|
|
|
|
assert(*in == '%');
|
|
|
|
for (i = 0; i < fmt_desc_sz; i++) {
|
|
const struct pattern_fmt_desc *desc;
|
|
|
|
desc = &fmt_desc[i];
|
|
len = strlen(desc->fmt);
|
|
if (0 == strncmp(in, desc->fmt, len)) {
|
|
fmt->desc = desc;
|
|
fmt->off = parsed;
|
|
f = fmt;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!f)
|
|
return NULL;
|
|
if (f->desc->len > out_len)
|
|
return NULL;
|
|
|
|
memset(out, '\0', f->desc->len);
|
|
*filled = f->desc->len;
|
|
|
|
return in + len;
|
|
}
|
|
|
|
/**
|
|
* parse_and_fill_pattern() - Parses combined input, which consists of strings,
|
|
* numbers and pattern formats.
|
|
* @in - string input
|
|
* @in_len - size of the input string
|
|
* @out - output buffer where parsed result will be put
|
|
* @out_len - lengths of the output buffer
|
|
* @fmt_desc - array of pattern format descriptors [input]
|
|
* @fmt_desc_sz - size of the format descriptor array
|
|
* @fmt - array of pattern formats [output]
|
|
* @fmt_sz - pointer where the size of pattern formats array stored [input],
|
|
* after successfull parsing this pointer will contain the number
|
|
* of parsed formats if any [output].
|
|
*
|
|
* strings:
|
|
* bytes sequence in double quotes, e.g. "123".
|
|
* NOTE: there is no way to escape quote, so "123\"abc" does not work.
|
|
*
|
|
* numbers:
|
|
* hexidecimal - sequence of hex bytes starting from 0x or 0X prefix,
|
|
* e.g. 0xff12ceff1100ff
|
|
* decimal - decimal number in range [INT_MIN, INT_MAX]
|
|
*
|
|
* formats:
|
|
* %o - offset of block, reserved 8 bytes.
|
|
*
|
|
* Explicit examples of combined string:
|
|
* #1 #2 #3 #4
|
|
* in="abcd" in=-1024 in=66 in=0xFF0X1
|
|
* out=61 62 63 64 out=00 fc ff ff out=42 out=ff 01
|
|
*
|
|
* #5 #6
|
|
* in=%o in="123"0xFFeeCC
|
|
* out=00 00 00 00 00 00 00 00 out=31 32 33 ff ec cc
|
|
*
|
|
* #7
|
|
* in=-100xab"1"%o"2"
|
|
* out=f6 ff ff ff ab 31 00 00 00 00 00 00 00 00 32
|
|
*
|
|
* #9
|
|
* in=%o0xdeadbeef%o
|
|
* out=00 00 00 00 00 00 00 00 de ad be ef 00 00 00 00 00 00 00 00
|
|
*
|
|
* #10
|
|
* in=0xfefefefefefefefefefefefefefefefefefefefefe
|
|
* out=fe fe fe fe fe fe fe fe fe fe fe fe fe fe fe fe fe fe fe fe fe
|
|
*
|
|
* Returns number of bytes filled or err < 0 in case of failure.
|
|
*/
|
|
int parse_and_fill_pattern(const char *in, unsigned int in_len,
|
|
char *out, unsigned int out_len,
|
|
const struct pattern_fmt_desc *fmt_desc,
|
|
unsigned int fmt_desc_sz,
|
|
struct pattern_fmt *fmt,
|
|
unsigned int *fmt_sz_out)
|
|
{
|
|
const char *beg, *end, *out_beg = out;
|
|
unsigned int total = 0, fmt_rem = 0;
|
|
|
|
if (!in || !in_len || !out || !out_len)
|
|
return -EINVAL;
|
|
if (fmt_sz_out)
|
|
fmt_rem = *fmt_sz_out;
|
|
|
|
beg = in;
|
|
do {
|
|
unsigned int filled;
|
|
int parsed_fmt;
|
|
|
|
filled = 0;
|
|
parsed_fmt = 0;
|
|
|
|
switch (*beg) {
|
|
case '"':
|
|
end = parse_string(beg, out, out_len, &filled);
|
|
break;
|
|
case '%':
|
|
end = parse_format(beg, out, out - out_beg, out_len,
|
|
&filled, fmt_desc, fmt_desc_sz,
|
|
fmt, fmt_rem);
|
|
parsed_fmt = 1;
|
|
break;
|
|
default:
|
|
end = parse_number(beg, out, out_len, &filled);
|
|
break;
|
|
}
|
|
|
|
if (!end)
|
|
return -EINVAL;
|
|
|
|
if (parsed_fmt) {
|
|
assert(fmt_rem);
|
|
fmt_rem--;
|
|
fmt++;
|
|
}
|
|
|
|
assert(end - beg <= in_len);
|
|
in_len -= end - beg;
|
|
beg = end;
|
|
|
|
assert(filled);
|
|
assert(filled <= out_len);
|
|
out_len -= filled;
|
|
out += filled;
|
|
total += filled;
|
|
|
|
} while (in_len);
|
|
|
|
if (fmt_sz_out)
|
|
*fmt_sz_out -= fmt_rem;
|
|
return total;
|
|
}
|
|
|
|
/**
|
|
* dup_pattern() - Duplicates part of the pattern all over the buffer.
|
|
*
|
|
* Returns 0 in case of success or errno < 0 in case of failure.
|
|
*/
|
|
static int dup_pattern(char *out, unsigned int out_len, unsigned int pattern_len)
|
|
{
|
|
unsigned int left, len, off;
|
|
|
|
if (out_len <= pattern_len)
|
|
/* Normal case */
|
|
return 0;
|
|
|
|
off = pattern_len;
|
|
left = (out_len - off);
|
|
len = min(left, off);
|
|
|
|
/* Duplicate leftover */
|
|
while (left) {
|
|
memcpy(out + off, out, len);
|
|
left -= len;
|
|
off <<= 1;
|
|
len = min(left, off);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* cpy_pattern() - Copies pattern to the buffer.
|
|
*
|
|
* Function copies pattern along the whole buffer.
|
|
*
|
|
* Returns 0 in case of success or errno < 0 in case of failure.
|
|
*/
|
|
int cpy_pattern(const char *pattern, unsigned int pattern_len,
|
|
char *out, unsigned int out_len)
|
|
{
|
|
unsigned int len;
|
|
|
|
if (!pattern || !pattern_len || !out || !out_len)
|
|
return -EINVAL;
|
|
|
|
/* Copy pattern */
|
|
len = min(pattern_len, out_len);
|
|
memcpy(out, pattern, len);
|
|
|
|
/* Spread filled chunk all over the buffer */
|
|
return dup_pattern(out, out_len, pattern_len);
|
|
}
|
|
|
|
/**
|
|
* cmp_pattern() - Compares pattern and buffer.
|
|
*
|
|
* For the sake of performance this function avoids any loops.
|
|
* Firstly it tries to compare the buffer itself, checking that
|
|
* buffer consists of repeating patterns along the buffer size.
|
|
*
|
|
* If the difference is not found then the function tries to compare
|
|
* buffer and pattern.
|
|
*
|
|
* Returns 0 in case of success or errno < 0 in case of failure.
|
|
*/
|
|
int cmp_pattern(const char *pattern, unsigned int pattern_size,
|
|
unsigned int off, const char *buf, unsigned int len)
|
|
{
|
|
int rc;
|
|
unsigned int size;
|
|
|
|
/* Find the difference in buffer */
|
|
if (len > pattern_size) {
|
|
rc = memcmp(buf, buf + pattern_size, len - pattern_size);
|
|
if (rc)
|
|
return -EILSEQ;
|
|
}
|
|
/* Compare second part of the pattern with buffer */
|
|
if (off) {
|
|
size = min(len, pattern_size - off);
|
|
rc = memcmp(buf, pattern + off, size);
|
|
if (rc)
|
|
return -EILSEQ;
|
|
buf += size;
|
|
len -= size;
|
|
}
|
|
/* Compare first part of the pattern or the whole pattern
|
|
* with buffer */
|
|
if (len) {
|
|
size = min(len, (off ? off : pattern_size));
|
|
rc = memcmp(buf, pattern, size);
|
|
if (rc)
|
|
return -EILSEQ;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* paste_format_inplace() - Pastes parsed formats to the pattern.
|
|
*
|
|
* This function pastes formats to the pattern. If @fmt_sz is 0
|
|
* function does nothing and pattern buffer is left untouched.
|
|
*
|
|
* Returns 0 in case of success or errno < 0 in case of failure.
|
|
*/
|
|
int paste_format_inplace(char *pattern, unsigned int pattern_len,
|
|
struct pattern_fmt *fmt, unsigned int fmt_sz,
|
|
void *priv)
|
|
{
|
|
int i, rc;
|
|
unsigned int len;
|
|
|
|
if (!pattern || !pattern_len || !fmt)
|
|
return -EINVAL;
|
|
|
|
/* Paste formats for first pattern chunk */
|
|
for (i = 0; i < fmt_sz; i++) {
|
|
struct pattern_fmt *f;
|
|
|
|
f = &fmt[i];
|
|
if (pattern_len <= f->off)
|
|
break;
|
|
len = min(pattern_len - f->off, f->desc->len);
|
|
rc = f->desc->paste(pattern + f->off, len, priv);
|
|
if (rc)
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* paste_format() - Pastes parsed formats to the buffer.
|
|
*
|
|
* This function copies pattern to the buffer, pastes format
|
|
* into it and then duplicates pattern all over the buffer size.
|
|
*
|
|
* Returns 0 in case of success or errno < 0 in case of failure.
|
|
*/
|
|
int paste_format(const char *pattern, unsigned int pattern_len,
|
|
struct pattern_fmt *fmt, unsigned int fmt_sz,
|
|
char *out, unsigned int out_len, void *priv)
|
|
{
|
|
int rc;
|
|
unsigned int len;
|
|
|
|
if (!pattern || !pattern_len || !out || !out_len)
|
|
return -EINVAL;
|
|
|
|
/* Copy pattern */
|
|
len = min(pattern_len, out_len);
|
|
memcpy(out, pattern, len);
|
|
|
|
rc = paste_format_inplace(out, len, fmt, fmt_sz, priv);
|
|
if (rc)
|
|
return rc;
|
|
|
|
/* Spread filled chunk all over the buffer */
|
|
return dup_pattern(out, out_len, pattern_len);
|
|
}
|