lib/pattern: Support NULL output buffer in parse_and_fill_pattern()
[fio.git] / lib / pattern.c
CommitLineData
fada2fcd
TK
1#include <stdio.h>
2#include <stdlib.h>
3#include <string.h>
4#include <limits.h>
5#include <errno.h>
6#include <assert.h>
a1554f65
SB
7#include <fcntl.h>
8#include <unistd.h>
fada2fcd 9
634bd210
RP
10#include "strntol.h"
11#include "pattern.h"
fada2fcd 12#include "../minmax.h"
a328eb9a 13#include "../oslib/strcasestr.h"
b29c71c4 14#include "../oslib/strndup.h"
634bd210 15
a1554f65
SB
16/**
17 * parse_file() - parses binary file to fill buffer
18 * @beg - string input, extract filename from this
19 * @out - output buffer where parsed number should be put
20 * @out_len - length of the output buffer
21 * @filled - pointer where number of bytes successfully
22 * parsed will be put
23 *
24 * Returns the end pointer where parsing has been stopped.
25 * In case of parsing error or lack of bytes in output buffer
26 * NULL will be returned.
27 */
28static const char *parse_file(const char *beg, char *out,
29 unsigned int out_len,
30 unsigned int *filled)
31{
32 const char *end;
33 char *file;
34 int fd;
35 ssize_t count;
36
37 if (!out_len)
38 goto err_out;
39
40 assert(*beg == '\'');
41 beg++;
42 end = strchr(beg, '\'');
43 if (!end)
44 goto err_out;
45
46 file = strndup(beg, end - beg);
47 if (file == NULL)
48 goto err_out;
49
50 fd = open(file, O_RDONLY);
51 if (fd < 0)
52 goto err_free_out;
53
6c939739
LG
54 if (out) {
55 count = read(fd, out, out_len);
56 if (count == -1)
57 goto err_free_close_out;
58 } else {
59 count = lseek(fd, 0, SEEK_END);
60 if (count == -1)
61 goto err_free_close_out;
62 if (count >= out_len)
63 count = out_len;
64 }
a1554f65
SB
65
66 *filled = count;
67 close(fd);
68 free(file);
69
70 /* Catch up quote */
71 return end + 1;
72
73err_free_close_out:
74 close(fd);
75err_free_out:
76 free(file);
77err_out:
78 return NULL;
79
80}
81
634bd210
RP
82/**
83 * parse_string() - parses string in double quotes, like "abc"
84 * @beg - string input
85 * @out - output buffer where parsed number should be put
86 * @out_len - length of the output buffer
87 * @filled - pointer where number of bytes successfully
88 * parsed will be put
89 *
90 * Returns the end pointer where parsing has been stopped.
91 * In case of parsing error or lack of bytes in output buffer
92 * NULL will be returned.
93 */
94static const char *parse_string(const char *beg, char *out,
95 unsigned int out_len,
96 unsigned int *filled)
97{
98 const char *end;
99
100 if (!out_len)
101 return NULL;
102
103 assert(*beg == '"');
104 beg++;
105 end = strchr(beg, '"');
106 if (!end)
107 return NULL;
108 if (end - beg > out_len)
109 return NULL;
110
6c939739
LG
111 if (out)
112 memcpy(out, beg, end - beg);
634bd210
RP
113 *filled = end - beg;
114
115 /* Catch up quote */
116 return end + 1;
117}
118
119/**
120 * parse_number() - parses numbers
121 * @beg - string input
122 * @out - output buffer where parsed number should be put
123 * @out_len - length of the output buffer
124 * @filled - pointer where number of bytes successfully
125 * parsed will be put
126 *
127 * Supports decimals in the range [INT_MIN, INT_MAX] and
128 * hexidecimals of any size, which should be started with
129 * prefix 0x or 0X.
130 *
131 * Returns the end pointer where parsing has been stopped.
132 * In case of parsing error or lack of bytes in output buffer
133 * NULL will be returned.
134 */
135static const char *parse_number(const char *beg, char *out,
136 unsigned int out_len,
137 unsigned int *filled)
138{
139 const char *end;
140 unsigned int val;
141 long lval;
142 int num, i;
143
144 if (!out_len)
145 return NULL;
146
147 num = 0;
148 sscanf(beg, "0%*[xX]%*[0-9a-fA-F]%n", &num);
149 if (num == 0) {
150 /* Here we are trying to parse decimal */
151
152 char *_end;
153
154 /* Looking ahead */
155 _end = strcasestr(beg, "0x");
156 if (_end)
157 num = _end - beg;
158 if (num)
159 lval = strntol(beg, num, &_end, 10);
160 else
161 lval = strtol(beg, &_end, 10);
162 if (beg == _end || lval > INT_MAX || lval < INT_MIN)
163 return NULL;
164 end = _end;
165 i = 0;
166 if (!lval) {
167 num = 0;
6c939739
LG
168 if (out)
169 out[i] = 0x00;
634bd210
RP
170 i = 1;
171 } else {
172 val = (unsigned int)lval;
173 for (; val && out_len; out_len--, i++, val >>= 8)
6c939739
LG
174 if (out)
175 out[i] = val & 0xff;
634bd210
RP
176 if (val)
177 return NULL;
178 }
179 } else {
180 assert(num > 2);
181
182 /* Catch up 0x prefix */
183 num -= 2;
184 beg += 2;
185
186 /* Look back, handle this combined string: 0xff0x14 */
187 if (beg[num] && !strncasecmp(&beg[num - 1], "0x", 2))
188 num--;
189
190 end = beg + num;
191
192 for (i = 0; num && out_len;
193 out_len--, i++, num -= 2, beg += 2) {
194 const char *fmt;
195
196 fmt = (num & 1 ? "%1hhx" : "%2hhx");
6c939739
LG
197 if (out)
198 sscanf(beg, fmt, &out[i]);
634bd210
RP
199 if (num & 1) {
200 num++;
201 beg--;
202 }
203 }
204 if (num)
205 return NULL;
206 }
207
208 *filled = i;
209 return end;
210
211}
212
213/**
214 * parse_format() - parses formats, like %o, etc
215 * @in - string input
216 * @out - output buffer where space for format should be reserved
217 * @parsed - number of bytes which were already parsed so far
218 * @out_len - length of the output buffer
44031abf 219 * @fmt_desc - format descriptor array, what we expect to find
634bd210
RP
220 * @fmt - format array, the output
221 * @fmt_sz - size of format array
222 *
223 * This function tries to find formats, e.g.:
224 * %o - offset of the block
225 *
fc002f14 226 * In case of successful parsing it fills the format param
634bd210
RP
227 * with proper offset and the size of the expected value, which
228 * should be pasted into buffer using the format 'func' callback.
229 *
230 * Returns the end pointer where parsing has been stopped.
231 * In case of parsing error or lack of bytes in output buffer
232 * NULL will be returned.
233 */
234static const char *parse_format(const char *in, char *out, unsigned int parsed,
235 unsigned int out_len, unsigned int *filled,
236 const struct pattern_fmt_desc *fmt_desc,
634bd210
RP
237 struct pattern_fmt *fmt, unsigned int fmt_sz)
238{
239 int i;
240 struct pattern_fmt *f = NULL;
241 unsigned int len = 0;
242
969b9fbb 243 if (!out_len || !fmt_desc || !fmt || !fmt_sz)
634bd210
RP
244 return NULL;
245
246 assert(*in == '%');
247
969b9fbb 248 for (i = 0; fmt_desc[i].fmt; i++) {
634bd210
RP
249 const struct pattern_fmt_desc *desc;
250
251 desc = &fmt_desc[i];
252 len = strlen(desc->fmt);
253 if (0 == strncmp(in, desc->fmt, len)) {
254 fmt->desc = desc;
255 fmt->off = parsed;
256 f = fmt;
257 break;
258 }
259 }
260
261 if (!f)
262 return NULL;
263 if (f->desc->len > out_len)
264 return NULL;
265
6c939739
LG
266 if (out)
267 memset(out, '\0', f->desc->len);
634bd210
RP
268 *filled = f->desc->len;
269
270 return in + len;
271}
272
273/**
274 * parse_and_fill_pattern() - Parses combined input, which consists of strings,
275 * numbers and pattern formats.
276 * @in - string input
277 * @in_len - size of the input string
6c939739
LG
278 * @out - output buffer where parsed result will be put, may be NULL
279 * in which case this function just calculates the required
280 * length of the buffer
634bd210
RP
281 * @out_len - lengths of the output buffer
282 * @fmt_desc - array of pattern format descriptors [input]
634bd210
RP
283 * @fmt - array of pattern formats [output]
284 * @fmt_sz - pointer where the size of pattern formats array stored [input],
fc002f14 285 * after successful parsing this pointer will contain the number
634bd210
RP
286 * of parsed formats if any [output].
287 *
288 * strings:
289 * bytes sequence in double quotes, e.g. "123".
290 * NOTE: there is no way to escape quote, so "123\"abc" does not work.
291 *
292 * numbers:
fc002f14 293 * hexadecimal - sequence of hex bytes starting from 0x or 0X prefix,
634bd210
RP
294 * e.g. 0xff12ceff1100ff
295 * decimal - decimal number in range [INT_MIN, INT_MAX]
296 *
297 * formats:
298 * %o - offset of block, reserved 8 bytes.
299 *
300 * Explicit examples of combined string:
301 * #1 #2 #3 #4
302 * in="abcd" in=-1024 in=66 in=0xFF0X1
303 * out=61 62 63 64 out=00 fc ff ff out=42 out=ff 01
304 *
305 * #5 #6
306 * in=%o in="123"0xFFeeCC
307 * out=00 00 00 00 00 00 00 00 out=31 32 33 ff ec cc
308 *
309 * #7
310 * in=-100xab"1"%o"2"
311 * out=f6 ff ff ff ab 31 00 00 00 00 00 00 00 00 32
312 *
313 * #9
314 * in=%o0xdeadbeef%o
315 * out=00 00 00 00 00 00 00 00 de ad be ef 00 00 00 00 00 00 00 00
316 *
317 * #10
318 * in=0xfefefefefefefefefefefefefefefefefefefefefe
319 * out=fe fe fe fe fe fe fe fe fe fe fe fe fe fe fe fe fe fe fe fe fe
320 *
321 * Returns number of bytes filled or err < 0 in case of failure.
322 */
323int parse_and_fill_pattern(const char *in, unsigned int in_len,
324 char *out, unsigned int out_len,
325 const struct pattern_fmt_desc *fmt_desc,
634bd210
RP
326 struct pattern_fmt *fmt,
327 unsigned int *fmt_sz_out)
328{
329 const char *beg, *end, *out_beg = out;
330 unsigned int total = 0, fmt_rem = 0;
331
6c939739 332 if (!in || !in_len || !out_len)
634bd210
RP
333 return -EINVAL;
334 if (fmt_sz_out)
335 fmt_rem = *fmt_sz_out;
336
337 beg = in;
338 do {
339 unsigned int filled;
340 int parsed_fmt;
341
342 filled = 0;
343 parsed_fmt = 0;
344
345 switch (*beg) {
a1554f65
SB
346 case '\'':
347 end = parse_file(beg, out, out_len, &filled);
348 break;
634bd210
RP
349 case '"':
350 end = parse_string(beg, out, out_len, &filled);
351 break;
352 case '%':
353 end = parse_format(beg, out, out - out_beg, out_len,
969b9fbb 354 &filled, fmt_desc, fmt, fmt_rem);
634bd210
RP
355 parsed_fmt = 1;
356 break;
357 default:
358 end = parse_number(beg, out, out_len, &filled);
359 break;
360 }
361
362 if (!end)
363 return -EINVAL;
364
365 if (parsed_fmt) {
366 assert(fmt_rem);
367 fmt_rem--;
368 fmt++;
369 }
370
371 assert(end - beg <= in_len);
372 in_len -= end - beg;
373 beg = end;
374
375 assert(filled);
376 assert(filled <= out_len);
377 out_len -= filled;
378 out += filled;
379 total += filled;
380
381 } while (in_len);
382
383 if (fmt_sz_out)
384 *fmt_sz_out -= fmt_rem;
385 return total;
386}
387
388/**
389 * dup_pattern() - Duplicates part of the pattern all over the buffer.
390 *
391 * Returns 0 in case of success or errno < 0 in case of failure.
392 */
393static int dup_pattern(char *out, unsigned int out_len, unsigned int pattern_len)
394{
395 unsigned int left, len, off;
396
397 if (out_len <= pattern_len)
398 /* Normal case */
399 return 0;
400
401 off = pattern_len;
402 left = (out_len - off);
403 len = min(left, off);
404
405 /* Duplicate leftover */
406 while (left) {
407 memcpy(out + off, out, len);
408 left -= len;
409 off <<= 1;
410 len = min(left, off);
411 }
412
413 return 0;
414}
415
416/**
417 * cpy_pattern() - Copies pattern to the buffer.
418 *
419 * Function copies pattern along the whole buffer.
420 *
421 * Returns 0 in case of success or errno < 0 in case of failure.
422 */
423int cpy_pattern(const char *pattern, unsigned int pattern_len,
424 char *out, unsigned int out_len)
425{
426 unsigned int len;
427
428 if (!pattern || !pattern_len || !out || !out_len)
429 return -EINVAL;
430
431 /* Copy pattern */
432 len = min(pattern_len, out_len);
433 memcpy(out, pattern, len);
434
435 /* Spread filled chunk all over the buffer */
436 return dup_pattern(out, out_len, pattern_len);
437}
438
439/**
440 * cmp_pattern() - Compares pattern and buffer.
441 *
442 * For the sake of performance this function avoids any loops.
443 * Firstly it tries to compare the buffer itself, checking that
444 * buffer consists of repeating patterns along the buffer size.
445 *
446 * If the difference is not found then the function tries to compare
447 * buffer and pattern.
448 *
449 * Returns 0 in case of success or errno < 0 in case of failure.
450 */
451int cmp_pattern(const char *pattern, unsigned int pattern_size,
452 unsigned int off, const char *buf, unsigned int len)
453{
454 int rc;
455 unsigned int size;
456
457 /* Find the difference in buffer */
458 if (len > pattern_size) {
459 rc = memcmp(buf, buf + pattern_size, len - pattern_size);
460 if (rc)
461 return -EILSEQ;
462 }
463 /* Compare second part of the pattern with buffer */
464 if (off) {
465 size = min(len, pattern_size - off);
466 rc = memcmp(buf, pattern + off, size);
467 if (rc)
468 return -EILSEQ;
469 buf += size;
470 len -= size;
471 }
472 /* Compare first part of the pattern or the whole pattern
473 * with buffer */
474 if (len) {
475 size = min(len, (off ? off : pattern_size));
476 rc = memcmp(buf, pattern, size);
477 if (rc)
478 return -EILSEQ;
479 }
480
481 return 0;
482}
483
484/**
485 * paste_format_inplace() - Pastes parsed formats to the pattern.
486 *
487 * This function pastes formats to the pattern. If @fmt_sz is 0
488 * function does nothing and pattern buffer is left untouched.
489 *
490 * Returns 0 in case of success or errno < 0 in case of failure.
491 */
492int paste_format_inplace(char *pattern, unsigned int pattern_len,
493 struct pattern_fmt *fmt, unsigned int fmt_sz,
494 void *priv)
495{
496 int i, rc;
497 unsigned int len;
498
499 if (!pattern || !pattern_len || !fmt)
500 return -EINVAL;
501
502 /* Paste formats for first pattern chunk */
503 for (i = 0; i < fmt_sz; i++) {
504 struct pattern_fmt *f;
505
506 f = &fmt[i];
507 if (pattern_len <= f->off)
508 break;
509 len = min(pattern_len - f->off, f->desc->len);
510 rc = f->desc->paste(pattern + f->off, len, priv);
511 if (rc)
512 return rc;
513 }
514
515 return 0;
516}
517
518/**
519 * paste_format() - Pastes parsed formats to the buffer.
520 *
521 * This function copies pattern to the buffer, pastes format
522 * into it and then duplicates pattern all over the buffer size.
523 *
524 * Returns 0 in case of success or errno < 0 in case of failure.
525 */
526int paste_format(const char *pattern, unsigned int pattern_len,
527 struct pattern_fmt *fmt, unsigned int fmt_sz,
528 char *out, unsigned int out_len, void *priv)
529{
530 int rc;
531 unsigned int len;
532
533 if (!pattern || !pattern_len || !out || !out_len)
534 return -EINVAL;
535
536 /* Copy pattern */
537 len = min(pattern_len, out_len);
538 memcpy(out, pattern, len);
539
540 rc = paste_format_inplace(out, len, fmt, fmt_sz, priv);
541 if (rc)
542 return rc;
543
544 /* Spread filled chunk all over the buffer */
545 return dup_pattern(out, out_len, pattern_len);
546}