]> granicus.if.org Git - strace/blob - qualify.c
qualify: add syntax for optional non-matching syscall qualification
[strace] / qualify.c
1 /*
2  * Copyright (c) 2016 Dmitry V. Levin <ldv@altlinux.org>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 3. The name of the author may not be used to endorse or promote products
14  *    derived from this software without specific prior written permission.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27
28 #include "defs.h"
29 #include "nsig.h"
30 #include <regex.h>
31
32 typedef unsigned int number_slot_t;
33 #define BITS_PER_SLOT (sizeof(number_slot_t) * 8)
34
35 struct number_set {
36         number_slot_t *vec;
37         unsigned int nslots;
38         bool not;
39 };
40
41 struct number_set read_set;
42 struct number_set write_set;
43 struct number_set signal_set;
44
45 static struct number_set abbrev_set[SUPPORTED_PERSONALITIES];
46 static struct number_set inject_set[SUPPORTED_PERSONALITIES];
47 static struct number_set raw_set[SUPPORTED_PERSONALITIES];
48 static struct number_set trace_set[SUPPORTED_PERSONALITIES];
49 static struct number_set verbose_set[SUPPORTED_PERSONALITIES];
50
51 static void
52 number_setbit(const unsigned int i, number_slot_t *const vec)
53 {
54         vec[i / BITS_PER_SLOT] |= (number_slot_t) 1 << (i % BITS_PER_SLOT);
55 }
56
57 static bool
58 number_isset(const unsigned int i, const number_slot_t *const vec)
59 {
60         return vec[i / BITS_PER_SLOT] & ((number_slot_t) 1 << (i % BITS_PER_SLOT));
61 }
62
63 static void
64 reallocate_number_set(struct number_set *const set, const unsigned int new_nslots)
65 {
66         if (new_nslots <= set->nslots)
67                 return;
68         set->vec = xreallocarray(set->vec, new_nslots, sizeof(*set->vec));
69         memset(set->vec + set->nslots, 0,
70                sizeof(*set->vec) * (new_nslots - set->nslots));
71         set->nslots = new_nslots;
72 }
73
74 static void
75 add_number_to_set(const unsigned int number, struct number_set *const set)
76 {
77         reallocate_number_set(set, number / BITS_PER_SLOT + 1);
78         number_setbit(number, set->vec);
79 }
80
81 bool
82 is_number_in_set(const unsigned int number, const struct number_set *const set)
83 {
84         return ((number / BITS_PER_SLOT < set->nslots)
85                 && number_isset(number, set->vec)) ^ set->not;
86 }
87
88 typedef int (*string_to_uint_func)(const char *);
89
90 /*
91  * Add numbers to SET according to STR specification.
92  */
93 static void
94 qualify_tokens(const char *const str, struct number_set *const set,
95                string_to_uint_func func, const char *const name)
96 {
97         /* Clear the set. */
98         if (set->nslots)
99                 memset(set->vec, 0, sizeof(*set->vec) * set->nslots);
100         set->not = false;
101
102         /*
103          * Each leading ! character means inversion
104          * of the remaining specification.
105          */
106         const char *s = str;
107 handle_inversion:
108         while (*s == '!') {
109                 set->not = !set->not;
110                 ++s;
111         }
112
113         if (strcmp(s, "none") == 0) {
114                 /*
115                  * No numbers are added to the set.
116                  * Subsequent is_number_in_set invocations will return set->not.
117                  */
118                 return;
119         } else if (strcmp(s, "all") == 0) {
120                 s = "!none";
121                 goto handle_inversion;
122         }
123
124         /*
125          * Split the string into comma separated tokens.
126          * For each token, find out the corresponding number
127          * by calling FUNC, and add that number to the set.
128          * The absence of tokens or a negative answer
129          * from FUNC is a fatal error.
130          */
131         char *copy = xstrdup(s);
132         char *saveptr = NULL;
133         const char *token;
134         int number = -1;
135
136         for (token = strtok_r(copy, ",", &saveptr); token;
137              token = strtok_r(NULL, ",", &saveptr)) {
138                 number = func(token);
139                 if (number < 0) {
140                         error_msg_and_die("invalid %s '%s'", name, token);
141                 }
142
143                 add_number_to_set(number, set);
144         }
145
146         free(copy);
147
148         if (number < 0) {
149                 error_msg_and_die("invalid %s '%s'", name, str);
150         }
151 }
152
153 static int
154 sigstr_to_uint(const char *s)
155 {
156         int i;
157
158         if (*s >= '0' && *s <= '9')
159                 return string_to_uint_upto(s, 255);
160
161         if (strncasecmp(s, "SIG", 3) == 0)
162                 s += 3;
163
164         for (i = 0; i <= 255; ++i) {
165                 const char *name = signame(i);
166
167                 if (strncasecmp(name, "SIG", 3) != 0)
168                         continue;
169
170                 name += 3;
171
172                 if (strcasecmp(name, s) != 0)
173                         continue;
174
175                 return i;
176         }
177
178         return -1;
179 }
180
181 static bool
182 qualify_syscall_number(const char *s, struct number_set *set)
183 {
184         int n = string_to_uint(s);
185         if (n < 0)
186                 return false;
187
188         unsigned int p;
189         bool done = false;
190
191         for (p = 0; p < SUPPORTED_PERSONALITIES; ++p) {
192                 if ((unsigned) n >= nsyscall_vec[p]) {
193                         continue;
194                 }
195                 add_number_to_set(n, &set[p]);
196                 done = true;
197         }
198
199         return done;
200 }
201
202 static void
203 regerror_msg_and_die(int errcode, const regex_t *preg,
204                      const char *str, const char *pattern)
205 {
206         char buf[512];
207
208         regerror(errcode, preg, buf, sizeof(buf));
209         error_msg_and_die("%s: %s: %s", str, pattern, buf);
210 }
211
212 static bool
213 qualify_syscall_regex(const char *s, struct number_set *set)
214 {
215         regex_t preg;
216         int rc;
217
218         if ((rc = regcomp(&preg, s, REG_EXTENDED | REG_NOSUB)) != 0)
219                 regerror_msg_and_die(rc, &preg, "regcomp", s);
220
221         unsigned int p;
222         bool found = false;
223         for (p = 0; p < SUPPORTED_PERSONALITIES; ++p) {
224                 unsigned int i;
225
226                 for (i = 0; i < nsyscall_vec[p]; ++i) {
227                         if (!sysent_vec[p][i].sys_name)
228                                 continue;
229                         rc = regexec(&preg, sysent_vec[p][i].sys_name,
230                                      0, NULL, 0);
231                         if (rc == REG_NOMATCH)
232                                 continue;
233                         else if (rc)
234                                 regerror_msg_and_die(rc, &preg, "regexec", s);
235                         add_number_to_set(i, &set[p]);
236                         found = true;
237                 }
238         }
239
240         regfree(&preg);
241         return found;
242 }
243
244 static unsigned int
245 lookup_class(const char *s)
246 {
247         static const struct {
248                 const char *name;
249                 unsigned int value;
250         } syscall_class[] = {
251                 { "desc",       TRACE_DESC      },
252                 { "file",       TRACE_FILE      },
253                 { "memory",     TRACE_MEMORY    },
254                 { "process",    TRACE_PROCESS   },
255                 { "signal",     TRACE_SIGNAL    },
256                 { "ipc",        TRACE_IPC       },
257                 { "network",    TRACE_NETWORK   },
258                 { "%desc",      TRACE_DESC      },
259                 { "%file",      TRACE_FILE      },
260                 { "%memory",    TRACE_MEMORY    },
261                 { "%process",   TRACE_PROCESS   },
262                 { "%signal",    TRACE_SIGNAL    },
263                 { "%ipc",       TRACE_IPC       },
264                 { "%network",   TRACE_NETWORK   },
265                 { "%statfs",    TRACE_STATFS    },
266                 { "%fstatfs",   TRACE_FSTATFS   },
267                 { "%%statfs",   TRACE_STATFS_LIKE       },
268         };
269
270         unsigned int i;
271         for (i = 0; i < ARRAY_SIZE(syscall_class); ++i) {
272                 if (strcmp(s, syscall_class[i].name) == 0) {
273                         return syscall_class[i].value;
274                 }
275         }
276
277         return 0;
278 }
279
280 static bool
281 qualify_syscall_class(const char *s, struct number_set *set)
282 {
283         const unsigned int n = lookup_class(s);
284         if (!n)
285                 return false;
286
287         unsigned int p;
288         for (p = 0; p < SUPPORTED_PERSONALITIES; ++p) {
289                 unsigned int i;
290
291                 for (i = 0; i < nsyscall_vec[p]; ++i) {
292                         if (!sysent_vec[p][i].sys_name
293                             || (sysent_vec[p][i].sys_flags & n) != n) {
294                                 continue;
295                         }
296                         add_number_to_set(i, &set[p]);
297                 }
298         }
299
300         return true;
301 }
302
303 static bool
304 qualify_syscall_name(const char *s, struct number_set *set)
305 {
306         unsigned int p;
307         bool found = false;
308
309         for (p = 0; p < SUPPORTED_PERSONALITIES; ++p) {
310                 unsigned int i;
311
312                 for (i = 0; i < nsyscall_vec[p]; ++i) {
313                         if (!sysent_vec[p][i].sys_name
314                             || strcmp(s, sysent_vec[p][i].sys_name)) {
315                                 continue;
316                         }
317                         add_number_to_set(i, &set[p]);
318                         found = true;
319                 }
320         }
321
322         return found;
323 }
324
325 static bool
326 qualify_syscall(const char *token, struct number_set *set)
327 {
328         bool ignore_fail = false;
329
330         while (*token == '?') {
331                 token++;
332                 ignore_fail = true;
333         }
334         if (*token >= '0' && *token <= '9')
335                 return qualify_syscall_number(token, set) || ignore_fail;
336         if (*token == '/')
337                 return qualify_syscall_regex(token + 1, set) || ignore_fail;
338         return qualify_syscall_class(token, set)
339                || qualify_syscall_name(token, set)
340                || ignore_fail;
341 }
342
343 /*
344  * Add syscall numbers to SETs for each supported personality
345  * according to STR specification.
346  */
347 static void
348 qualify_syscall_tokens(const char *const str, struct number_set *const set,
349                        const char *const name)
350 {
351         /* Clear all sets. */
352         unsigned int p;
353         for (p = 0; p < SUPPORTED_PERSONALITIES; ++p) {
354                 if (set[p].nslots)
355                         memset(set[p].vec, 0,
356                                sizeof(*set[p].vec) * set[p].nslots);
357                 set[p].not = false;
358         }
359
360         /*
361          * Each leading ! character means inversion
362          * of the remaining specification.
363          */
364         const char *s = str;
365 handle_inversion:
366         while (*s == '!') {
367                 for (p = 0; p < SUPPORTED_PERSONALITIES; ++p) {
368                         set[p].not = !set[p].not;
369                 }
370                 ++s;
371         }
372
373         if (strcmp(s, "none") == 0) {
374                 /*
375                  * No syscall numbers are added to sets.
376                  * Subsequent is_number_in_set invocations
377                  * will return set[p]->not.
378                  */
379                 return;
380         } else if (strcmp(s, "all") == 0) {
381                 s = "!none";
382                 goto handle_inversion;
383         }
384
385         /*
386          * Split the string into comma separated tokens.
387          * For each token, call qualify_syscall that will take care
388          * if adding appropriate syscall numbers to sets.
389          * The absence of tokens or a negative return code
390          * from qualify_syscall is a fatal error.
391          */
392         char *copy = xstrdup(s);
393         char *saveptr = NULL;
394         const char *token;
395         bool done = false;
396
397         for (token = strtok_r(copy, ",", &saveptr); token;
398              token = strtok_r(NULL, ",", &saveptr)) {
399                 done = qualify_syscall(token, set);
400                 if (!done) {
401                         error_msg_and_die("invalid %s '%s'", name, token);
402                 }
403         }
404
405         free(copy);
406
407         if (!done) {
408                 error_msg_and_die("invalid %s '%s'", name, str);
409         }
410 }
411
412 /*
413  * Returns NULL if STR does not start with PREFIX,
414  * or a pointer to the first char in STR after PREFIX.
415  */
416 static const char *
417 strip_prefix(const char *prefix, const char *str)
418 {
419         size_t len = strlen(prefix);
420
421         return strncmp(prefix, str, len) ? NULL : str + len;
422 }
423
424 static int
425 find_errno_by_name(const char *name)
426 {
427         unsigned int i;
428
429         for (i = 1; i < nerrnos; ++i) {
430                 if (errnoent[i] && (strcasecmp(name, errnoent[i]) == 0))
431                         return i;
432         }
433
434         return -1;
435 }
436
437 static bool
438 parse_inject_token(const char *const token, struct inject_opts *const fopts,
439                    const bool fault_tokens_only)
440 {
441         const char *val;
442         int intval;
443
444         if ((val = strip_prefix("when=", token))) {
445                 /*
446                  *      == 1+1
447                  * F    == F+0
448                  * F+   == F+1
449                  * F+S
450                  */
451                 char *end;
452                 intval = string_to_uint_ex(val, &end, 0xffff, "+");
453                 if (intval < 1)
454                         return false;
455
456                 fopts->first = intval;
457
458                 if (*end) {
459                         val = end + 1;
460                         if (*val) {
461                                 /* F+S */
462                                 intval = string_to_uint_upto(val, 0xffff);
463                                 if (intval < 1)
464                                         return false;
465                                 fopts->step = intval;
466                         } else {
467                                 /* F+ == F+1 */
468                                 fopts->step = 1;
469                         }
470                 } else {
471                         /* F == F+0 */
472                         fopts->step = 0;
473                 }
474         } else if ((val = strip_prefix("error=", token))) {
475                 if (fopts->rval != INJECT_OPTS_RVAL_DEFAULT)
476                         return false;
477                 intval = string_to_uint_upto(val, MAX_ERRNO_VALUE);
478                 if (intval < 0)
479                         intval = find_errno_by_name(val);
480                 if (intval < 1)
481                         return false;
482                 fopts->rval = -intval;
483         } else if (!fault_tokens_only && (val = strip_prefix("retval=", token))) {
484                 if (fopts->rval != INJECT_OPTS_RVAL_DEFAULT)
485                         return false;
486                 intval = string_to_uint(val);
487                 if (intval < 0)
488                         return false;
489                 fopts->rval = intval;
490         } else if (!fault_tokens_only && (val = strip_prefix("signal=", token))) {
491                 intval = sigstr_to_uint(val);
492                 if (intval < 1 || intval > NSIG_BYTES * 8)
493                         return false;
494                 fopts->signo = intval;
495         } else {
496                 return false;
497         }
498
499         return true;
500 }
501
502 static char *
503 parse_inject_expression(const char *const s, char **buf,
504                         struct inject_opts *const fopts,
505                         const bool fault_tokens_only)
506 {
507         char *saveptr = NULL;
508         char *name = NULL;
509         char *token;
510
511         *buf = xstrdup(s);
512         for (token = strtok_r(*buf, ":", &saveptr); token;
513              token = strtok_r(NULL, ":", &saveptr)) {
514                 if (!name)
515                         name = token;
516                 else if (!parse_inject_token(token, fopts, fault_tokens_only))
517                         goto parse_error;
518         }
519
520         if (name)
521                 return name;
522
523 parse_error:
524         free(*buf);
525         return *buf = NULL;
526 }
527
528 static void
529 qualify_read(const char *const str)
530 {
531         qualify_tokens(str, &read_set, string_to_uint, "descriptor");
532 }
533
534 static void
535 qualify_write(const char *const str)
536 {
537         qualify_tokens(str, &write_set, string_to_uint, "descriptor");
538 }
539
540 static void
541 qualify_signals(const char *const str)
542 {
543         qualify_tokens(str, &signal_set, sigstr_to_uint, "signal");
544 }
545
546 static void
547 qualify_trace(const char *const str)
548 {
549         qualify_syscall_tokens(str, trace_set, "system call");
550 }
551
552 static void
553 qualify_abbrev(const char *const str)
554 {
555         qualify_syscall_tokens(str, abbrev_set, "system call");
556 }
557
558 static void
559 qualify_verbose(const char *const str)
560 {
561         qualify_syscall_tokens(str, verbose_set, "system call");
562 }
563
564 static void
565 qualify_raw(const char *const str)
566 {
567         qualify_syscall_tokens(str, raw_set, "system call");
568 }
569
570 static void
571 qualify_inject_common(const char *const str,
572                       const bool fault_tokens_only,
573                       const char *const description)
574 {
575         struct inject_opts opts = {
576                 .first = 1,
577                 .step = 1,
578                 .rval = INJECT_OPTS_RVAL_DEFAULT,
579                 .signo = 0
580         };
581         char *buf = NULL;
582         char *name = parse_inject_expression(str, &buf, &opts, fault_tokens_only);
583         if (!name) {
584                 error_msg_and_die("invalid %s '%s'", description, str);
585         }
586
587         /* If neither of retval, error, or signal is specified, then ... */
588         if (opts.rval == INJECT_OPTS_RVAL_DEFAULT && !opts.signo) {
589                 if (fault_tokens_only) {
590                         /* in fault= syntax the default error code is ENOSYS. */
591                         opts.rval = -ENOSYS;
592                 } else {
593                         /* in inject= syntax this is not allowed. */
594                         error_msg_and_die("invalid %s '%s'", description, str);
595                 }
596         }
597
598         struct number_set tmp_set[SUPPORTED_PERSONALITIES];
599         memset(tmp_set, 0, sizeof(tmp_set));
600         qualify_syscall_tokens(name, tmp_set, description);
601
602         free(buf);
603
604         /*
605          * Initialize inject_vec accourding to tmp_set.
606          * Merge tmp_set into inject_set.
607          */
608         unsigned int p;
609         for (p = 0; p < SUPPORTED_PERSONALITIES; ++p) {
610                 if (!tmp_set[p].nslots && !tmp_set[p].not) {
611                         continue;
612                 }
613
614                 if (!inject_vec[p]) {
615                         inject_vec[p] = xcalloc(nsyscall_vec[p],
616                                                sizeof(*inject_vec[p]));
617                 }
618
619                 unsigned int i;
620                 for (i = 0; i < nsyscall_vec[p]; ++i) {
621                         if (is_number_in_set(i, &tmp_set[p])) {
622                                 add_number_to_set(i, &inject_set[p]);
623                                 inject_vec[p][i] = opts;
624                         }
625                 }
626
627                 free(tmp_set[p].vec);
628         }
629 }
630
631 static void
632 qualify_fault(const char *const str)
633 {
634         qualify_inject_common(str, true, "fault argument");
635 }
636
637 static void
638 qualify_inject(const char *const str)
639 {
640         qualify_inject_common(str, false, "inject argument");
641 }
642
643 static const struct qual_options {
644         const char *name;
645         void (*qualify)(const char *);
646 } qual_options[] = {
647         { "trace",      qualify_trace   },
648         { "t",          qualify_trace   },
649         { "abbrev",     qualify_abbrev  },
650         { "a",          qualify_abbrev  },
651         { "verbose",    qualify_verbose },
652         { "v",          qualify_verbose },
653         { "raw",        qualify_raw     },
654         { "x",          qualify_raw     },
655         { "signal",     qualify_signals },
656         { "signals",    qualify_signals },
657         { "s",          qualify_signals },
658         { "read",       qualify_read    },
659         { "reads",      qualify_read    },
660         { "r",          qualify_read    },
661         { "write",      qualify_write   },
662         { "writes",     qualify_write   },
663         { "w",          qualify_write   },
664         { "fault",      qualify_fault   },
665         { "inject",     qualify_inject  },
666 };
667
668 void
669 qualify(const char *str)
670 {
671         const struct qual_options *opt = qual_options;
672         unsigned int i;
673
674         for (i = 0; i < ARRAY_SIZE(qual_options); ++i) {
675                 const char *p = qual_options[i].name;
676                 unsigned int len = strlen(p);
677
678                 if (strncmp(str, p, len) || str[len] != '=')
679                         continue;
680
681                 opt = &qual_options[i];
682                 str += len + 1;
683                 break;
684         }
685
686         opt->qualify(str);
687 }
688
689 unsigned int
690 qual_flags(const unsigned int scno)
691 {
692         return  (is_number_in_set(scno, &trace_set[current_personality])
693                    ? QUAL_TRACE : 0)
694                 | (is_number_in_set(scno, &abbrev_set[current_personality])
695                    ? QUAL_ABBREV : 0)
696                 | (is_number_in_set(scno, &verbose_set[current_personality])
697                    ? QUAL_VERBOSE : 0)
698                 | (is_number_in_set(scno, &raw_set[current_personality])
699                    ? QUAL_RAW : 0)
700                 | (is_number_in_set(scno, &inject_set[current_personality])
701                    ? QUAL_INJECT : 0);
702 }