From c72e01b9ea891fa5cbbbe9876bdfb49079b55be7 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 28 Apr 2024 09:26:15 +0200 Subject: Adding upstream version 1.6.4. Signed-off-by: Daniel Baumann --- src/Makefile | 62 ++ src/sqlhist-lex.c | 2204 +++++++++++++++++++++++++++++++++++++++++++ src/sqlhist-parse.h | 77 ++ src/sqlhist.l | 104 ++ src/sqlhist.tab.c | 1755 ++++++++++++++++++++++++++++++++++ src/sqlhist.tab.h | 118 +++ src/sqlhist.y | 248 +++++ src/tracefs-dynevents.c | 779 +++++++++++++++ src/tracefs-eprobes.c | 56 ++ src/tracefs-events.c | 1515 ++++++++++++++++++++++++++++++ src/tracefs-filter.c | 807 ++++++++++++++++ src/tracefs-hist.c | 2401 +++++++++++++++++++++++++++++++++++++++++++++++ src/tracefs-instance.c | 1241 ++++++++++++++++++++++++ src/tracefs-kprobes.c | 198 ++++ src/tracefs-marker.c | 199 ++++ src/tracefs-record.c | 611 ++++++++++++ src/tracefs-sqlhist.c | 1653 ++++++++++++++++++++++++++++++++ src/tracefs-tools.c | 1273 +++++++++++++++++++++++++ src/tracefs-uprobes.c | 90 ++ src/tracefs-utils.c | 624 ++++++++++++ 20 files changed, 16015 insertions(+) create mode 100644 src/Makefile create mode 100644 src/sqlhist-lex.c create mode 100644 src/sqlhist-parse.h create mode 100644 src/sqlhist.l create mode 100644 src/sqlhist.tab.c create mode 100644 src/sqlhist.tab.h create mode 100644 src/sqlhist.y create mode 100644 src/tracefs-dynevents.c create mode 100644 src/tracefs-eprobes.c create mode 100644 src/tracefs-events.c create mode 100644 src/tracefs-filter.c create mode 100644 src/tracefs-hist.c create mode 100644 src/tracefs-instance.c create mode 100644 src/tracefs-kprobes.c create mode 100644 src/tracefs-marker.c create mode 100644 src/tracefs-record.c create mode 100644 src/tracefs-sqlhist.c create mode 100644 src/tracefs-tools.c create mode 100644 src/tracefs-uprobes.c create mode 100644 src/tracefs-utils.c (limited to 'src') diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..e2965bc --- /dev/null +++ b/src/Makefile @@ -0,0 +1,62 @@ +# SPDX-License-Identifier: LGPL-2.1 + +include $(src)/scripts/utils.mk + +OBJS = +OBJS += tracefs-utils.o +OBJS += tracefs-instance.o +OBJS += tracefs-events.o +OBJS += tracefs-tools.o +OBJS += tracefs-marker.o +OBJS += tracefs-kprobes.o +OBJS += tracefs-hist.o +OBJS += tracefs-filter.o +OBJS += tracefs-dynevents.o +OBJS += tracefs-eprobes.o +OBJS += tracefs-uprobes.o +OBJS += tracefs-record.o + +# Order matters for the the three below +OBJS += sqlhist-lex.o +OBJS += sqlhist.tab.o +OBJS += tracefs-sqlhist.o + +OBJS := $(OBJS:%.o=$(bdir)/%.o) + +$(LIBTRACEFS_STATIC): $(OBJS) + $(Q)$(call do_build_static_lib) + +$(LIBTRACEFS_SHARED): $(OBJS) + $(Q)$(call do_compile_shared_library,$(notdir $(LIBTRACEFS_SHARED_VERSION))) + +$(LIBTRACEFS_SHARED_VERSION): $(LIBTRACEFS_SHARED) + @ln -sf $( 0 +#define FLEX_BETA +#endif + +#ifdef yyget_lval +#define yyget_lval_ALREADY_DEFINED +#else +#define yyget_lval yyget_lval +#endif + +#ifdef yyset_lval +#define yyset_lval_ALREADY_DEFINED +#else +#define yyset_lval yyset_lval +#endif + +/* First, we deal with platform-specific or compiler-specific issues. */ + +/* begin standard C headers. */ +#include +#include +#include +#include + +/* end standard C headers. */ + +/* flex integer type definitions */ + +#ifndef FLEXINT_H +#define FLEXINT_H + +/* C99 systems have . Non-C99 systems may or may not. */ + +#if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L + +/* C99 says to define __STDC_LIMIT_MACROS before including stdint.h, + * if you want the limit (max/min) macros for int types. + */ +#ifndef __STDC_LIMIT_MACROS +#define __STDC_LIMIT_MACROS 1 +#endif + +#include +typedef int8_t flex_int8_t; +typedef uint8_t flex_uint8_t; +typedef int16_t flex_int16_t; +typedef uint16_t flex_uint16_t; +typedef int32_t flex_int32_t; +typedef uint32_t flex_uint32_t; +#else +typedef signed char flex_int8_t; +typedef short int flex_int16_t; +typedef int flex_int32_t; +typedef unsigned char flex_uint8_t; +typedef unsigned short int flex_uint16_t; +typedef unsigned int flex_uint32_t; + +/* Limits of integral types. */ +#ifndef INT8_MIN +#define INT8_MIN (-128) +#endif +#ifndef INT16_MIN +#define INT16_MIN (-32767-1) +#endif +#ifndef INT32_MIN +#define INT32_MIN (-2147483647-1) +#endif +#ifndef INT8_MAX +#define INT8_MAX (127) +#endif +#ifndef INT16_MAX +#define INT16_MAX (32767) +#endif +#ifndef INT32_MAX +#define INT32_MAX (2147483647) +#endif +#ifndef UINT8_MAX +#define UINT8_MAX (255U) +#endif +#ifndef UINT16_MAX +#define UINT16_MAX (65535U) +#endif +#ifndef UINT32_MAX +#define UINT32_MAX (4294967295U) +#endif + +#ifndef SIZE_MAX +#define SIZE_MAX (~(size_t)0) +#endif + +#endif /* ! C99 */ + +#endif /* ! FLEXINT_H */ + +/* begin standard C++ headers. */ + +/* TODO: this is always defined, so inline it */ +#define yyconst const + +#if defined(__GNUC__) && __GNUC__ >= 3 +#define yynoreturn __attribute__((__noreturn__)) +#else +#define yynoreturn +#endif + +/* Returned upon end-of-file. */ +#define YY_NULL 0 + +/* Promotes a possibly negative, possibly signed char to an + * integer in range [0..255] for use as an array index. + */ +#define YY_SC_TO_UI(c) ((YY_CHAR) (c)) + +/* An opaque pointer. */ +#ifndef YY_TYPEDEF_YY_SCANNER_T +#define YY_TYPEDEF_YY_SCANNER_T +typedef void* yyscan_t; +#endif + +/* For convenience, these vars (plus the bison vars far below) + are macros in the reentrant scanner. */ +#define yyin yyg->yyin_r +#define yyout yyg->yyout_r +#define yyextra yyg->yyextra_r +#define yyleng yyg->yyleng_r +#define yytext yyg->yytext_r +#define yylineno (YY_CURRENT_BUFFER_LVALUE->yy_bs_lineno) +#define yycolumn (YY_CURRENT_BUFFER_LVALUE->yy_bs_column) +#define yy_flex_debug yyg->yy_flex_debug_r + +/* Enter a start condition. This macro really ought to take a parameter, + * but we do it the disgusting crufty way forced on us by the ()-less + * definition of BEGIN. + */ +#define BEGIN yyg->yy_start = 1 + 2 * +/* Translate the current start state into a value that can be later handed + * to BEGIN to return to the state. The YYSTATE alias is for lex + * compatibility. + */ +#define YY_START ((yyg->yy_start - 1) / 2) +#define YYSTATE YY_START +/* Action number for EOF rule of a given start state. */ +#define YY_STATE_EOF(state) (YY_END_OF_BUFFER + state + 1) +/* Special action meaning "start processing a new file". */ +#define YY_NEW_FILE yyrestart( yyin , yyscanner ) +#define YY_END_OF_BUFFER_CHAR 0 + +/* Size of default input buffer. */ +#ifndef YY_BUF_SIZE +#ifdef __ia64__ +/* On IA-64, the buffer size is 16k, not 8k. + * Moreover, YY_BUF_SIZE is 2*YY_READ_BUF_SIZE in the general case. + * Ditto for the __ia64__ case accordingly. + */ +#define YY_BUF_SIZE 32768 +#else +#define YY_BUF_SIZE 16384 +#endif /* __ia64__ */ +#endif + +/* The state buf must be large enough to hold one state per character in the main buffer. + */ +#define YY_STATE_BUF_SIZE ((YY_BUF_SIZE + 2) * sizeof(yy_state_type)) + +#ifndef YY_TYPEDEF_YY_BUFFER_STATE +#define YY_TYPEDEF_YY_BUFFER_STATE +typedef struct yy_buffer_state *YY_BUFFER_STATE; +#endif + +#ifndef YY_TYPEDEF_YY_SIZE_T +#define YY_TYPEDEF_YY_SIZE_T +typedef size_t yy_size_t; +#endif + +#define EOB_ACT_CONTINUE_SCAN 0 +#define EOB_ACT_END_OF_FILE 1 +#define EOB_ACT_LAST_MATCH 2 + + #define YY_LESS_LINENO(n) + #define YY_LINENO_REWIND_TO(ptr) + +/* Return all but the first "n" matched characters back to the input stream. */ +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up yytext. */ \ + int yyless_macro_arg = (n); \ + YY_LESS_LINENO(yyless_macro_arg);\ + *yy_cp = yyg->yy_hold_char; \ + YY_RESTORE_YY_MORE_OFFSET \ + yyg->yy_c_buf_p = yy_cp = yy_bp + yyless_macro_arg - YY_MORE_ADJ; \ + YY_DO_BEFORE_ACTION; /* set up yytext again */ \ + } \ + while ( 0 ) +#define unput(c) yyunput( c, yyg->yytext_ptr , yyscanner ) + +#ifndef YY_STRUCT_YY_BUFFER_STATE +#define YY_STRUCT_YY_BUFFER_STATE +struct yy_buffer_state + { + FILE *yy_input_file; + + char *yy_ch_buf; /* input buffer */ + char *yy_buf_pos; /* current position in input buffer */ + + /* Size of input buffer in bytes, not including room for EOB + * characters. + */ + int yy_buf_size; + + /* Number of characters read into yy_ch_buf, not including EOB + * characters. + */ + int yy_n_chars; + + /* Whether we "own" the buffer - i.e., we know we created it, + * and can realloc() it to grow it, and should free() it to + * delete it. + */ + int yy_is_our_buffer; + + /* Whether this is an "interactive" input source; if so, and + * if we're using stdio for input, then we want to use getc() + * instead of fread(), to make sure we stop fetching input after + * each newline. + */ + int yy_is_interactive; + + /* Whether we're considered to be at the beginning of a line. + * If so, '^' rules will be active on the next match, otherwise + * not. + */ + int yy_at_bol; + + int yy_bs_lineno; /**< The line count. */ + int yy_bs_column; /**< The column count. */ + + /* Whether to try to fill the input buffer when we reach the + * end of it. + */ + int yy_fill_buffer; + + int yy_buffer_status; + +#define YY_BUFFER_NEW 0 +#define YY_BUFFER_NORMAL 1 + /* When an EOF's been seen but there's still some text to process + * then we mark the buffer as YY_EOF_PENDING, to indicate that we + * shouldn't try reading from the input source any more. We might + * still have a bunch of tokens to match, though, because of + * possible backing-up. + * + * When we actually see the EOF, we change the status to "new" + * (via yyrestart()), so that the user can continue scanning by + * just pointing yyin at a new input file. + */ +#define YY_BUFFER_EOF_PENDING 2 + + }; +#endif /* !YY_STRUCT_YY_BUFFER_STATE */ + +/* We provide macros for accessing buffer states in case in the + * future we want to put the buffer states in a more general + * "scanner state". + * + * Returns the top of the stack, or NULL. + */ +#define YY_CURRENT_BUFFER ( yyg->yy_buffer_stack \ + ? yyg->yy_buffer_stack[yyg->yy_buffer_stack_top] \ + : NULL) +/* Same as previous macro, but useful when we know that the buffer stack is not + * NULL or when we need an lvalue. For internal use only. + */ +#define YY_CURRENT_BUFFER_LVALUE yyg->yy_buffer_stack[yyg->yy_buffer_stack_top] + +void yyrestart ( FILE *input_file , yyscan_t yyscanner ); +void yy_switch_to_buffer ( YY_BUFFER_STATE new_buffer , yyscan_t yyscanner ); +YY_BUFFER_STATE yy_create_buffer ( FILE *file, int size , yyscan_t yyscanner ); +void yy_delete_buffer ( YY_BUFFER_STATE b , yyscan_t yyscanner ); +void yy_flush_buffer ( YY_BUFFER_STATE b , yyscan_t yyscanner ); +void yypush_buffer_state ( YY_BUFFER_STATE new_buffer , yyscan_t yyscanner ); +void yypop_buffer_state ( yyscan_t yyscanner ); + +static void yyensure_buffer_stack ( yyscan_t yyscanner ); +static void yy_load_buffer_state ( yyscan_t yyscanner ); +static void yy_init_buffer ( YY_BUFFER_STATE b, FILE *file , yyscan_t yyscanner ); +#define YY_FLUSH_BUFFER yy_flush_buffer( YY_CURRENT_BUFFER , yyscanner) + +YY_BUFFER_STATE yy_scan_buffer ( char *base, yy_size_t size , yyscan_t yyscanner ); +YY_BUFFER_STATE yy_scan_string ( const char *yy_str , yyscan_t yyscanner ); +YY_BUFFER_STATE yy_scan_bytes ( const char *bytes, int len , yyscan_t yyscanner ); + +void *yyalloc ( yy_size_t , yyscan_t yyscanner ); +void *yyrealloc ( void *, yy_size_t , yyscan_t yyscanner ); +void yyfree ( void * , yyscan_t yyscanner ); + +#define yy_new_buffer yy_create_buffer +#define yy_set_interactive(is_interactive) \ + { \ + if ( ! YY_CURRENT_BUFFER ){ \ + yyensure_buffer_stack (yyscanner); \ + YY_CURRENT_BUFFER_LVALUE = \ + yy_create_buffer( yyin, YY_BUF_SIZE , yyscanner); \ + } \ + YY_CURRENT_BUFFER_LVALUE->yy_is_interactive = is_interactive; \ + } +#define yy_set_bol(at_bol) \ + { \ + if ( ! YY_CURRENT_BUFFER ){\ + yyensure_buffer_stack (yyscanner); \ + YY_CURRENT_BUFFER_LVALUE = \ + yy_create_buffer( yyin, YY_BUF_SIZE , yyscanner); \ + } \ + YY_CURRENT_BUFFER_LVALUE->yy_at_bol = at_bol; \ + } +#define YY_AT_BOL() (YY_CURRENT_BUFFER_LVALUE->yy_at_bol) + +/* Begin user sect3 */ +typedef flex_uint8_t YY_CHAR; + +typedef int yy_state_type; + +#define yytext_ptr yytext_r + +static yy_state_type yy_get_previous_state ( yyscan_t yyscanner ); +static yy_state_type yy_try_NUL_trans ( yy_state_type current_state , yyscan_t yyscanner); +static int yy_get_next_buffer ( yyscan_t yyscanner ); +static void yynoreturn yy_fatal_error ( const char* msg , yyscan_t yyscanner ); + +/* Done after the current pattern has been matched and before the + * corresponding action - sets up yytext. + */ +#define YY_DO_BEFORE_ACTION \ + yyg->yytext_ptr = yy_bp; \ + yyleng = (int) (yy_cp - yy_bp); \ + yyg->yy_hold_char = *yy_cp; \ + *yy_cp = '\0'; \ + yyg->yy_c_buf_p = yy_cp; +#define YY_NUM_RULES 24 +#define YY_END_OF_BUFFER 25 +/* This struct is not used in this scanner, + but its presence is necessary. */ +struct yy_trans_info + { + flex_int32_t yy_verify; + flex_int32_t yy_nxt; + }; +static const flex_int16_t yy_accept[72] = + { 0, + 0, 0, 25, 23, 21, 22, 20, 23, 19, 20, + 12, 12, 19, 20, 19, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 23, 23, 19, 13, 0, 9, + 17, 12, 0, 14, 16, 15, 10, 10, 2, 10, + 10, 10, 5, 10, 10, 10, 10, 18, 11, 10, + 10, 10, 10, 10, 10, 7, 3, 4, 10, 0, + 10, 10, 0, 6, 1, 0, 0, 0, 0, 8, + 0 + } ; + +static const YY_CHAR yy_ec[256] = + { 0, + 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 2, 4, 5, 1, 1, 1, 6, 1, 7, + 7, 7, 7, 7, 8, 9, 7, 10, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 1, 1, 12, + 13, 14, 1, 1, 15, 16, 17, 16, 18, 19, + 20, 21, 22, 23, 20, 24, 25, 26, 27, 20, + 20, 28, 29, 30, 20, 20, 31, 32, 33, 20, + 1, 34, 1, 1, 20, 1, 35, 16, 36, 16, + + 37, 38, 20, 39, 40, 41, 20, 42, 43, 44, + 45, 20, 20, 46, 47, 48, 20, 20, 49, 50, + 51, 20, 1, 52, 1, 53, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1 + } ; + +static const YY_CHAR yy_meta[54] = + { 0, + 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, + 3, 1, 1, 1, 4, 4, 4, 4, 4, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 6, 5, 1, 4, 4, 4, 4, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, + 5, 1, 1 + } ; + +static const flex_int16_t yy_base[77] = + { 0, + 0, 0, 94, 180, 180, 180, 58, 62, 59, 180, + 22, 23, 51, 50, 49, 64, 102, 41, 31, 0, + 33, 40, 52, 47, 0, 9, 180, 180, 53, 180, + 180, 37, 0, 180, 180, 180, 0, 0, 0, 59, + 63, 69, 0, 68, 71, 79, 0, 180, 0, 74, + 80, 81, 97, 49, 96, 0, 0, 0, 109, 101, + 111, 101, 113, 0, 0, 114, 106, 118, 111, 180, + 180, 159, 163, 168, 171, 175 + } ; + +static const flex_int16_t yy_def[77] = + { 0, + 71, 1, 71, 71, 71, 71, 71, 72, 71, 71, + 73, 73, 71, 71, 71, 74, 74, 17, 17, 74, + 74, 74, 74, 74, 75, 71, 71, 71, 72, 71, + 71, 73, 76, 71, 71, 71, 74, 17, 74, 17, + 74, 74, 74, 74, 74, 74, 74, 71, 76, 74, + 74, 74, 74, 74, 74, 74, 74, 74, 74, 71, + 74, 74, 71, 74, 74, 71, 71, 71, 71, 71, + 0, 71, 71, 71, 71, 71 + } ; + +static const flex_int16_t yy_nxt[234] = + { 0, + 4, 5, 6, 7, 8, 9, 10, 10, 4, 11, + 12, 13, 14, 15, 16, 17, 18, 17, 19, 20, + 20, 20, 21, 20, 20, 20, 22, 20, 23, 20, + 24, 20, 20, 25, 16, 18, 17, 19, 20, 20, + 21, 20, 20, 20, 22, 20, 23, 20, 24, 20, + 20, 26, 27, 33, 71, 40, 60, 30, 41, 42, + 48, 36, 35, 34, 31, 43, 30, 46, 71, 44, + 28, 33, 71, 38, 38, 40, 41, 42, 38, 38, + 38, 38, 38, 43, 45, 46, 71, 50, 44, 51, + 52, 53, 39, 71, 71, 54, 55, 71, 38, 38, + + 38, 38, 45, 56, 57, 50, 58, 51, 52, 53, + 39, 38, 38, 54, 59, 55, 38, 38, 38, 38, + 38, 56, 57, 61, 58, 62, 71, 63, 64, 71, + 65, 66, 67, 59, 68, 69, 38, 38, 38, 38, + 70, 61, 71, 71, 62, 63, 71, 64, 65, 71, + 66, 67, 68, 71, 69, 71, 71, 71, 70, 29, + 29, 29, 29, 29, 29, 32, 32, 71, 32, 37, + 37, 37, 37, 37, 47, 47, 47, 49, 49, 3, + 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, + 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, + + 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, + 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, + 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, + 71, 71, 71 + } ; + +static const flex_int16_t yy_chk[234] = + { 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 11, 12, 18, 54, 29, 19, 21, + 26, 15, 14, 13, 9, 22, 8, 24, 32, 23, + 7, 11, 12, 16, 16, 18, 19, 21, 16, 16, + 16, 16, 16, 22, 23, 24, 32, 40, 23, 41, + 42, 44, 16, 3, 0, 45, 46, 0, 16, 16, + + 16, 16, 23, 50, 51, 40, 52, 41, 42, 44, + 16, 17, 17, 45, 53, 46, 17, 17, 17, 17, + 17, 50, 51, 55, 52, 59, 0, 60, 61, 0, + 62, 63, 66, 53, 67, 68, 17, 17, 17, 17, + 69, 55, 0, 0, 59, 60, 0, 61, 62, 0, + 63, 66, 67, 0, 68, 0, 0, 0, 69, 72, + 72, 72, 72, 72, 72, 73, 73, 0, 73, 74, + 74, 74, 74, 74, 75, 75, 75, 76, 76, 71, + 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, + 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, + + 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, + 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, + 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, + 71, 71, 71 + } ; + +/* The intent behind this definition is that it'll catch + * any uses of REJECT which flex missed. + */ +#define REJECT reject_used_but_not_detected +#define yymore() yymore_used_but_not_detected +#define YY_MORE_ADJ 0 +#define YY_RESTORE_YY_MORE_OFFSET +#line 1 "sqlhist.l" +#line 2 "sqlhist.l" +/* code here */ + +#include +#include "sqlhist-parse.h" + +extern int my_yyinput(void *extra, char *buf, int max); + +#undef YY_INPUT +#define YY_INPUT(b, r, m) ({r = my_yyinput(yyextra, b, m);}) + +#define YY_NO_INPUT +#define YY_NO_UNPUT + +#define YY_EXTRA_TYPE struct sqlhist_bison * + +#define yytext yyg->yytext_r + +#define TRACE_SB ((struct sqlhist_bison *)yyextra) +#define HANDLE_COLUMN do { TRACE_SB->line_idx += strlen(yytext); } while (0) + +#line 527 "sqlhist-lex.c" +#line 528 "sqlhist-lex.c" + +#define INITIAL 0 + +#ifndef YY_NO_UNISTD_H +/* Special case for "unistd.h", since it is non-ANSI. We include it way + * down here because we want the user's section 1 to have been scanned first. + * The user has a chance to override it with an option. + */ +#include +#endif + +#ifndef YY_EXTRA_TYPE +#define YY_EXTRA_TYPE void * +#endif + +/* Holds the entire state of the reentrant scanner. */ +struct yyguts_t + { + + /* User-defined. Not touched by flex. */ + YY_EXTRA_TYPE yyextra_r; + + /* The rest are the same as the globals declared in the non-reentrant scanner. */ + FILE *yyin_r, *yyout_r; + size_t yy_buffer_stack_top; /**< index of top of stack. */ + size_t yy_buffer_stack_max; /**< capacity of stack. */ + YY_BUFFER_STATE * yy_buffer_stack; /**< Stack as an array. */ + char yy_hold_char; + int yy_n_chars; + int yyleng_r; + char *yy_c_buf_p; + int yy_init; + int yy_start; + int yy_did_buffer_switch_on_eof; + int yy_start_stack_ptr; + int yy_start_stack_depth; + int *yy_start_stack; + yy_state_type yy_last_accepting_state; + char* yy_last_accepting_cpos; + + int yylineno_r; + int yy_flex_debug_r; + + char *yytext_r; + int yy_more_flag; + int yy_more_len; + + YYSTYPE * yylval_r; + + }; /* end struct yyguts_t */ + +static int yy_init_globals ( yyscan_t yyscanner ); + + /* This must go here because YYSTYPE and YYLTYPE are included + * from bison output in section 1.*/ + # define yylval yyg->yylval_r + +int yylex_init (yyscan_t* scanner); + +int yylex_init_extra ( YY_EXTRA_TYPE user_defined, yyscan_t* scanner); + +/* Accessor methods to globals. + These are made visible to non-reentrant scanners for convenience. */ + +int yylex_destroy ( yyscan_t yyscanner ); + +int yyget_debug ( yyscan_t yyscanner ); + +void yyset_debug ( int debug_flag , yyscan_t yyscanner ); + +YY_EXTRA_TYPE yyget_extra ( yyscan_t yyscanner ); + +void yyset_extra ( YY_EXTRA_TYPE user_defined , yyscan_t yyscanner ); + +FILE *yyget_in ( yyscan_t yyscanner ); + +void yyset_in ( FILE * _in_str , yyscan_t yyscanner ); + +FILE *yyget_out ( yyscan_t yyscanner ); + +void yyset_out ( FILE * _out_str , yyscan_t yyscanner ); + + int yyget_leng ( yyscan_t yyscanner ); + +char *yyget_text ( yyscan_t yyscanner ); + +int yyget_lineno ( yyscan_t yyscanner ); + +void yyset_lineno ( int _line_number , yyscan_t yyscanner ); + +int yyget_column ( yyscan_t yyscanner ); + +void yyset_column ( int _column_no , yyscan_t yyscanner ); + +YYSTYPE * yyget_lval ( yyscan_t yyscanner ); + +void yyset_lval ( YYSTYPE * yylval_param , yyscan_t yyscanner ); + +/* Macros after this point can all be overridden by user definitions in + * section 1. + */ + +#ifndef YY_SKIP_YYWRAP +#ifdef __cplusplus +extern "C" int yywrap ( yyscan_t yyscanner ); +#else +extern int yywrap ( yyscan_t yyscanner ); +#endif +#endif + +#ifndef YY_NO_UNPUT + + static void yyunput ( int c, char *buf_ptr , yyscan_t yyscanner); + +#endif + +#ifndef yytext_ptr +static void yy_flex_strncpy ( char *, const char *, int , yyscan_t yyscanner); +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen ( const char * , yyscan_t yyscanner); +#endif + +#ifndef YY_NO_INPUT +#ifdef __cplusplus +static int yyinput ( yyscan_t yyscanner ); +#else +static int input ( yyscan_t yyscanner ); +#endif + +#endif + +/* Amount of stuff to slurp up with each read. */ +#ifndef YY_READ_BUF_SIZE +#ifdef __ia64__ +/* On IA-64, the buffer size is 16k, not 8k */ +#define YY_READ_BUF_SIZE 16384 +#else +#define YY_READ_BUF_SIZE 8192 +#endif /* __ia64__ */ +#endif + +/* Copy whatever the last rule matched to the standard output. */ +#ifndef ECHO +/* This used to be an fputs(), but since the string might contain NUL's, + * we now use fwrite(). + */ +#define ECHO do { if (fwrite( yytext, (size_t) yyleng, 1, yyout )) {} } while (0) +#endif + +/* Gets input and stuffs it into "buf". number of characters read, or YY_NULL, + * is returned in "result". + */ +#ifndef YY_INPUT +#define YY_INPUT(buf,result,max_size) \ + if ( YY_CURRENT_BUFFER_LVALUE->yy_is_interactive ) \ + { \ + int c = '*'; \ + int n; \ + for ( n = 0; n < max_size && \ + (c = getc( yyin )) != EOF && c != '\n'; ++n ) \ + buf[n] = (char) c; \ + if ( c == '\n' ) \ + buf[n++] = (char) c; \ + if ( c == EOF && ferror( yyin ) ) \ + YY_FATAL_ERROR( "input in flex scanner failed" ); \ + result = n; \ + } \ + else \ + { \ + errno=0; \ + while ( (result = (int) fread(buf, 1, (yy_size_t) max_size, yyin)) == 0 && ferror(yyin)) \ + { \ + if( errno != EINTR) \ + { \ + YY_FATAL_ERROR( "input in flex scanner failed" ); \ + break; \ + } \ + errno=0; \ + clearerr(yyin); \ + } \ + }\ +\ + +#endif + +/* No semi-colon after return; correct usage is to write "yyterminate();" - + * we don't want an extra ';' after the "return" because that will cause + * some compilers to complain about unreachable statements. + */ +#ifndef yyterminate +#define yyterminate() return YY_NULL +#endif + +/* Number of entries by which start-condition stack grows. */ +#ifndef YY_START_STACK_INCR +#define YY_START_STACK_INCR 25 +#endif + +/* Report a fatal error. */ +#ifndef YY_FATAL_ERROR +#define YY_FATAL_ERROR(msg) yy_fatal_error( msg , yyscanner) +#endif + +/* end tables serialization structures and prototypes */ + +/* Default declaration of generated scanner - a define so the user can + * easily add parameters. + */ +#ifndef YY_DECL +#define YY_DECL_IS_OURS 1 + +extern int yylex \ + (YYSTYPE * yylval_param , yyscan_t yyscanner); + +#define YY_DECL int yylex \ + (YYSTYPE * yylval_param , yyscan_t yyscanner) +#endif /* !YY_DECL */ + +/* Code executed at the beginning of each rule, after yytext and yyleng + * have been set up. + */ +#ifndef YY_USER_ACTION +#define YY_USER_ACTION +#endif + +/* Code executed at the end of each rule. */ +#ifndef YY_BREAK +#define YY_BREAK /*LINTED*/break; +#endif + +#define YY_RULE_SETUP \ + YY_USER_ACTION + +/** The main scanner function which does all the work. + */ +YY_DECL +{ + yy_state_type yy_current_state; + char *yy_cp, *yy_bp; + int yy_act; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + yylval = yylval_param; + + if ( !yyg->yy_init ) + { + yyg->yy_init = 1; + +#ifdef YY_USER_INIT + YY_USER_INIT; +#endif + + if ( ! yyg->yy_start ) + yyg->yy_start = 1; /* first start state */ + + if ( ! yyin ) + yyin = stdin; + + if ( ! yyout ) + yyout = stdout; + + if ( ! YY_CURRENT_BUFFER ) { + yyensure_buffer_stack (yyscanner); + YY_CURRENT_BUFFER_LVALUE = + yy_create_buffer( yyin, YY_BUF_SIZE , yyscanner); + } + + yy_load_buffer_state( yyscanner ); + } + + { +#line 33 "sqlhist.l" + + +#line 805 "sqlhist-lex.c" + + while ( /*CONSTCOND*/1 ) /* loops until end-of-file is reached */ + { + yy_cp = yyg->yy_c_buf_p; + + /* Support of yytext. */ + *yy_cp = yyg->yy_hold_char; + + /* yy_bp points to the position in yy_ch_buf of the start of + * the current run. + */ + yy_bp = yy_cp; + + yy_current_state = yyg->yy_start; +yy_match: + do + { + YY_CHAR yy_c = yy_ec[YY_SC_TO_UI(*yy_cp)] ; + if ( yy_accept[yy_current_state] ) + { + yyg->yy_last_accepting_state = yy_current_state; + yyg->yy_last_accepting_cpos = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 72 ) + yy_c = yy_meta[yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c]; + ++yy_cp; + } + while ( yy_base[yy_current_state] != 180 ); + +yy_find_action: + yy_act = yy_accept[yy_current_state]; + if ( yy_act == 0 ) + { /* have to back up */ + yy_cp = yyg->yy_last_accepting_cpos; + yy_current_state = yyg->yy_last_accepting_state; + yy_act = yy_accept[yy_current_state]; + } + + YY_DO_BEFORE_ACTION; + +do_action: /* This label is used only to access EOF actions. */ + + switch ( yy_act ) + { /* beginning of action switch */ + case 0: /* must back up */ + /* undo the effects of YY_DO_BEFORE_ACTION */ + *yy_cp = yyg->yy_hold_char; + yy_cp = yyg->yy_last_accepting_cpos; + yy_current_state = yyg->yy_last_accepting_state; + goto yy_find_action; + +case 1: +YY_RULE_SETUP +#line 35 "sqlhist.l" +{ HANDLE_COLUMN; return SELECT; } + YY_BREAK +case 2: +YY_RULE_SETUP +#line 36 "sqlhist.l" +{ HANDLE_COLUMN; return AS; } + YY_BREAK +case 3: +YY_RULE_SETUP +#line 37 "sqlhist.l" +{ HANDLE_COLUMN; return FROM; } + YY_BREAK +case 4: +YY_RULE_SETUP +#line 38 "sqlhist.l" +{ HANDLE_COLUMN; return JOIN; } + YY_BREAK +case 5: +YY_RULE_SETUP +#line 39 "sqlhist.l" +{ HANDLE_COLUMN; return ON; } + YY_BREAK +case 6: +YY_RULE_SETUP +#line 40 "sqlhist.l" +{ HANDLE_COLUMN; return WHERE; } + YY_BREAK +case 7: +YY_RULE_SETUP +#line 41 "sqlhist.l" +{ HANDLE_COLUMN; return CAST; } + YY_BREAK +case 8: +YY_RULE_SETUP +#line 43 "sqlhist.l" +{ + HANDLE_COLUMN; + yylval->string = store_str(TRACE_SB, yyg->yytext_r); + return FIELD; +} + YY_BREAK +case 9: +/* rule 9 can match eol */ +YY_RULE_SETUP +#line 49 "sqlhist.l" +{ + HANDLE_COLUMN; + yylval->string = store_str(TRACE_SB, yyg->yytext_r); + return STRING; +} + YY_BREAK +case 10: +YY_RULE_SETUP +#line 55 "sqlhist.l" +{ + const char *str = yyg->yytext_r; + HANDLE_COLUMN; + if (str[0] == '\\') { str++; }; + yylval->string = store_str(TRACE_SB, str); + return FIELD; +} + YY_BREAK +case 11: +YY_RULE_SETUP +#line 63 "sqlhist.l" +{ + HANDLE_COLUMN; + yylval->number = strtol(yyg->yytext_r, NULL, 0); + return NUMBER; +} + YY_BREAK +case 12: +YY_RULE_SETUP +#line 69 "sqlhist.l" +{ + HANDLE_COLUMN; + yylval->number = strtol(yyg->yytext_r, NULL, 0); + return NUMBER; +} + YY_BREAK +case 13: +YY_RULE_SETUP +#line 75 "sqlhist.l" +{ HANDLE_COLUMN; return NEQ; } + YY_BREAK +case 14: +YY_RULE_SETUP +#line 76 "sqlhist.l" +{ HANDLE_COLUMN; return LE; } + YY_BREAK +case 15: +YY_RULE_SETUP +#line 77 "sqlhist.l" +{ HANDLE_COLUMN; return GE; } + YY_BREAK +case 16: +YY_RULE_SETUP +#line 78 "sqlhist.l" +{ HANDLE_COLUMN; return EQ; } + YY_BREAK +case 17: +YY_RULE_SETUP +#line 79 "sqlhist.l" +{ HANDLE_COLUMN; return AND; } + YY_BREAK +case 18: +YY_RULE_SETUP +#line 80 "sqlhist.l" +{ HANDLE_COLUMN; return OR; } + YY_BREAK +case 19: +YY_RULE_SETUP +#line 81 "sqlhist.l" +{ HANDLE_COLUMN; return yytext[0]; } + YY_BREAK +case 20: +YY_RULE_SETUP +#line 83 "sqlhist.l" +{ HANDLE_COLUMN; return yytext[0]; } + YY_BREAK +case 21: +YY_RULE_SETUP +#line 85 "sqlhist.l" +{ HANDLE_COLUMN; } + YY_BREAK +case 22: +/* rule 22 can match eol */ +YY_RULE_SETUP +#line 86 "sqlhist.l" +{ TRACE_SB->line_idx = 0; TRACE_SB->line_no++; } + YY_BREAK +case 23: +YY_RULE_SETUP +#line 88 "sqlhist.l" +{ HANDLE_COLUMN; return PARSE_ERROR; } + YY_BREAK +case 24: +YY_RULE_SETUP +#line 89 "sqlhist.l" +ECHO; + YY_BREAK +#line 1006 "sqlhist-lex.c" +case YY_STATE_EOF(INITIAL): + yyterminate(); + + case YY_END_OF_BUFFER: + { + /* Amount of text matched not including the EOB char. */ + int yy_amount_of_matched_text = (int) (yy_cp - yyg->yytext_ptr) - 1; + + /* Undo the effects of YY_DO_BEFORE_ACTION. */ + *yy_cp = yyg->yy_hold_char; + YY_RESTORE_YY_MORE_OFFSET + + if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_NEW ) + { + /* We're scanning a new file or input source. It's + * possible that this happened because the user + * just pointed yyin at a new source and called + * yylex(). If so, then we have to assure + * consistency between YY_CURRENT_BUFFER and our + * globals. Here is the right place to do so, because + * this is the first action (other than possibly a + * back-up) that will match for the new input source. + */ + yyg->yy_n_chars = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; + YY_CURRENT_BUFFER_LVALUE->yy_input_file = yyin; + YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = YY_BUFFER_NORMAL; + } + + /* Note that here we test for yy_c_buf_p "<=" to the position + * of the first EOB in the buffer, since yy_c_buf_p will + * already have been incremented past the NUL character + * (since all states make transitions on EOB to the + * end-of-buffer state). Contrast this with the test + * in input(). + */ + if ( yyg->yy_c_buf_p <= &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] ) + { /* This was really a NUL. */ + yy_state_type yy_next_state; + + yyg->yy_c_buf_p = yyg->yytext_ptr + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state( yyscanner ); + + /* Okay, we're now positioned to make the NUL + * transition. We couldn't have + * yy_get_previous_state() go ahead and do it + * for us because it doesn't know how to deal + * with the possibility of jamming (and we don't + * want to build jamming into it because then it + * will run more slowly). + */ + + yy_next_state = yy_try_NUL_trans( yy_current_state , yyscanner); + + yy_bp = yyg->yytext_ptr + YY_MORE_ADJ; + + if ( yy_next_state ) + { + /* Consume the NUL. */ + yy_cp = ++yyg->yy_c_buf_p; + yy_current_state = yy_next_state; + goto yy_match; + } + + else + { + yy_cp = yyg->yy_c_buf_p; + goto yy_find_action; + } + } + + else switch ( yy_get_next_buffer( yyscanner ) ) + { + case EOB_ACT_END_OF_FILE: + { + yyg->yy_did_buffer_switch_on_eof = 0; + + if ( yywrap( yyscanner ) ) + { + /* Note: because we've taken care in + * yy_get_next_buffer() to have set up + * yytext, we can now set up + * yy_c_buf_p so that if some total + * hoser (like flex itself) wants to + * call the scanner after we return the + * YY_NULL, it'll still work - another + * YY_NULL will get returned. + */ + yyg->yy_c_buf_p = yyg->yytext_ptr + YY_MORE_ADJ; + + yy_act = YY_STATE_EOF(YY_START); + goto do_action; + } + + else + { + if ( ! yyg->yy_did_buffer_switch_on_eof ) + YY_NEW_FILE; + } + break; + } + + case EOB_ACT_CONTINUE_SCAN: + yyg->yy_c_buf_p = + yyg->yytext_ptr + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state( yyscanner ); + + yy_cp = yyg->yy_c_buf_p; + yy_bp = yyg->yytext_ptr + YY_MORE_ADJ; + goto yy_match; + + case EOB_ACT_LAST_MATCH: + yyg->yy_c_buf_p = + &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars]; + + yy_current_state = yy_get_previous_state( yyscanner ); + + yy_cp = yyg->yy_c_buf_p; + yy_bp = yyg->yytext_ptr + YY_MORE_ADJ; + goto yy_find_action; + } + break; + } + + default: + YY_FATAL_ERROR( + "fatal flex scanner internal error--no action found" ); + } /* end of action switch */ + } /* end of scanning one token */ + } /* end of user's declarations */ +} /* end of yylex */ + +/* yy_get_next_buffer - try to read in a new buffer + * + * Returns a code representing an action: + * EOB_ACT_LAST_MATCH - + * EOB_ACT_CONTINUE_SCAN - continue scanning from current position + * EOB_ACT_END_OF_FILE - end of file + */ +static int yy_get_next_buffer (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + char *dest = YY_CURRENT_BUFFER_LVALUE->yy_ch_buf; + char *source = yyg->yytext_ptr; + int number_to_move, i; + int ret_val; + + if ( yyg->yy_c_buf_p > &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars + 1] ) + YY_FATAL_ERROR( + "fatal flex scanner internal error--end of buffer missed" ); + + if ( YY_CURRENT_BUFFER_LVALUE->yy_fill_buffer == 0 ) + { /* Don't try to fill the buffer, so this is an EOF. */ + if ( yyg->yy_c_buf_p - yyg->yytext_ptr - YY_MORE_ADJ == 1 ) + { + /* We matched a single character, the EOB, so + * treat this as a final EOF. + */ + return EOB_ACT_END_OF_FILE; + } + + else + { + /* We matched some text prior to the EOB, first + * process it. + */ + return EOB_ACT_LAST_MATCH; + } + } + + /* Try to read more data. */ + + /* First move last chars to start of buffer. */ + number_to_move = (int) (yyg->yy_c_buf_p - yyg->yytext_ptr - 1); + + for ( i = 0; i < number_to_move; ++i ) + *(dest++) = *(source++); + + if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_EOF_PENDING ) + /* don't do the read, it's not guaranteed to return an EOF, + * just force an EOF + */ + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars = 0; + + else + { + int num_to_read = + YY_CURRENT_BUFFER_LVALUE->yy_buf_size - number_to_move - 1; + + while ( num_to_read <= 0 ) + { /* Not enough room in the buffer - grow it. */ + + /* just a shorter name for the current buffer */ + YY_BUFFER_STATE b = YY_CURRENT_BUFFER_LVALUE; + + int yy_c_buf_p_offset = + (int) (yyg->yy_c_buf_p - b->yy_ch_buf); + + if ( b->yy_is_our_buffer ) + { + int new_size = b->yy_buf_size * 2; + + if ( new_size <= 0 ) + b->yy_buf_size += b->yy_buf_size / 8; + else + b->yy_buf_size *= 2; + + b->yy_ch_buf = (char *) + /* Include room in for 2 EOB chars. */ + yyrealloc( (void *) b->yy_ch_buf, + (yy_size_t) (b->yy_buf_size + 2) , yyscanner ); + } + else + /* Can't grow it, we don't own it. */ + b->yy_ch_buf = NULL; + + if ( ! b->yy_ch_buf ) + YY_FATAL_ERROR( + "fatal error - scanner input buffer overflow" ); + + yyg->yy_c_buf_p = &b->yy_ch_buf[yy_c_buf_p_offset]; + + num_to_read = YY_CURRENT_BUFFER_LVALUE->yy_buf_size - + number_to_move - 1; + + } + + if ( num_to_read > YY_READ_BUF_SIZE ) + num_to_read = YY_READ_BUF_SIZE; + + /* Read in more data. */ + YY_INPUT( (&YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]), + yyg->yy_n_chars, num_to_read ); + + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars; + } + + if ( yyg->yy_n_chars == 0 ) + { + if ( number_to_move == YY_MORE_ADJ ) + { + ret_val = EOB_ACT_END_OF_FILE; + yyrestart( yyin , yyscanner); + } + + else + { + ret_val = EOB_ACT_LAST_MATCH; + YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = + YY_BUFFER_EOF_PENDING; + } + } + + else + ret_val = EOB_ACT_CONTINUE_SCAN; + + if ((yyg->yy_n_chars + number_to_move) > YY_CURRENT_BUFFER_LVALUE->yy_buf_size) { + /* Extend the array by 50%, plus the number we really need. */ + int new_size = yyg->yy_n_chars + number_to_move + (yyg->yy_n_chars >> 1); + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf = (char *) yyrealloc( + (void *) YY_CURRENT_BUFFER_LVALUE->yy_ch_buf, (yy_size_t) new_size , yyscanner ); + if ( ! YY_CURRENT_BUFFER_LVALUE->yy_ch_buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_get_next_buffer()" ); + /* "- 2" to take care of EOB's */ + YY_CURRENT_BUFFER_LVALUE->yy_buf_size = (int) (new_size - 2); + } + + yyg->yy_n_chars += number_to_move; + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] = YY_END_OF_BUFFER_CHAR; + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars + 1] = YY_END_OF_BUFFER_CHAR; + + yyg->yytext_ptr = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[0]; + + return ret_val; +} + +/* yy_get_previous_state - get the state just before the EOB char was reached */ + + static yy_state_type yy_get_previous_state (yyscan_t yyscanner) +{ + yy_state_type yy_current_state; + char *yy_cp; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + yy_current_state = yyg->yy_start; + + for ( yy_cp = yyg->yytext_ptr + YY_MORE_ADJ; yy_cp < yyg->yy_c_buf_p; ++yy_cp ) + { + YY_CHAR yy_c = (*yy_cp ? yy_ec[YY_SC_TO_UI(*yy_cp)] : 1); + if ( yy_accept[yy_current_state] ) + { + yyg->yy_last_accepting_state = yy_current_state; + yyg->yy_last_accepting_cpos = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 72 ) + yy_c = yy_meta[yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c]; + } + + return yy_current_state; +} + +/* yy_try_NUL_trans - try to make a transition on the NUL character + * + * synopsis + * next_state = yy_try_NUL_trans( current_state ); + */ + static yy_state_type yy_try_NUL_trans (yy_state_type yy_current_state , yyscan_t yyscanner) +{ + int yy_is_jam; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; /* This var may be unused depending upon options. */ + char *yy_cp = yyg->yy_c_buf_p; + + YY_CHAR yy_c = 1; + if ( yy_accept[yy_current_state] ) + { + yyg->yy_last_accepting_state = yy_current_state; + yyg->yy_last_accepting_cpos = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 72 ) + yy_c = yy_meta[yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c]; + yy_is_jam = (yy_current_state == 71); + + (void)yyg; + return yy_is_jam ? 0 : yy_current_state; +} + +#ifndef YY_NO_UNPUT + + static void yyunput (int c, char * yy_bp , yyscan_t yyscanner) +{ + char *yy_cp; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + yy_cp = yyg->yy_c_buf_p; + + /* undo effects of setting up yytext */ + *yy_cp = yyg->yy_hold_char; + + if ( yy_cp < YY_CURRENT_BUFFER_LVALUE->yy_ch_buf + 2 ) + { /* need to shift things up to make room */ + /* +2 for EOB chars. */ + int number_to_move = yyg->yy_n_chars + 2; + char *dest = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[ + YY_CURRENT_BUFFER_LVALUE->yy_buf_size + 2]; + char *source = + &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]; + + while ( source > YY_CURRENT_BUFFER_LVALUE->yy_ch_buf ) + *--dest = *--source; + + yy_cp += (int) (dest - source); + yy_bp += (int) (dest - source); + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = + yyg->yy_n_chars = (int) YY_CURRENT_BUFFER_LVALUE->yy_buf_size; + + if ( yy_cp < YY_CURRENT_BUFFER_LVALUE->yy_ch_buf + 2 ) + YY_FATAL_ERROR( "flex scanner push-back overflow" ); + } + + *--yy_cp = (char) c; + + yyg->yytext_ptr = yy_bp; + yyg->yy_hold_char = *yy_cp; + yyg->yy_c_buf_p = yy_cp; +} + +#endif + +#ifndef YY_NO_INPUT +#ifdef __cplusplus + static int yyinput (yyscan_t yyscanner) +#else + static int input (yyscan_t yyscanner) +#endif + +{ + int c; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + *yyg->yy_c_buf_p = yyg->yy_hold_char; + + if ( *yyg->yy_c_buf_p == YY_END_OF_BUFFER_CHAR ) + { + /* yy_c_buf_p now points to the character we want to return. + * If this occurs *before* the EOB characters, then it's a + * valid NUL; if not, then we've hit the end of the buffer. + */ + if ( yyg->yy_c_buf_p < &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] ) + /* This was really a NUL. */ + *yyg->yy_c_buf_p = '\0'; + + else + { /* need more input */ + int offset = (int) (yyg->yy_c_buf_p - yyg->yytext_ptr); + ++yyg->yy_c_buf_p; + + switch ( yy_get_next_buffer( yyscanner ) ) + { + case EOB_ACT_LAST_MATCH: + /* This happens because yy_g_n_b() + * sees that we've accumulated a + * token and flags that we need to + * try matching the token before + * proceeding. But for input(), + * there's no matching to consider. + * So convert the EOB_ACT_LAST_MATCH + * to EOB_ACT_END_OF_FILE. + */ + + /* Reset buffer status. */ + yyrestart( yyin , yyscanner); + + /*FALLTHROUGH*/ + + case EOB_ACT_END_OF_FILE: + { + if ( yywrap( yyscanner ) ) + return 0; + + if ( ! yyg->yy_did_buffer_switch_on_eof ) + YY_NEW_FILE; +#ifdef __cplusplus + return yyinput(yyscanner); +#else + return input(yyscanner); +#endif + } + + case EOB_ACT_CONTINUE_SCAN: + yyg->yy_c_buf_p = yyg->yytext_ptr + offset; + break; + } + } + } + + c = *(unsigned char *) yyg->yy_c_buf_p; /* cast for 8-bit char's */ + *yyg->yy_c_buf_p = '\0'; /* preserve yytext */ + yyg->yy_hold_char = *++yyg->yy_c_buf_p; + + return c; +} +#endif /* ifndef YY_NO_INPUT */ + +/** Immediately switch to a different input stream. + * @param input_file A readable stream. + * @param yyscanner The scanner object. + * @note This function does not reset the start condition to @c INITIAL . + */ + void yyrestart (FILE * input_file , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if ( ! YY_CURRENT_BUFFER ){ + yyensure_buffer_stack (yyscanner); + YY_CURRENT_BUFFER_LVALUE = + yy_create_buffer( yyin, YY_BUF_SIZE , yyscanner); + } + + yy_init_buffer( YY_CURRENT_BUFFER, input_file , yyscanner); + yy_load_buffer_state( yyscanner ); +} + +/** Switch to a different input buffer. + * @param new_buffer The new input buffer. + * @param yyscanner The scanner object. + */ + void yy_switch_to_buffer (YY_BUFFER_STATE new_buffer , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + /* TODO. We should be able to replace this entire function body + * with + * yypop_buffer_state(); + * yypush_buffer_state(new_buffer); + */ + yyensure_buffer_stack (yyscanner); + if ( YY_CURRENT_BUFFER == new_buffer ) + return; + + if ( YY_CURRENT_BUFFER ) + { + /* Flush out information for old buffer. */ + *yyg->yy_c_buf_p = yyg->yy_hold_char; + YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = yyg->yy_c_buf_p; + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars; + } + + YY_CURRENT_BUFFER_LVALUE = new_buffer; + yy_load_buffer_state( yyscanner ); + + /* We don't actually know whether we did this switch during + * EOF (yywrap()) processing, but the only time this flag + * is looked at is after yywrap() is called, so it's safe + * to go ahead and always set it. + */ + yyg->yy_did_buffer_switch_on_eof = 1; +} + +static void yy_load_buffer_state (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yyg->yy_n_chars = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; + yyg->yytext_ptr = yyg->yy_c_buf_p = YY_CURRENT_BUFFER_LVALUE->yy_buf_pos; + yyin = YY_CURRENT_BUFFER_LVALUE->yy_input_file; + yyg->yy_hold_char = *yyg->yy_c_buf_p; +} + +/** Allocate and initialize an input buffer state. + * @param file A readable stream. + * @param size The character buffer size in bytes. When in doubt, use @c YY_BUF_SIZE. + * @param yyscanner The scanner object. + * @return the allocated buffer state. + */ + YY_BUFFER_STATE yy_create_buffer (FILE * file, int size , yyscan_t yyscanner) +{ + YY_BUFFER_STATE b; + + b = (YY_BUFFER_STATE) yyalloc( sizeof( struct yy_buffer_state ) , yyscanner ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" ); + + b->yy_buf_size = size; + + /* yy_ch_buf has to be 2 characters longer than the size given because + * we need to put in 2 end-of-buffer characters. + */ + b->yy_ch_buf = (char *) yyalloc( (yy_size_t) (b->yy_buf_size + 2) , yyscanner ); + if ( ! b->yy_ch_buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" ); + + b->yy_is_our_buffer = 1; + + yy_init_buffer( b, file , yyscanner); + + return b; +} + +/** Destroy the buffer. + * @param b a buffer created with yy_create_buffer() + * @param yyscanner The scanner object. + */ + void yy_delete_buffer (YY_BUFFER_STATE b , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if ( ! b ) + return; + + if ( b == YY_CURRENT_BUFFER ) /* Not sure if we should pop here. */ + YY_CURRENT_BUFFER_LVALUE = (YY_BUFFER_STATE) 0; + + if ( b->yy_is_our_buffer ) + yyfree( (void *) b->yy_ch_buf , yyscanner ); + + yyfree( (void *) b , yyscanner ); +} + +/* Initializes or reinitializes a buffer. + * This function is sometimes called more than once on the same buffer, + * such as during a yyrestart() or at EOF. + */ + static void yy_init_buffer (YY_BUFFER_STATE b, FILE * file , yyscan_t yyscanner) + +{ + int oerrno = errno; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + yy_flush_buffer( b , yyscanner); + + b->yy_input_file = file; + b->yy_fill_buffer = 1; + + /* If b is the current buffer, then yy_init_buffer was _probably_ + * called from yyrestart() or through yy_get_next_buffer. + * In that case, we don't want to reset the lineno or column. + */ + if (b != YY_CURRENT_BUFFER){ + b->yy_bs_lineno = 1; + b->yy_bs_column = 0; + } + + b->yy_is_interactive = file ? (isatty( fileno(file) ) > 0) : 0; + + errno = oerrno; +} + +/** Discard all buffered characters. On the next scan, YY_INPUT will be called. + * @param b the buffer state to be flushed, usually @c YY_CURRENT_BUFFER. + * @param yyscanner The scanner object. + */ + void yy_flush_buffer (YY_BUFFER_STATE b , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + if ( ! b ) + return; + + b->yy_n_chars = 0; + + /* We always need two end-of-buffer characters. The first causes + * a transition to the end-of-buffer state. The second causes + * a jam in that state. + */ + b->yy_ch_buf[0] = YY_END_OF_BUFFER_CHAR; + b->yy_ch_buf[1] = YY_END_OF_BUFFER_CHAR; + + b->yy_buf_pos = &b->yy_ch_buf[0]; + + b->yy_at_bol = 1; + b->yy_buffer_status = YY_BUFFER_NEW; + + if ( b == YY_CURRENT_BUFFER ) + yy_load_buffer_state( yyscanner ); +} + +/** Pushes the new state onto the stack. The new state becomes + * the current state. This function will allocate the stack + * if necessary. + * @param new_buffer The new state. + * @param yyscanner The scanner object. + */ +void yypush_buffer_state (YY_BUFFER_STATE new_buffer , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + if (new_buffer == NULL) + return; + + yyensure_buffer_stack(yyscanner); + + /* This block is copied from yy_switch_to_buffer. */ + if ( YY_CURRENT_BUFFER ) + { + /* Flush out information for old buffer. */ + *yyg->yy_c_buf_p = yyg->yy_hold_char; + YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = yyg->yy_c_buf_p; + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars; + } + + /* Only push if top exists. Otherwise, replace top. */ + if (YY_CURRENT_BUFFER) + yyg->yy_buffer_stack_top++; + YY_CURRENT_BUFFER_LVALUE = new_buffer; + + /* copied from yy_switch_to_buffer. */ + yy_load_buffer_state( yyscanner ); + yyg->yy_did_buffer_switch_on_eof = 1; +} + +/** Removes and deletes the top of the stack, if present. + * The next element becomes the new top. + * @param yyscanner The scanner object. + */ +void yypop_buffer_state (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + if (!YY_CURRENT_BUFFER) + return; + + yy_delete_buffer(YY_CURRENT_BUFFER , yyscanner); + YY_CURRENT_BUFFER_LVALUE = NULL; + if (yyg->yy_buffer_stack_top > 0) + --yyg->yy_buffer_stack_top; + + if (YY_CURRENT_BUFFER) { + yy_load_buffer_state( yyscanner ); + yyg->yy_did_buffer_switch_on_eof = 1; + } +} + +/* Allocates the stack if it does not exist. + * Guarantees space for at least one push. + */ +static void yyensure_buffer_stack (yyscan_t yyscanner) +{ + yy_size_t num_to_alloc; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if (!yyg->yy_buffer_stack) { + + /* First allocation is just for 2 elements, since we don't know if this + * scanner will even need a stack. We use 2 instead of 1 to avoid an + * immediate realloc on the next call. + */ + num_to_alloc = 1; /* After all that talk, this was set to 1 anyways... */ + yyg->yy_buffer_stack = (struct yy_buffer_state**)yyalloc + (num_to_alloc * sizeof(struct yy_buffer_state*) + , yyscanner); + if ( ! yyg->yy_buffer_stack ) + YY_FATAL_ERROR( "out of dynamic memory in yyensure_buffer_stack()" ); + + memset(yyg->yy_buffer_stack, 0, num_to_alloc * sizeof(struct yy_buffer_state*)); + + yyg->yy_buffer_stack_max = num_to_alloc; + yyg->yy_buffer_stack_top = 0; + return; + } + + if (yyg->yy_buffer_stack_top >= (yyg->yy_buffer_stack_max) - 1){ + + /* Increase the buffer to prepare for a possible push. */ + yy_size_t grow_size = 8 /* arbitrary grow size */; + + num_to_alloc = yyg->yy_buffer_stack_max + grow_size; + yyg->yy_buffer_stack = (struct yy_buffer_state**)yyrealloc + (yyg->yy_buffer_stack, + num_to_alloc * sizeof(struct yy_buffer_state*) + , yyscanner); + if ( ! yyg->yy_buffer_stack ) + YY_FATAL_ERROR( "out of dynamic memory in yyensure_buffer_stack()" ); + + /* zero only the new slots.*/ + memset(yyg->yy_buffer_stack + yyg->yy_buffer_stack_max, 0, grow_size * sizeof(struct yy_buffer_state*)); + yyg->yy_buffer_stack_max = num_to_alloc; + } +} + +/** Setup the input buffer state to scan directly from a user-specified character buffer. + * @param base the character buffer + * @param size the size in bytes of the character buffer + * @param yyscanner The scanner object. + * @return the newly allocated buffer state object. + */ +YY_BUFFER_STATE yy_scan_buffer (char * base, yy_size_t size , yyscan_t yyscanner) +{ + YY_BUFFER_STATE b; + + if ( size < 2 || + base[size-2] != YY_END_OF_BUFFER_CHAR || + base[size-1] != YY_END_OF_BUFFER_CHAR ) + /* They forgot to leave room for the EOB's. */ + return NULL; + + b = (YY_BUFFER_STATE) yyalloc( sizeof( struct yy_buffer_state ) , yyscanner ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in yy_scan_buffer()" ); + + b->yy_buf_size = (int) (size - 2); /* "- 2" to take care of EOB's */ + b->yy_buf_pos = b->yy_ch_buf = base; + b->yy_is_our_buffer = 0; + b->yy_input_file = NULL; + b->yy_n_chars = b->yy_buf_size; + b->yy_is_interactive = 0; + b->yy_at_bol = 1; + b->yy_fill_buffer = 0; + b->yy_buffer_status = YY_BUFFER_NEW; + + yy_switch_to_buffer( b , yyscanner ); + + return b; +} + +/** Setup the input buffer state to scan a string. The next call to yylex() will + * scan from a @e copy of @a str. + * @param yystr a NUL-terminated string to scan + * @param yyscanner The scanner object. + * @return the newly allocated buffer state object. + * @note If you want to scan bytes that may contain NUL values, then use + * yy_scan_bytes() instead. + */ +YY_BUFFER_STATE yy_scan_string (const char * yystr , yyscan_t yyscanner) +{ + + return yy_scan_bytes( yystr, (int) strlen(yystr) , yyscanner); +} + +/** Setup the input buffer state to scan the given bytes. The next call to yylex() will + * scan from a @e copy of @a bytes. + * @param yybytes the byte buffer to scan + * @param _yybytes_len the number of bytes in the buffer pointed to by @a bytes. + * @param yyscanner The scanner object. + * @return the newly allocated buffer state object. + */ +YY_BUFFER_STATE yy_scan_bytes (const char * yybytes, int _yybytes_len , yyscan_t yyscanner) +{ + YY_BUFFER_STATE b; + char *buf; + yy_size_t n; + int i; + + /* Get memory for full buffer, including space for trailing EOB's. */ + n = (yy_size_t) (_yybytes_len + 2); + buf = (char *) yyalloc( n , yyscanner ); + if ( ! buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_scan_bytes()" ); + + for ( i = 0; i < _yybytes_len; ++i ) + buf[i] = yybytes[i]; + + buf[_yybytes_len] = buf[_yybytes_len+1] = YY_END_OF_BUFFER_CHAR; + + b = yy_scan_buffer( buf, n , yyscanner); + if ( ! b ) + YY_FATAL_ERROR( "bad buffer in yy_scan_bytes()" ); + + /* It's okay to grow etc. this buffer, and we should throw it + * away when we're done. + */ + b->yy_is_our_buffer = 1; + + return b; +} + +#ifndef YY_EXIT_FAILURE +#define YY_EXIT_FAILURE 2 +#endif + +static void yynoreturn yy_fatal_error (const char* msg , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + (void)yyg; + fprintf( stderr, "%s\n", msg ); + exit( YY_EXIT_FAILURE ); +} + +/* Redefine yyless() so it works in section 3 code. */ + +#undef yyless +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up yytext. */ \ + int yyless_macro_arg = (n); \ + YY_LESS_LINENO(yyless_macro_arg);\ + yytext[yyleng] = yyg->yy_hold_char; \ + yyg->yy_c_buf_p = yytext + yyless_macro_arg; \ + yyg->yy_hold_char = *yyg->yy_c_buf_p; \ + *yyg->yy_c_buf_p = '\0'; \ + yyleng = yyless_macro_arg; \ + } \ + while ( 0 ) + +/* Accessor methods (get/set functions) to struct members. */ + +/** Get the user-defined data for this scanner. + * @param yyscanner The scanner object. + */ +YY_EXTRA_TYPE yyget_extra (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yyextra; +} + +/** Get the current line number. + * @param yyscanner The scanner object. + */ +int yyget_lineno (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if (! YY_CURRENT_BUFFER) + return 0; + + return yylineno; +} + +/** Get the current column number. + * @param yyscanner The scanner object. + */ +int yyget_column (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if (! YY_CURRENT_BUFFER) + return 0; + + return yycolumn; +} + +/** Get the input stream. + * @param yyscanner The scanner object. + */ +FILE *yyget_in (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yyin; +} + +/** Get the output stream. + * @param yyscanner The scanner object. + */ +FILE *yyget_out (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yyout; +} + +/** Get the length of the current token. + * @param yyscanner The scanner object. + */ +int yyget_leng (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yyleng; +} + +/** Get the current token. + * @param yyscanner The scanner object. + */ + +char *yyget_text (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yytext; +} + +/** Set the user-defined data. This data is never touched by the scanner. + * @param user_defined The data to be associated with this scanner. + * @param yyscanner The scanner object. + */ +void yyset_extra (YY_EXTRA_TYPE user_defined , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yyextra = user_defined ; +} + +/** Set the current line number. + * @param _line_number line number + * @param yyscanner The scanner object. + */ +void yyset_lineno (int _line_number , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + /* lineno is only valid if an input buffer exists. */ + if (! YY_CURRENT_BUFFER ) + YY_FATAL_ERROR( "yyset_lineno called with no buffer" ); + + yylineno = _line_number; +} + +/** Set the current column. + * @param _column_no column number + * @param yyscanner The scanner object. + */ +void yyset_column (int _column_no , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + /* column is only valid if an input buffer exists. */ + if (! YY_CURRENT_BUFFER ) + YY_FATAL_ERROR( "yyset_column called with no buffer" ); + + yycolumn = _column_no; +} + +/** Set the input stream. This does not discard the current + * input buffer. + * @param _in_str A readable stream. + * @param yyscanner The scanner object. + * @see yy_switch_to_buffer + */ +void yyset_in (FILE * _in_str , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yyin = _in_str ; +} + +void yyset_out (FILE * _out_str , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yyout = _out_str ; +} + +int yyget_debug (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yy_flex_debug; +} + +void yyset_debug (int _bdebug , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yy_flex_debug = _bdebug ; +} + +/* Accessor methods for yylval and yylloc */ + +YYSTYPE * yyget_lval (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yylval; +} + +void yyset_lval (YYSTYPE * yylval_param , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yylval = yylval_param; +} + +/* User-visible API */ + +/* yylex_init is special because it creates the scanner itself, so it is + * the ONLY reentrant function that doesn't take the scanner as the last argument. + * That's why we explicitly handle the declaration, instead of using our macros. + */ +int yylex_init(yyscan_t* ptr_yy_globals) +{ + if (ptr_yy_globals == NULL){ + errno = EINVAL; + return 1; + } + + *ptr_yy_globals = (yyscan_t) yyalloc ( sizeof( struct yyguts_t ), NULL ); + + if (*ptr_yy_globals == NULL){ + errno = ENOMEM; + return 1; + } + + /* By setting to 0xAA, we expose bugs in yy_init_globals. Leave at 0x00 for releases. */ + memset(*ptr_yy_globals,0x00,sizeof(struct yyguts_t)); + + return yy_init_globals ( *ptr_yy_globals ); +} + +/* yylex_init_extra has the same functionality as yylex_init, but follows the + * convention of taking the scanner as the last argument. Note however, that + * this is a *pointer* to a scanner, as it will be allocated by this call (and + * is the reason, too, why this function also must handle its own declaration). + * The user defined value in the first argument will be available to yyalloc in + * the yyextra field. + */ +int yylex_init_extra( YY_EXTRA_TYPE yy_user_defined, yyscan_t* ptr_yy_globals ) +{ + struct yyguts_t dummy_yyguts; + + yyset_extra (yy_user_defined, &dummy_yyguts); + + if (ptr_yy_globals == NULL){ + errno = EINVAL; + return 1; + } + + *ptr_yy_globals = (yyscan_t) yyalloc ( sizeof( struct yyguts_t ), &dummy_yyguts ); + + if (*ptr_yy_globals == NULL){ + errno = ENOMEM; + return 1; + } + + /* By setting to 0xAA, we expose bugs in + yy_init_globals. Leave at 0x00 for releases. */ + memset(*ptr_yy_globals,0x00,sizeof(struct yyguts_t)); + + yyset_extra (yy_user_defined, *ptr_yy_globals); + + return yy_init_globals ( *ptr_yy_globals ); +} + +static int yy_init_globals (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + /* Initialization is the same as for the non-reentrant scanner. + * This function is called from yylex_destroy(), so don't allocate here. + */ + + yyg->yy_buffer_stack = NULL; + yyg->yy_buffer_stack_top = 0; + yyg->yy_buffer_stack_max = 0; + yyg->yy_c_buf_p = NULL; + yyg->yy_init = 0; + yyg->yy_start = 0; + + yyg->yy_start_stack_ptr = 0; + yyg->yy_start_stack_depth = 0; + yyg->yy_start_stack = NULL; + +/* Defined in main.c */ +#ifdef YY_STDINIT + yyin = stdin; + yyout = stdout; +#else + yyin = NULL; + yyout = NULL; +#endif + + /* For future reference: Set errno on error, since we are called by + * yylex_init() + */ + return 0; +} + +/* yylex_destroy is for both reentrant and non-reentrant scanners. */ +int yylex_destroy (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + /* Pop the buffer stack, destroying each element. */ + while(YY_CURRENT_BUFFER){ + yy_delete_buffer( YY_CURRENT_BUFFER , yyscanner ); + YY_CURRENT_BUFFER_LVALUE = NULL; + yypop_buffer_state(yyscanner); + } + + /* Destroy the stack itself. */ + yyfree(yyg->yy_buffer_stack , yyscanner); + yyg->yy_buffer_stack = NULL; + + /* Destroy the start condition stack. */ + yyfree( yyg->yy_start_stack , yyscanner ); + yyg->yy_start_stack = NULL; + + /* Reset the globals. This is important in a non-reentrant scanner so the next time + * yylex() is called, initialization will occur. */ + yy_init_globals( yyscanner); + + /* Destroy the main struct (reentrant only). */ + yyfree ( yyscanner , yyscanner ); + yyscanner = NULL; + return 0; +} + +/* + * Internal utility routines. + */ + +#ifndef yytext_ptr +static void yy_flex_strncpy (char* s1, const char * s2, int n , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + (void)yyg; + + int i; + for ( i = 0; i < n; ++i ) + s1[i] = s2[i]; +} +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen (const char * s , yyscan_t yyscanner) +{ + int n; + for ( n = 0; s[n]; ++n ) + ; + + return n; +} +#endif + +void *yyalloc (yy_size_t size , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + (void)yyg; + return malloc(size); +} + +void *yyrealloc (void * ptr, yy_size_t size , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + (void)yyg; + + /* The cast to (char *) in the following accommodates both + * implementations that use char* generic pointers, and those + * that use void* generic pointers. It works with the latter + * because both ANSI C and C++ allow castless assignment from + * any pointer type to void*, and deal with argument conversions + * as though doing an assignment. + */ + return realloc(ptr, size); +} + +void yyfree (void * ptr , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + (void)yyg; + free( (char *) ptr ); /* see yyrealloc() for (char *) cast */ +} + +#define YYTABLES_NAME "yytables" + +#line 89 "sqlhist.l" + + +int yywrap(void *data) +{ + return 1; +} + +void yyerror(struct sqlhist_bison *sb, char *fmt, ...) +{ + struct yyguts_t * yyg = (struct yyguts_t*)sb->scanner; + va_list ap; + + va_start(ap, fmt); + sql_parse_error(sb, yytext, fmt, ap); + va_end(ap); +} + diff --git a/src/sqlhist-parse.h b/src/sqlhist-parse.h new file mode 100644 index 0000000..7bd2a63 --- /dev/null +++ b/src/sqlhist-parse.h @@ -0,0 +1,77 @@ +#ifndef __SQLHIST_PARSE_H +#define __SQLHIST_PARSE_H + +#include +#include + +#include + +struct str_hash; + +struct sql_table; + +struct sqlhist_bison { + void *scanner; + const char *buffer; + size_t buffer_size; + size_t buffer_idx; + int line_no; + int line_idx; + struct sql_table *table; + char *parse_error_str; + struct str_hash *str_hash[1 << HASH_BITS]; +}; + +#include "sqlhist.tab.h" + +enum filter_type { + FILTER_GROUP, + FILTER_NOT_GROUP, + FILTER_EQ, + FILTER_NE, + FILTER_LE, + FILTER_LT, + FILTER_GE, + FILTER_GT, + FILTER_BIN_AND, + FILTER_STR_CMP, + FILTER_AND, + FILTER_OR, +}; + +enum compare_type { + COMPARE_GROUP, + COMPARE_ADD, + COMPARE_SUB, + COMPARE_MUL, + COMPARE_DIV, + COMPARE_BIN_AND, + COMPARE_BIN_OR, + COMPARE_AND, + COMPARE_OR, +}; + +char * store_str(struct sqlhist_bison *sb, const char *str); + +int table_start(struct sqlhist_bison *sb); + +void *add_field(struct sqlhist_bison *sb, const char *field, const char *label); + +void *add_filter(struct sqlhist_bison *sb, void *A, void *B, enum filter_type op); + +int add_match(struct sqlhist_bison *sb, void *A, void *B); +void *add_compare(struct sqlhist_bison *sb, void *A, void *B, enum compare_type type); +int add_where(struct sqlhist_bison *sb, void *expr); + +int add_selection(struct sqlhist_bison *sb, void *item, const char *label); +int add_from(struct sqlhist_bison *sb, void *item); +int add_to(struct sqlhist_bison *sb, void *item); +void *add_cast(struct sqlhist_bison *sb, void *field, const char *type); + +void *add_string(struct sqlhist_bison *sb, const char *str); +void *add_number(struct sqlhist_bison *sb, long val); + +extern void sql_parse_error(struct sqlhist_bison *sb, const char *text, + const char *fmt, va_list ap); + +#endif diff --git a/src/sqlhist.l b/src/sqlhist.l new file mode 100644 index 0000000..4df475a --- /dev/null +++ b/src/sqlhist.l @@ -0,0 +1,104 @@ +%{ +/* code here */ + +#include +#include "sqlhist-parse.h" + +extern int my_yyinput(void *extra, char *buf, int max); + +#undef YY_INPUT +#define YY_INPUT(b, r, m) ({r = my_yyinput(yyextra, b, m);}) + +#define YY_NO_INPUT +#define YY_NO_UNPUT + +#define YY_EXTRA_TYPE struct sqlhist_bison * + +#define yytext yyg->yytext_r + +#define TRACE_SB ((struct sqlhist_bison *)yyextra) +#define HANDLE_COLUMN do { TRACE_SB->line_idx += strlen(yytext); } while (0) + +%} + +%option caseless +%option reentrant +%option bison-bridge + +field \\?[a-z_][a-z0-9_\.]* +qstring \"[^\"]*\" + +hexnum 0x[0-9a-f]+ +number [0-9a-f]+ +%% + +select { HANDLE_COLUMN; return SELECT; } +as { HANDLE_COLUMN; return AS; } +from { HANDLE_COLUMN; return FROM; } +join { HANDLE_COLUMN; return JOIN; } +on { HANDLE_COLUMN; return ON; } +where { HANDLE_COLUMN; return WHERE; } +cast { HANDLE_COLUMN; return CAST; } + +sym-offset { + HANDLE_COLUMN; + yylval->string = store_str(TRACE_SB, yyg->yytext_r); + return FIELD; +} + +{qstring} { + HANDLE_COLUMN; + yylval->string = store_str(TRACE_SB, yyg->yytext_r); + return STRING; +} + +{field} { + const char *str = yyg->yytext_r; + HANDLE_COLUMN; + if (str[0] == '\\') { str++; }; + yylval->string = store_str(TRACE_SB, str); + return FIELD; +} + +{hexnum} { + HANDLE_COLUMN; + yylval->number = strtol(yyg->yytext_r, NULL, 0); + return NUMBER; +} + +{number} { + HANDLE_COLUMN; + yylval->number = strtol(yyg->yytext_r, NULL, 0); + return NUMBER; +} + +\!= { HANDLE_COLUMN; return NEQ; } +\<= { HANDLE_COLUMN; return LE; } +\>= { HANDLE_COLUMN; return GE; } +== { HANDLE_COLUMN; return EQ; } +&& { HANDLE_COLUMN; return AND; } +"||" { HANDLE_COLUMN; return OR; } +[<>&~] { HANDLE_COLUMN; return yytext[0]; } + +[\!()\-\+\*/,=] { HANDLE_COLUMN; return yytext[0]; } + +[ \t] { HANDLE_COLUMN; } +\n { TRACE_SB->line_idx = 0; TRACE_SB->line_no++; } + +. { HANDLE_COLUMN; return PARSE_ERROR; } +%% + +int yywrap(void *data) +{ + return 1; +} + +void yyerror(struct sqlhist_bison *sb, char *fmt, ...) +{ + struct yyguts_t * yyg = (struct yyguts_t*)sb->scanner; + va_list ap; + + va_start(ap, fmt); + sql_parse_error(sb, yytext, fmt, ap); + va_end(ap); +} diff --git a/src/sqlhist.tab.c b/src/sqlhist.tab.c new file mode 100644 index 0000000..6393e95 --- /dev/null +++ b/src/sqlhist.tab.c @@ -0,0 +1,1755 @@ +/* A Bison parser, made by GNU Bison 3.6.4. */ + +/* Bison implementation for Yacc-like parsers in C + + Copyright (C) 1984, 1989-1990, 2000-2015, 2018-2020 Free Software Foundation, + Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +/* As a special exception, you may create a larger work that contains + part or all of the Bison parser skeleton and distribute that work + under terms of your choice, so long as that work isn't itself a + parser generator using the skeleton or a modified version thereof + as a parser skeleton. Alternatively, if you modify or redistribute + the parser skeleton itself, you may (at your option) remove this + special exception, which will cause the skeleton and the resulting + Bison output files to be licensed under the GNU General Public + License without this special exception. + + This special exception was added by the Free Software Foundation in + version 2.2 of Bison. */ + +/* C LALR(1) parser skeleton written by Richard Stallman, by + simplifying the original so-called "semantic" parser. */ + +/* DO NOT RELY ON FEATURES THAT ARE NOT DOCUMENTED in the manual, + especially those whose name start with YY_ or yy_. They are + private implementation details that can be changed or removed. */ + +/* All symbols defined below should begin with yy or YY, to avoid + infringing on user name space. This should be done even for local + variables, as they might otherwise be expanded by user macros. + There are some unavoidable exceptions within include files to + define necessary library symbols; they are noted "INFRINGES ON + USER NAME SPACE" below. */ + +/* Identify Bison output. */ +#define YYBISON 1 + +/* Bison version. */ +#define YYBISON_VERSION "3.6.4" + +/* Skeleton name. */ +#define YYSKELETON_NAME "yacc.c" + +/* Pure parsers. */ +#define YYPURE 1 + +/* Push parsers. */ +#define YYPUSH 0 + +/* Pull parsers. */ +#define YYPULL 1 + +/* Substitute the type names. */ +#define YYSTYPE TRACEFS_STYPE +/* Substitute the variable and function names. */ +#define yyparse tracefs_parse +#define yylex tracefs_lex +#define yyerror tracefs_error +#define yydebug tracefs_debug +#define yynerrs tracefs_nerrs + +/* First part of user prologue. */ +#line 1 "sqlhist.y" + +#include +#include +#include +#include + +#include "sqlhist-parse.h" + +#define scanner sb->scanner + +extern int yylex(YYSTYPE *yylval, void *); +extern void yyerror(struct sqlhist_bison *, char *fmt, ...); + +#define CHECK_RETURN_PTR(x) \ + do { \ + if (!(x)) { \ + printf("FAILED MEMORY: %s\n", #x); \ + return -ENOMEM; \ + } \ + } while (0) + +#define CHECK_RETURN_VAL(x) \ + do { \ + if ((x) < 0) { \ + printf("FAILED MEMORY: %s\n", #x); \ + return -ENOMEM; \ + } \ + } while (0) + + +#line 108 "sqlhist.tab.c" + +# ifndef YY_CAST +# ifdef __cplusplus +# define YY_CAST(Type, Val) static_cast (Val) +# define YY_REINTERPRET_CAST(Type, Val) reinterpret_cast (Val) +# else +# define YY_CAST(Type, Val) ((Type) (Val)) +# define YY_REINTERPRET_CAST(Type, Val) ((Type) (Val)) +# endif +# endif +# ifndef YY_NULLPTR +# if defined __cplusplus +# if 201103L <= __cplusplus +# define YY_NULLPTR nullptr +# else +# define YY_NULLPTR 0 +# endif +# else +# define YY_NULLPTR ((void*)0) +# endif +# endif + +/* Use api.header.include to #include this header + instead of duplicating it here. */ +#ifndef YY_TRACEFS_SQLHIST_TAB_H_INCLUDED +# define YY_TRACEFS_SQLHIST_TAB_H_INCLUDED +/* Debug traces. */ +#ifndef TRACEFS_DEBUG +# if defined YYDEBUG +#if YYDEBUG +# define TRACEFS_DEBUG 1 +# else +# define TRACEFS_DEBUG 0 +# endif +# else /* ! defined YYDEBUG */ +# define TRACEFS_DEBUG 1 +# endif /* ! defined YYDEBUG */ +#endif /* ! defined TRACEFS_DEBUG */ +#if TRACEFS_DEBUG +extern int tracefs_debug; +#endif + +/* Token kinds. */ +#ifndef TRACEFS_TOKENTYPE +# define TRACEFS_TOKENTYPE + enum tracefs_tokentype + { + TRACEFS_EMPTY = -2, + TRACEFS_EOF = 0, /* "end of file" */ + TRACEFS_error = 256, /* error */ + TRACEFS_UNDEF = 257, /* "invalid token" */ + AS = 258, /* AS */ + SELECT = 259, /* SELECT */ + FROM = 260, /* FROM */ + JOIN = 261, /* JOIN */ + ON = 262, /* ON */ + WHERE = 263, /* WHERE */ + PARSE_ERROR = 264, /* PARSE_ERROR */ + CAST = 265, /* CAST */ + NUMBER = 266, /* NUMBER */ + field_type = 267, /* field_type */ + STRING = 268, /* STRING */ + FIELD = 269, /* FIELD */ + LE = 270, /* LE */ + GE = 271, /* GE */ + EQ = 272, /* EQ */ + NEQ = 273, /* NEQ */ + AND = 274, /* AND */ + OR = 275 /* OR */ + }; + typedef enum tracefs_tokentype tracefs_token_kind_t; +#endif + +/* Value type. */ +#if ! defined TRACEFS_STYPE && ! defined TRACEFS_STYPE_IS_DECLARED +union TRACEFS_STYPE +{ +#line 46 "sqlhist.y" + + int s32; + char *string; + long number; + void *expr; + +#line 193 "sqlhist.tab.c" + +}; +typedef union TRACEFS_STYPE TRACEFS_STYPE; +# define TRACEFS_STYPE_IS_TRIVIAL 1 +# define TRACEFS_STYPE_IS_DECLARED 1 +#endif + + + +int tracefs_parse (struct sqlhist_bison *sb); +/* "%code provides" blocks. */ +#line 37 "sqlhist.y" + + #define YYSTYPE TRACEFS_STYPE + #define yylex tracefs_lex + #define yyerror tracefs_error + +#line 211 "sqlhist.tab.c" + +#endif /* !YY_TRACEFS_SQLHIST_TAB_H_INCLUDED */ +/* Symbol kind. */ +enum yysymbol_kind_t +{ + YYSYMBOL_YYEMPTY = -2, + YYSYMBOL_YYEOF = 0, /* "end of file" */ + YYSYMBOL_YYerror = 1, /* error */ + YYSYMBOL_YYUNDEF = 2, /* "invalid token" */ + YYSYMBOL_AS = 3, /* AS */ + YYSYMBOL_SELECT = 4, /* SELECT */ + YYSYMBOL_FROM = 5, /* FROM */ + YYSYMBOL_JOIN = 6, /* JOIN */ + YYSYMBOL_ON = 7, /* ON */ + YYSYMBOL_WHERE = 8, /* WHERE */ + YYSYMBOL_PARSE_ERROR = 9, /* PARSE_ERROR */ + YYSYMBOL_CAST = 10, /* CAST */ + YYSYMBOL_NUMBER = 11, /* NUMBER */ + YYSYMBOL_field_type = 12, /* field_type */ + YYSYMBOL_STRING = 13, /* STRING */ + YYSYMBOL_FIELD = 14, /* FIELD */ + YYSYMBOL_LE = 15, /* LE */ + YYSYMBOL_GE = 16, /* GE */ + YYSYMBOL_EQ = 17, /* EQ */ + YYSYMBOL_NEQ = 18, /* NEQ */ + YYSYMBOL_AND = 19, /* AND */ + YYSYMBOL_OR = 20, /* OR */ + YYSYMBOL_21_ = 21, /* '+' */ + YYSYMBOL_22_ = 22, /* '-' */ + YYSYMBOL_23_ = 23, /* '*' */ + YYSYMBOL_24_ = 24, /* '/' */ + YYSYMBOL_25_ = 25, /* '<' */ + YYSYMBOL_26_ = 26, /* '>' */ + YYSYMBOL_27_ = 27, /* ',' */ + YYSYMBOL_28_ = 28, /* '(' */ + YYSYMBOL_29_ = 29, /* ')' */ + YYSYMBOL_30_ = 30, /* '=' */ + YYSYMBOL_31_ = 31, /* "!=" */ + YYSYMBOL_32_ = 32, /* '&' */ + YYSYMBOL_33_ = 33, /* '~' */ + YYSYMBOL_34_ = 34, /* '!' */ + YYSYMBOL_YYACCEPT = 35, /* $accept */ + YYSYMBOL_start = 36, /* start */ + YYSYMBOL_label = 37, /* label */ + YYSYMBOL_select = 38, /* select */ + YYSYMBOL_select_statement = 39, /* select_statement */ + YYSYMBOL_selection_list = 40, /* selection_list */ + YYSYMBOL_selection = 41, /* selection */ + YYSYMBOL_selection_expr = 42, /* selection_expr */ + YYSYMBOL_selection_addition = 43, /* selection_addition */ + YYSYMBOL_item = 44, /* item */ + YYSYMBOL_field = 45, /* field */ + YYSYMBOL_named_field = 46, /* named_field */ + YYSYMBOL_name = 47, /* name */ + YYSYMBOL_str_val = 48, /* str_val */ + YYSYMBOL_val = 49, /* val */ + YYSYMBOL_compare = 50, /* compare */ + YYSYMBOL_compare_and_or = 51, /* compare_and_or */ + YYSYMBOL_compare_items = 52, /* compare_items */ + YYSYMBOL_compare_cmds = 53, /* compare_cmds */ + YYSYMBOL_compare_list = 54, /* compare_list */ + YYSYMBOL_where_clause = 55, /* where_clause */ + YYSYMBOL_opt_where_clause = 56, /* opt_where_clause */ + YYSYMBOL_opt_join_clause = 57, /* opt_join_clause */ + YYSYMBOL_table_exp = 58, /* table_exp */ + YYSYMBOL_from_clause = 59, /* from_clause */ + YYSYMBOL_join_clause = 60, /* join_clause */ + YYSYMBOL_match = 61, /* match */ + YYSYMBOL_match_clause = 62 /* match_clause */ +}; +typedef enum yysymbol_kind_t yysymbol_kind_t; + + + + +#ifdef short +# undef short +#endif + +/* On compilers that do not define __PTRDIFF_MAX__ etc., make sure + and (if available) are included + so that the code can choose integer types of a good width. */ + +#ifndef __PTRDIFF_MAX__ +# include /* INFRINGES ON USER NAME SPACE */ +# if defined __STDC_VERSION__ && 199901 <= __STDC_VERSION__ +# include /* INFRINGES ON USER NAME SPACE */ +# define YY_STDINT_H +# endif +#endif + +/* Narrow types that promote to a signed type and that can represent a + signed or unsigned integer of at least N bits. In tables they can + save space and decrease cache pressure. Promoting to a signed type + helps avoid bugs in integer arithmetic. */ + +#ifdef __INT_LEAST8_MAX__ +typedef __INT_LEAST8_TYPE__ yytype_int8; +#elif defined YY_STDINT_H +typedef int_least8_t yytype_int8; +#else +typedef signed char yytype_int8; +#endif + +#ifdef __INT_LEAST16_MAX__ +typedef __INT_LEAST16_TYPE__ yytype_int16; +#elif defined YY_STDINT_H +typedef int_least16_t yytype_int16; +#else +typedef short yytype_int16; +#endif + +#if defined __UINT_LEAST8_MAX__ && __UINT_LEAST8_MAX__ <= __INT_MAX__ +typedef __UINT_LEAST8_TYPE__ yytype_uint8; +#elif (!defined __UINT_LEAST8_MAX__ && defined YY_STDINT_H \ + && UINT_LEAST8_MAX <= INT_MAX) +typedef uint_least8_t yytype_uint8; +#elif !defined __UINT_LEAST8_MAX__ && UCHAR_MAX <= INT_MAX +typedef unsigned char yytype_uint8; +#else +typedef short yytype_uint8; +#endif + +#if defined __UINT_LEAST16_MAX__ && __UINT_LEAST16_MAX__ <= __INT_MAX__ +typedef __UINT_LEAST16_TYPE__ yytype_uint16; +#elif (!defined __UINT_LEAST16_MAX__ && defined YY_STDINT_H \ + && UINT_LEAST16_MAX <= INT_MAX) +typedef uint_least16_t yytype_uint16; +#elif !defined __UINT_LEAST16_MAX__ && USHRT_MAX <= INT_MAX +typedef unsigned short yytype_uint16; +#else +typedef int yytype_uint16; +#endif + +#ifndef YYPTRDIFF_T +# if defined __PTRDIFF_TYPE__ && defined __PTRDIFF_MAX__ +# define YYPTRDIFF_T __PTRDIFF_TYPE__ +# define YYPTRDIFF_MAXIMUM __PTRDIFF_MAX__ +# elif defined PTRDIFF_MAX +# ifndef ptrdiff_t +# include /* INFRINGES ON USER NAME SPACE */ +# endif +# define YYPTRDIFF_T ptrdiff_t +# define YYPTRDIFF_MAXIMUM PTRDIFF_MAX +# else +# define YYPTRDIFF_T long +# define YYPTRDIFF_MAXIMUM LONG_MAX +# endif +#endif + +#ifndef YYSIZE_T +# ifdef __SIZE_TYPE__ +# define YYSIZE_T __SIZE_TYPE__ +# elif defined size_t +# define YYSIZE_T size_t +# elif defined __STDC_VERSION__ && 199901 <= __STDC_VERSION__ +# include /* INFRINGES ON USER NAME SPACE */ +# define YYSIZE_T size_t +# else +# define YYSIZE_T unsigned +# endif +#endif + +#define YYSIZE_MAXIMUM \ + YY_CAST (YYPTRDIFF_T, \ + (YYPTRDIFF_MAXIMUM < YY_CAST (YYSIZE_T, -1) \ + ? YYPTRDIFF_MAXIMUM \ + : YY_CAST (YYSIZE_T, -1))) + +#define YYSIZEOF(X) YY_CAST (YYPTRDIFF_T, sizeof (X)) + + +/* Stored state numbers (used for stacks). */ +typedef yytype_int8 yy_state_t; + +/* State numbers in computations. */ +typedef int yy_state_fast_t; + +#ifndef YY_ +# if defined YYENABLE_NLS && YYENABLE_NLS +# if ENABLE_NLS +# include /* INFRINGES ON USER NAME SPACE */ +# define YY_(Msgid) dgettext ("bison-runtime", Msgid) +# endif +# endif +# ifndef YY_ +# define YY_(Msgid) Msgid +# endif +#endif + + +#ifndef YY_ATTRIBUTE_PURE +# if defined __GNUC__ && 2 < __GNUC__ + (96 <= __GNUC_MINOR__) +# define YY_ATTRIBUTE_PURE __attribute__ ((__pure__)) +# else +# define YY_ATTRIBUTE_PURE +# endif +#endif + +#ifndef YY_ATTRIBUTE_UNUSED +# if defined __GNUC__ && 2 < __GNUC__ + (7 <= __GNUC_MINOR__) +# define YY_ATTRIBUTE_UNUSED __attribute__ ((__unused__)) +# else +# define YY_ATTRIBUTE_UNUSED +# endif +#endif + +/* Suppress unused-variable warnings by "using" E. */ +#if ! defined lint || defined __GNUC__ +# define YYUSE(E) ((void) (E)) +#else +# define YYUSE(E) /* empty */ +#endif + +#if defined __GNUC__ && ! defined __ICC && 407 <= __GNUC__ * 100 + __GNUC_MINOR__ +/* Suppress an incorrect diagnostic about yylval being uninitialized. */ +# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \ + _Pragma ("GCC diagnostic push") \ + _Pragma ("GCC diagnostic ignored \"-Wuninitialized\"") \ + _Pragma ("GCC diagnostic ignored \"-Wmaybe-uninitialized\"") +# define YY_IGNORE_MAYBE_UNINITIALIZED_END \ + _Pragma ("GCC diagnostic pop") +#else +# define YY_INITIAL_VALUE(Value) Value +#endif +#ifndef YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN +# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN +# define YY_IGNORE_MAYBE_UNINITIALIZED_END +#endif +#ifndef YY_INITIAL_VALUE +# define YY_INITIAL_VALUE(Value) /* Nothing. */ +#endif + +#if defined __cplusplus && defined __GNUC__ && ! defined __ICC && 6 <= __GNUC__ +# define YY_IGNORE_USELESS_CAST_BEGIN \ + _Pragma ("GCC diagnostic push") \ + _Pragma ("GCC diagnostic ignored \"-Wuseless-cast\"") +# define YY_IGNORE_USELESS_CAST_END \ + _Pragma ("GCC diagnostic pop") +#endif +#ifndef YY_IGNORE_USELESS_CAST_BEGIN +# define YY_IGNORE_USELESS_CAST_BEGIN +# define YY_IGNORE_USELESS_CAST_END +#endif + + +#define YY_ASSERT(E) ((void) (0 && (E))) + +#if !defined yyoverflow + +/* The parser invokes alloca or malloc; define the necessary symbols. */ + +# ifdef YYSTACK_USE_ALLOCA +# if YYSTACK_USE_ALLOCA +# ifdef __GNUC__ +# define YYSTACK_ALLOC __builtin_alloca +# elif defined __BUILTIN_VA_ARG_INCR +# include /* INFRINGES ON USER NAME SPACE */ +# elif defined _AIX +# define YYSTACK_ALLOC __alloca +# elif defined _MSC_VER +# include /* INFRINGES ON USER NAME SPACE */ +# define alloca _alloca +# else +# define YYSTACK_ALLOC alloca +# if ! defined _ALLOCA_H && ! defined EXIT_SUCCESS +# include /* INFRINGES ON USER NAME SPACE */ + /* Use EXIT_SUCCESS as a witness for stdlib.h. */ +# ifndef EXIT_SUCCESS +# define EXIT_SUCCESS 0 +# endif +# endif +# endif +# endif +# endif + +# ifdef YYSTACK_ALLOC + /* Pacify GCC's 'empty if-body' warning. */ +# define YYSTACK_FREE(Ptr) do { /* empty */; } while (0) +# ifndef YYSTACK_ALLOC_MAXIMUM + /* The OS might guarantee only one guard page at the bottom of the stack, + and a page size can be as small as 4096 bytes. So we cannot safely + invoke alloca (N) if N exceeds 4096. Use a slightly smaller number + to allow for a few compiler-allocated temporary stack slots. */ +# define YYSTACK_ALLOC_MAXIMUM 4032 /* reasonable circa 2006 */ +# endif +# else +# define YYSTACK_ALLOC YYMALLOC +# define YYSTACK_FREE YYFREE +# ifndef YYSTACK_ALLOC_MAXIMUM +# define YYSTACK_ALLOC_MAXIMUM YYSIZE_MAXIMUM +# endif +# if (defined __cplusplus && ! defined EXIT_SUCCESS \ + && ! ((defined YYMALLOC || defined malloc) \ + && (defined YYFREE || defined free))) +# include /* INFRINGES ON USER NAME SPACE */ +# ifndef EXIT_SUCCESS +# define EXIT_SUCCESS 0 +# endif +# endif +# ifndef YYMALLOC +# define YYMALLOC malloc +# if ! defined malloc && ! defined EXIT_SUCCESS +void *malloc (YYSIZE_T); /* INFRINGES ON USER NAME SPACE */ +# endif +# endif +# ifndef YYFREE +# define YYFREE free +# if ! defined free && ! defined EXIT_SUCCESS +void free (void *); /* INFRINGES ON USER NAME SPACE */ +# endif +# endif +# endif +#endif /* !defined yyoverflow */ + +#if (! defined yyoverflow \ + && (! defined __cplusplus \ + || (defined TRACEFS_STYPE_IS_TRIVIAL && TRACEFS_STYPE_IS_TRIVIAL))) + +/* A type that is properly aligned for any stack member. */ +union yyalloc +{ + yy_state_t yyss_alloc; + YYSTYPE yyvs_alloc; +}; + +/* The size of the maximum gap between one aligned stack and the next. */ +# define YYSTACK_GAP_MAXIMUM (YYSIZEOF (union yyalloc) - 1) + +/* The size of an array large to enough to hold all stacks, each with + N elements. */ +# define YYSTACK_BYTES(N) \ + ((N) * (YYSIZEOF (yy_state_t) + YYSIZEOF (YYSTYPE)) \ + + YYSTACK_GAP_MAXIMUM) + +# define YYCOPY_NEEDED 1 + +/* Relocate STACK from its old location to the new one. The + local variables YYSIZE and YYSTACKSIZE give the old and new number of + elements in the stack, and YYPTR gives the new location of the + stack. Advance YYPTR to a properly aligned location for the next + stack. */ +# define YYSTACK_RELOCATE(Stack_alloc, Stack) \ + do \ + { \ + YYPTRDIFF_T yynewbytes; \ + YYCOPY (&yyptr->Stack_alloc, Stack, yysize); \ + Stack = &yyptr->Stack_alloc; \ + yynewbytes = yystacksize * YYSIZEOF (*Stack) + YYSTACK_GAP_MAXIMUM; \ + yyptr += yynewbytes / YYSIZEOF (*yyptr); \ + } \ + while (0) + +#endif + +#if defined YYCOPY_NEEDED && YYCOPY_NEEDED +/* Copy COUNT objects from SRC to DST. The source and destination do + not overlap. */ +# ifndef YYCOPY +# if defined __GNUC__ && 1 < __GNUC__ +# define YYCOPY(Dst, Src, Count) \ + __builtin_memcpy (Dst, Src, YY_CAST (YYSIZE_T, (Count)) * sizeof (*(Src))) +# else +# define YYCOPY(Dst, Src, Count) \ + do \ + { \ + YYPTRDIFF_T yyi; \ + for (yyi = 0; yyi < (Count); yyi++) \ + (Dst)[yyi] = (Src)[yyi]; \ + } \ + while (0) +# endif +# endif +#endif /* !YYCOPY_NEEDED */ + +/* YYFINAL -- State number of the termination state. */ +#define YYFINAL 5 +/* YYLAST -- Last index in YYTABLE. */ +#define YYLAST 104 + +/* YYNTOKENS -- Number of terminals. */ +#define YYNTOKENS 35 +/* YYNNTS -- Number of nonterminals. */ +#define YYNNTS 28 +/* YYNRULES -- Number of rules. */ +#define YYNRULES 61 +/* YYNSTATES -- Number of states. */ +#define YYNSTATES 111 + +#define YYMAXUTOK 276 + + +/* YYTRANSLATE(TOKEN-NUM) -- Symbol number corresponding to TOKEN-NUM + as returned by yylex, with out-of-bounds checking. */ +#define YYTRANSLATE(YYX) \ + (0 <= (YYX) && (YYX) <= YYMAXUTOK \ + ? YY_CAST (yysymbol_kind_t, yytranslate[YYX]) \ + : YYSYMBOL_YYUNDEF) + +/* YYTRANSLATE[TOKEN-NUM] -- Symbol number corresponding to TOKEN-NUM + as returned by yylex. */ +static const yytype_int8 yytranslate[] = +{ + 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 34, 2, 2, 2, 2, 32, 2, + 28, 29, 23, 21, 27, 22, 2, 24, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 25, 30, 26, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 33, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 1, 2, 3, 4, + 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 31 +}; + +#if TRACEFS_DEBUG + /* YYRLINE[YYN] -- Source line where rule number YYN was defined. */ +static const yytype_uint8 yyrline[] = +{ + 0, 75, 75, 78, 79, 82, 86, 90, 91, 95, + 99, 106, 107, 108, 109, 110, 117, 122, 130, 131, + 135, 139, 143, 147, 151, 152, 157, 158, 159, 160, + 161, 162, 163, 164, 165, 166, 170, 171, 172, 173, + 174, 178, 179, 180, 181, 182, 186, 195, 196, 197, + 201, 204, 206, 209, 211, 215, 219, 234, 238, 239, + 244, 245 +}; +#endif + +/** Accessing symbol of state STATE. */ +#define YY_ACCESSING_SYMBOL(State) YY_CAST (yysymbol_kind_t, yystos[State]) + +#if TRACEFS_DEBUG || 0 +/* The user-facing name of the symbol whose (internal) number is + YYSYMBOL. No bounds checking. */ +static const char *yysymbol_name (yysymbol_kind_t yysymbol) YY_ATTRIBUTE_UNUSED; + +/* YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM. + First, the terminals, then, starting at YYNTOKENS, nonterminals. */ +static const char *const yytname[] = +{ + "\"end of file\"", "error", "\"invalid token\"", "AS", "SELECT", "FROM", + "JOIN", "ON", "WHERE", "PARSE_ERROR", "CAST", "NUMBER", "field_type", + "STRING", "FIELD", "LE", "GE", "EQ", "NEQ", "AND", "OR", "'+'", "'-'", + "'*'", "'/'", "'<'", "'>'", "','", "'('", "')'", "'='", "\"!=\"", "'&'", + "'~'", "'!'", "$accept", "start", "label", "select", "select_statement", + "selection_list", "selection", "selection_expr", "selection_addition", + "item", "field", "named_field", "name", "str_val", "val", "compare", + "compare_and_or", "compare_items", "compare_cmds", "compare_list", + "where_clause", "opt_where_clause", "opt_join_clause", "table_exp", + "from_clause", "join_clause", "match", "match_clause", YY_NULLPTR +}; + +static const char * +yysymbol_name (yysymbol_kind_t yysymbol) +{ + return yytname[yysymbol]; +} +#endif + +#ifdef YYPRINT +/* YYTOKNUM[NUM] -- (External) token number corresponding to the + (internal) symbol number NUM (which must be that of a token). */ +static const yytype_int16 yytoknum[] = +{ + 0, 256, 257, 258, 259, 260, 261, 262, 263, 264, + 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, + 275, 43, 45, 42, 47, 60, 62, 44, 40, 41, + 61, 276, 38, 126, 33 +}; +#endif + +#define YYPACT_NINF (-58) + +#define yypact_value_is_default(Yyn) \ + ((Yyn) == YYPACT_NINF) + +#define YYTABLE_NINF (-1) + +#define yytable_value_is_error(Yyn) \ + 0 + + /* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing + STATE-NUM. */ +static const yytype_int8 yypact[] = +{ + 4, -58, 13, 11, -58, -58, 5, -58, 43, 53, + 45, 29, -58, 19, 43, 44, 32, 60, -58, 73, + 11, 75, -58, -58, -58, 43, 43, 87, -58, -58, + 29, -58, -58, -58, 60, 83, -58, -58, -58, -58, + -58, 78, -58, 86, 14, -58, -58, 65, 60, -4, + -12, 34, -58, 76, -15, -58, -58, -10, 68, -58, + 1, -58, 18, -4, -58, 33, 33, 33, 33, 33, + 33, 33, 33, 33, 84, 14, 14, 14, 60, 60, + 60, -4, -58, -4, -4, -58, 49, -58, -58, -58, + -58, -58, -58, -58, -58, -58, -58, -58, -58, -58, + -58, -58, -58, -58, -58, -58, 51, -58, -58, -58, + -58 +}; + + /* YYDEFACT[STATE-NUM] -- Default reduction number in state STATE-NUM. + Performed when YYTABLE does not specify something else to do. Zero + means the default is an error. */ +static const yytype_int8 yydefact[] = +{ + 0, 5, 0, 0, 2, 1, 0, 20, 0, 0, + 7, 9, 13, 11, 0, 0, 0, 0, 6, 53, + 0, 0, 22, 10, 4, 0, 0, 0, 14, 12, + 20, 56, 19, 18, 0, 51, 54, 8, 3, 16, + 17, 0, 21, 0, 0, 52, 55, 0, 0, 0, + 0, 0, 45, 46, 47, 50, 15, 0, 60, 57, + 0, 40, 0, 0, 44, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 39, 0, 0, 42, 0, 25, 23, 24, + 28, 29, 31, 32, 26, 27, 30, 33, 34, 35, + 41, 49, 48, 59, 58, 61, 0, 37, 36, 43, + 38 +}; + + /* YYPGOTO[NTERM-NUM]. */ +static const yytype_int8 yypgoto[] = +{ + -58, -58, 69, -58, -58, 80, -58, -58, 90, -16, + -3, -58, 81, 27, 15, -41, -57, 28, -58, -21, + -58, -58, -58, -58, -58, -58, -58, 24 +}; + + /* YYDEFGOTO[NTERM-NUM]. */ +static const yytype_int8 yydefgoto[] = +{ + -1, 2, 23, 3, 4, 9, 10, 11, 12, 57, + 51, 33, 24, 89, 90, 61, 62, 53, 54, 55, + 45, 46, 35, 18, 19, 36, 58, 59 +}; + + /* YYTABLE[YYPACT[STATE-NUM]] -- What to do in state STATE-NUM. If + positive, shift that token. If negative, reduce the rule whose + number is the opposite. If YYTABLE_NINF, syntax error. */ +static const yytype_int8 yytable[] = +{ + 13, 31, 7, 52, 76, 16, 86, 78, 1, 64, + 7, 27, 77, 5, 32, 7, 63, 13, 43, 82, + 79, 6, 39, 40, 106, 7, 107, 108, 7, 81, + 60, 32, 21, 14, 52, 52, 52, 83, 84, 8, + 25, 26, 49, 22, 87, 32, 88, 85, 50, 65, + 66, 67, 68, 25, 26, 101, 102, 7, 17, 69, + 70, 29, 103, 104, 71, 72, 73, 74, 83, 84, + 83, 84, 20, 28, 30, 32, 32, 32, 109, 34, + 110, 91, 92, 93, 94, 95, 96, 97, 98, 22, + 41, 44, 47, 48, 56, 80, 75, 88, 15, 42, + 37, 99, 38, 100, 105 +}; + +static const yytype_int8 yycheck[] = +{ + 3, 17, 14, 44, 19, 8, 63, 17, 4, 50, + 14, 14, 27, 0, 17, 14, 28, 20, 34, 60, + 30, 10, 25, 26, 81, 14, 83, 84, 14, 28, + 34, 34, 3, 28, 75, 76, 77, 19, 20, 28, + 21, 22, 28, 14, 11, 48, 13, 29, 34, 15, + 16, 17, 18, 21, 22, 76, 77, 14, 5, 25, + 26, 29, 78, 79, 30, 31, 32, 33, 19, 20, + 19, 20, 27, 29, 14, 78, 79, 80, 29, 6, + 29, 66, 67, 68, 69, 70, 71, 72, 73, 14, + 3, 8, 14, 7, 29, 27, 20, 13, 8, 30, + 20, 74, 21, 75, 80 +}; + + /* YYSTOS[STATE-NUM] -- The (internal number of the) accessing + symbol of state STATE-NUM. */ +static const yytype_int8 yystos[] = +{ + 0, 4, 36, 38, 39, 0, 10, 14, 28, 40, + 41, 42, 43, 45, 28, 43, 45, 5, 58, 59, + 27, 3, 14, 37, 47, 21, 22, 45, 29, 29, + 14, 44, 45, 46, 6, 57, 60, 40, 47, 45, + 45, 3, 37, 44, 8, 55, 56, 14, 7, 28, + 34, 45, 50, 52, 53, 54, 29, 44, 61, 62, + 34, 50, 51, 28, 50, 15, 16, 17, 18, 25, + 26, 30, 31, 32, 33, 20, 19, 27, 17, 30, + 27, 28, 50, 19, 20, 29, 51, 11, 13, 48, + 49, 49, 49, 49, 49, 49, 49, 49, 49, 48, + 52, 54, 54, 44, 44, 62, 51, 51, 51, 29, + 29 +}; + + /* YYR1[YYN] -- Symbol number of symbol that rule YYN derives. */ +static const yytype_int8 yyr1[] = +{ + 0, 35, 36, 37, 37, 38, 39, 40, 40, 41, + 41, 42, 42, 42, 42, 42, 43, 43, 44, 44, + 45, 46, 47, 48, 49, 49, 50, 50, 50, 50, + 50, 50, 50, 50, 50, 50, 51, 51, 51, 51, + 51, 52, 52, 52, 52, 52, 53, 54, 54, 54, + 55, 56, 56, 57, 57, 58, 59, 60, 61, 61, + 62, 62 +}; + + /* YYR2[YYN] -- Number of symbols on the right hand side of rule YYN. */ +static const yytype_int8 yyr2[] = +{ + 0, 2, 1, 2, 1, 1, 3, 1, 3, 1, + 2, 1, 3, 1, 3, 6, 3, 3, 1, 1, + 1, 2, 1, 1, 1, 1, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 4, 2, + 1, 3, 3, 4, 2, 1, 1, 1, 3, 3, + 2, 0, 1, 0, 1, 3, 2, 4, 3, 3, + 1, 3 +}; + + +enum { YYENOMEM = -2 }; + +#define yyerrok (yyerrstatus = 0) +#define yyclearin (yychar = TRACEFS_EMPTY) + +#define YYACCEPT goto yyacceptlab +#define YYABORT goto yyabortlab +#define YYERROR goto yyerrorlab + + +#define YYRECOVERING() (!!yyerrstatus) + +#define YYBACKUP(Token, Value) \ + do \ + if (yychar == TRACEFS_EMPTY) \ + { \ + yychar = (Token); \ + yylval = (Value); \ + YYPOPSTACK (yylen); \ + yystate = *yyssp; \ + goto yybackup; \ + } \ + else \ + { \ + yyerror (sb, YY_("syntax error: cannot back up")); \ + YYERROR; \ + } \ + while (0) + +/* Backward compatibility with an undocumented macro. + Use TRACEFS_error or TRACEFS_UNDEF. */ +#define YYERRCODE TRACEFS_UNDEF + + +/* Enable debugging if requested. */ +#if TRACEFS_DEBUG + +# ifndef YYFPRINTF +# include /* INFRINGES ON USER NAME SPACE */ +# define YYFPRINTF fprintf +# endif + +# define YYDPRINTF(Args) \ +do { \ + if (yydebug) \ + YYFPRINTF Args; \ +} while (0) + +/* This macro is provided for backward compatibility. */ +# ifndef YY_LOCATION_PRINT +# define YY_LOCATION_PRINT(File, Loc) ((void) 0) +# endif + + +# define YY_SYMBOL_PRINT(Title, Kind, Value, Location) \ +do { \ + if (yydebug) \ + { \ + YYFPRINTF (stderr, "%s ", Title); \ + yy_symbol_print (stderr, \ + Kind, Value, sb); \ + YYFPRINTF (stderr, "\n"); \ + } \ +} while (0) + + +/*-----------------------------------. +| Print this symbol's value on YYO. | +`-----------------------------------*/ + +static void +yy_symbol_value_print (FILE *yyo, + yysymbol_kind_t yykind, YYSTYPE const * const yyvaluep, struct sqlhist_bison *sb) +{ + FILE *yyoutput = yyo; + YYUSE (yyoutput); + YYUSE (sb); + if (!yyvaluep) + return; +# ifdef YYPRINT + if (yykind < YYNTOKENS) + YYPRINT (yyo, yytoknum[yykind], *yyvaluep); +# endif + YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN + YYUSE (yykind); + YY_IGNORE_MAYBE_UNINITIALIZED_END +} + + +/*---------------------------. +| Print this symbol on YYO. | +`---------------------------*/ + +static void +yy_symbol_print (FILE *yyo, + yysymbol_kind_t yykind, YYSTYPE const * const yyvaluep, struct sqlhist_bison *sb) +{ + YYFPRINTF (yyo, "%s %s (", + yykind < YYNTOKENS ? "token" : "nterm", yysymbol_name (yykind)); + + yy_symbol_value_print (yyo, yykind, yyvaluep, sb); + YYFPRINTF (yyo, ")"); +} + +/*------------------------------------------------------------------. +| yy_stack_print -- Print the state stack from its BOTTOM up to its | +| TOP (included). | +`------------------------------------------------------------------*/ + +static void +yy_stack_print (yy_state_t *yybottom, yy_state_t *yytop) +{ + YYFPRINTF (stderr, "Stack now"); + for (; yybottom <= yytop; yybottom++) + { + int yybot = *yybottom; + YYFPRINTF (stderr, " %d", yybot); + } + YYFPRINTF (stderr, "\n"); +} + +# define YY_STACK_PRINT(Bottom, Top) \ +do { \ + if (yydebug) \ + yy_stack_print ((Bottom), (Top)); \ +} while (0) + + +/*------------------------------------------------. +| Report that the YYRULE is going to be reduced. | +`------------------------------------------------*/ + +static void +yy_reduce_print (yy_state_t *yyssp, YYSTYPE *yyvsp, + int yyrule, struct sqlhist_bison *sb) +{ + int yylno = yyrline[yyrule]; + int yynrhs = yyr2[yyrule]; + int yyi; + YYFPRINTF (stderr, "Reducing stack by rule %d (line %d):\n", + yyrule - 1, yylno); + /* The symbols being reduced. */ + for (yyi = 0; yyi < yynrhs; yyi++) + { + YYFPRINTF (stderr, " $%d = ", yyi + 1); + yy_symbol_print (stderr, + YY_ACCESSING_SYMBOL (+yyssp[yyi + 1 - yynrhs]), + &yyvsp[(yyi + 1) - (yynrhs)], sb); + YYFPRINTF (stderr, "\n"); + } +} + +# define YY_REDUCE_PRINT(Rule) \ +do { \ + if (yydebug) \ + yy_reduce_print (yyssp, yyvsp, Rule, sb); \ +} while (0) + +/* Nonzero means print parse trace. It is left uninitialized so that + multiple parsers can coexist. */ +int yydebug; +#else /* !TRACEFS_DEBUG */ +# define YYDPRINTF(Args) ((void) 0) +# define YY_SYMBOL_PRINT(Title, Kind, Value, Location) +# define YY_STACK_PRINT(Bottom, Top) +# define YY_REDUCE_PRINT(Rule) +#endif /* !TRACEFS_DEBUG */ + + +/* YYINITDEPTH -- initial size of the parser's stacks. */ +#ifndef YYINITDEPTH +# define YYINITDEPTH 200 +#endif + +/* YYMAXDEPTH -- maximum size the stacks can grow to (effective only + if the built-in stack extension method is used). + + Do not make this value too large; the results are undefined if + YYSTACK_ALLOC_MAXIMUM < YYSTACK_BYTES (YYMAXDEPTH) + evaluated with infinite-precision integer arithmetic. */ + +#ifndef YYMAXDEPTH +# define YYMAXDEPTH 10000 +#endif + + + + + + +/*-----------------------------------------------. +| Release the memory associated to this symbol. | +`-----------------------------------------------*/ + +static void +yydestruct (const char *yymsg, + yysymbol_kind_t yykind, YYSTYPE *yyvaluep, struct sqlhist_bison *sb) +{ + YYUSE (yyvaluep); + YYUSE (sb); + if (!yymsg) + yymsg = "Deleting"; + YY_SYMBOL_PRINT (yymsg, yykind, yyvaluep, yylocationp); + + YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN + YYUSE (yykind); + YY_IGNORE_MAYBE_UNINITIALIZED_END +} + + + + + + +/*----------. +| yyparse. | +`----------*/ + +int +yyparse (struct sqlhist_bison *sb) +{ +/* The lookahead symbol. */ +int yychar; + + +/* The semantic value of the lookahead symbol. */ +/* Default value used for initialization, for pacifying older GCCs + or non-GCC compilers. */ +YY_INITIAL_VALUE (static YYSTYPE yyval_default;) +YYSTYPE yylval YY_INITIAL_VALUE (= yyval_default); + + /* Number of syntax errors so far. */ + int yynerrs; + + yy_state_fast_t yystate; + /* Number of tokens to shift before error messages enabled. */ + int yyerrstatus; + + /* The stacks and their tools: + 'yyss': related to states. + 'yyvs': related to semantic values. + + Refer to the stacks through separate pointers, to allow yyoverflow + to reallocate them elsewhere. */ + + /* Their size. */ + YYPTRDIFF_T yystacksize; + + /* The state stack. */ + yy_state_t yyssa[YYINITDEPTH]; + yy_state_t *yyss; + yy_state_t *yyssp; + + /* The semantic value stack. */ + YYSTYPE yyvsa[YYINITDEPTH]; + YYSTYPE *yyvs; + YYSTYPE *yyvsp; + + int yyn; + /* The return value of yyparse. */ + int yyresult; + /* Lookahead token as an internal (translated) token number. */ + yysymbol_kind_t yytoken = YYSYMBOL_YYEMPTY; + /* The variables used to return semantic value and location from the + action routines. */ + YYSTYPE yyval; + + + +#define YYPOPSTACK(N) (yyvsp -= (N), yyssp -= (N)) + + /* The number of symbols on the RHS of the reduced rule. + Keep to zero when no symbol should be popped. */ + int yylen = 0; + + yynerrs = 0; + yystate = 0; + yyerrstatus = 0; + + yystacksize = YYINITDEPTH; + yyssp = yyss = yyssa; + yyvsp = yyvs = yyvsa; + + + YYDPRINTF ((stderr, "Starting parse\n")); + + yychar = TRACEFS_EMPTY; /* Cause a token to be read. */ + goto yysetstate; + + +/*------------------------------------------------------------. +| yynewstate -- push a new state, which is found in yystate. | +`------------------------------------------------------------*/ +yynewstate: + /* In all cases, when you get here, the value and location stacks + have just been pushed. So pushing a state here evens the stacks. */ + yyssp++; + + +/*--------------------------------------------------------------------. +| yysetstate -- set current state (the top of the stack) to yystate. | +`--------------------------------------------------------------------*/ +yysetstate: + YYDPRINTF ((stderr, "Entering state %d\n", yystate)); + YY_ASSERT (0 <= yystate && yystate < YYNSTATES); + YY_IGNORE_USELESS_CAST_BEGIN + *yyssp = YY_CAST (yy_state_t, yystate); + YY_IGNORE_USELESS_CAST_END + YY_STACK_PRINT (yyss, yyssp); + + if (yyss + yystacksize - 1 <= yyssp) +#if !defined yyoverflow && !defined YYSTACK_RELOCATE + goto yyexhaustedlab; +#else + { + /* Get the current used size of the three stacks, in elements. */ + YYPTRDIFF_T yysize = yyssp - yyss + 1; + +# if defined yyoverflow + { + /* Give user a chance to reallocate the stack. Use copies of + these so that the &'s don't force the real ones into + memory. */ + yy_state_t *yyss1 = yyss; + YYSTYPE *yyvs1 = yyvs; + + /* Each stack pointer address is followed by the size of the + data in use in that stack, in bytes. This used to be a + conditional around just the two extra args, but that might + be undefined if yyoverflow is a macro. */ + yyoverflow (YY_("memory exhausted"), + &yyss1, yysize * YYSIZEOF (*yyssp), + &yyvs1, yysize * YYSIZEOF (*yyvsp), + &yystacksize); + yyss = yyss1; + yyvs = yyvs1; + } +# else /* defined YYSTACK_RELOCATE */ + /* Extend the stack our own way. */ + if (YYMAXDEPTH <= yystacksize) + goto yyexhaustedlab; + yystacksize *= 2; + if (YYMAXDEPTH < yystacksize) + yystacksize = YYMAXDEPTH; + + { + yy_state_t *yyss1 = yyss; + union yyalloc *yyptr = + YY_CAST (union yyalloc *, + YYSTACK_ALLOC (YY_CAST (YYSIZE_T, YYSTACK_BYTES (yystacksize)))); + if (! yyptr) + goto yyexhaustedlab; + YYSTACK_RELOCATE (yyss_alloc, yyss); + YYSTACK_RELOCATE (yyvs_alloc, yyvs); +# undef YYSTACK_RELOCATE + if (yyss1 != yyssa) + YYSTACK_FREE (yyss1); + } +# endif + + yyssp = yyss + yysize - 1; + yyvsp = yyvs + yysize - 1; + + YY_IGNORE_USELESS_CAST_BEGIN + YYDPRINTF ((stderr, "Stack size increased to %ld\n", + YY_CAST (long, yystacksize))); + YY_IGNORE_USELESS_CAST_END + + if (yyss + yystacksize - 1 <= yyssp) + YYABORT; + } +#endif /* !defined yyoverflow && !defined YYSTACK_RELOCATE */ + + if (yystate == YYFINAL) + YYACCEPT; + + goto yybackup; + + +/*-----------. +| yybackup. | +`-----------*/ +yybackup: + /* Do appropriate processing given the current state. Read a + lookahead token if we need one and don't already have one. */ + + /* First try to decide what to do without reference to lookahead token. */ + yyn = yypact[yystate]; + if (yypact_value_is_default (yyn)) + goto yydefault; + + /* Not known => get a lookahead token if don't already have one. */ + + /* YYCHAR is either empty, or end-of-input, or a valid lookahead. */ + if (yychar == TRACEFS_EMPTY) + { + YYDPRINTF ((stderr, "Reading a token\n")); + yychar = yylex (&yylval, scanner); + } + + if (yychar <= TRACEFS_EOF) + { + yychar = TRACEFS_EOF; + yytoken = YYSYMBOL_YYEOF; + YYDPRINTF ((stderr, "Now at end of input.\n")); + } + else if (yychar == TRACEFS_error) + { + /* The scanner already issued an error message, process directly + to error recovery. But do not keep the error token as + lookahead, it is too special and may lead us to an endless + loop in error recovery. */ + yychar = TRACEFS_UNDEF; + yytoken = YYSYMBOL_YYerror; + goto yyerrlab1; + } + else + { + yytoken = YYTRANSLATE (yychar); + YY_SYMBOL_PRINT ("Next token is", yytoken, &yylval, &yylloc); + } + + /* If the proper action on seeing token YYTOKEN is to reduce or to + detect an error, take that action. */ + yyn += yytoken; + if (yyn < 0 || YYLAST < yyn || yycheck[yyn] != yytoken) + goto yydefault; + yyn = yytable[yyn]; + if (yyn <= 0) + { + if (yytable_value_is_error (yyn)) + goto yyerrlab; + yyn = -yyn; + goto yyreduce; + } + + /* Count tokens shifted since error; after three, turn off error + status. */ + if (yyerrstatus) + yyerrstatus--; + + /* Shift the lookahead token. */ + YY_SYMBOL_PRINT ("Shifting", yytoken, &yylval, &yylloc); + yystate = yyn; + YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN + *++yyvsp = yylval; + YY_IGNORE_MAYBE_UNINITIALIZED_END + + /* Discard the shifted token. */ + yychar = TRACEFS_EMPTY; + goto yynewstate; + + +/*-----------------------------------------------------------. +| yydefault -- do the default action for the current state. | +`-----------------------------------------------------------*/ +yydefault: + yyn = yydefact[yystate]; + if (yyn == 0) + goto yyerrlab; + goto yyreduce; + + +/*-----------------------------. +| yyreduce -- do a reduction. | +`-----------------------------*/ +yyreduce: + /* yyn is the number of a rule to reduce with. */ + yylen = yyr2[yyn]; + + /* If YYLEN is nonzero, implement the default value of the action: + '$$ = $1'. + + Otherwise, the following line sets YYVAL to garbage. + This behavior is undocumented and Bison + users should not rely upon it. Assigning to YYVAL + unconditionally makes the parser a bit smaller, and it avoids a + GCC warning that YYVAL may be used uninitialized. */ + yyval = yyvsp[1-yylen]; + + + YY_REDUCE_PRINT (yyn); + switch (yyn) + { + case 3: +#line 78 "sqlhist.y" + { CHECK_RETURN_PTR((yyval.string) = store_str(sb, (yyvsp[0].string))); } +#line 1328 "sqlhist.tab.c" + break; + + case 4: +#line 79 "sqlhist.y" + { CHECK_RETURN_PTR((yyval.string) = store_str(sb, (yyvsp[0].string))); } +#line 1334 "sqlhist.tab.c" + break; + + case 5: +#line 82 "sqlhist.y" + { table_start(sb); } +#line 1340 "sqlhist.tab.c" + break; + + case 9: +#line 96 "sqlhist.y" + { + CHECK_RETURN_VAL(add_selection(sb, (yyvsp[0].expr), NULL)); + } +#line 1348 "sqlhist.tab.c" + break; + + case 10: +#line 100 "sqlhist.y" + { + CHECK_RETURN_VAL(add_selection(sb, (yyvsp[-1].expr), (yyvsp[0].string))); + } +#line 1356 "sqlhist.tab.c" + break; + + case 12: +#line 107 "sqlhist.y" + { (yyval.expr) = (yyvsp[-1].expr); } +#line 1362 "sqlhist.tab.c" + break; + + case 14: +#line 109 "sqlhist.y" + { (yyval.expr) = (yyvsp[-1].expr); } +#line 1368 "sqlhist.tab.c" + break; + + case 15: +#line 110 "sqlhist.y" + { + (yyval.expr) = add_cast(sb, (yyvsp[-3].expr), (yyvsp[-1].string)); + CHECK_RETURN_PTR((yyval.expr)); + } +#line 1377 "sqlhist.tab.c" + break; + + case 16: +#line 118 "sqlhist.y" + { + (yyval.expr) = add_compare(sb, (yyvsp[-2].expr), (yyvsp[0].expr), COMPARE_ADD); + CHECK_RETURN_PTR((yyval.expr)); + } +#line 1386 "sqlhist.tab.c" + break; + + case 17: +#line 123 "sqlhist.y" + { + (yyval.expr) = add_compare(sb, (yyvsp[-2].expr), (yyvsp[0].expr), COMPARE_SUB); + CHECK_RETURN_PTR((yyval.expr)); + } +#line 1395 "sqlhist.tab.c" + break; + + case 20: +#line 135 "sqlhist.y" + { (yyval.expr) = add_field(sb, (yyvsp[0].string), NULL); CHECK_RETURN_PTR((yyval.expr)); } +#line 1401 "sqlhist.tab.c" + break; + + case 21: +#line 139 "sqlhist.y" + { (yyval.expr) = add_field(sb, (yyvsp[-1].string), (yyvsp[0].string)); CHECK_RETURN_PTR((yyval.expr)); } +#line 1407 "sqlhist.tab.c" + break; + + case 23: +#line 147 "sqlhist.y" + { (yyval.expr) = add_string(sb, (yyvsp[0].string)); CHECK_RETURN_PTR((yyval.expr)); } +#line 1413 "sqlhist.tab.c" + break; + + case 25: +#line 152 "sqlhist.y" + { (yyval.expr) = add_number(sb, (yyvsp[0].number)); CHECK_RETURN_PTR((yyval.expr)); } +#line 1419 "sqlhist.tab.c" + break; + + case 26: +#line 157 "sqlhist.y" + { (yyval.expr) = add_filter(sb, (yyvsp[-2].expr), (yyvsp[0].expr), FILTER_LT); CHECK_RETURN_PTR((yyval.expr)); } +#line 1425 "sqlhist.tab.c" + break; + + case 27: +#line 158 "sqlhist.y" + { (yyval.expr) = add_filter(sb, (yyvsp[-2].expr), (yyvsp[0].expr), FILTER_GT); CHECK_RETURN_PTR((yyval.expr)); } +#line 1431 "sqlhist.tab.c" + break; + + case 28: +#line 159 "sqlhist.y" + { (yyval.expr) = add_filter(sb, (yyvsp[-2].expr), (yyvsp[0].expr), FILTER_LE); CHECK_RETURN_PTR((yyval.expr)); } +#line 1437 "sqlhist.tab.c" + break; + + case 29: +#line 160 "sqlhist.y" + { (yyval.expr) = add_filter(sb, (yyvsp[-2].expr), (yyvsp[0].expr), FILTER_GE); CHECK_RETURN_PTR((yyval.expr)); } +#line 1443 "sqlhist.tab.c" + break; + + case 30: +#line 161 "sqlhist.y" + { (yyval.expr) = add_filter(sb, (yyvsp[-2].expr), (yyvsp[0].expr), FILTER_EQ); CHECK_RETURN_PTR((yyval.expr)); } +#line 1449 "sqlhist.tab.c" + break; + + case 31: +#line 162 "sqlhist.y" + { (yyval.expr) = add_filter(sb, (yyvsp[-2].expr), (yyvsp[0].expr), FILTER_EQ); CHECK_RETURN_PTR((yyval.expr)); } +#line 1455 "sqlhist.tab.c" + break; + + case 32: +#line 163 "sqlhist.y" + { (yyval.expr) = add_filter(sb, (yyvsp[-2].expr), (yyvsp[0].expr), FILTER_NE); CHECK_RETURN_PTR((yyval.expr)); } +#line 1461 "sqlhist.tab.c" + break; + + case 33: +#line 164 "sqlhist.y" + { (yyval.expr) = add_filter(sb, (yyvsp[-2].expr), (yyvsp[0].expr), FILTER_NE); CHECK_RETURN_PTR((yyval.expr)); } +#line 1467 "sqlhist.tab.c" + break; + + case 34: +#line 165 "sqlhist.y" + { (yyval.expr) = add_filter(sb, (yyvsp[-2].expr), (yyvsp[0].expr), FILTER_BIN_AND); CHECK_RETURN_PTR((yyval.expr)); } +#line 1473 "sqlhist.tab.c" + break; + + case 35: +#line 166 "sqlhist.y" + { (yyval.expr) = add_filter(sb, (yyvsp[-2].expr), (yyvsp[0].expr), FILTER_STR_CMP); CHECK_RETURN_PTR((yyval.expr)); } +#line 1479 "sqlhist.tab.c" + break; + + case 36: +#line 170 "sqlhist.y" + { (yyval.expr) = add_filter(sb, (yyvsp[-2].expr), (yyvsp[0].expr), FILTER_OR); CHECK_RETURN_PTR((yyval.expr)); } +#line 1485 "sqlhist.tab.c" + break; + + case 37: +#line 171 "sqlhist.y" + { (yyval.expr) = add_filter(sb, (yyvsp[-2].expr), (yyvsp[0].expr), FILTER_AND); CHECK_RETURN_PTR((yyval.expr)); } +#line 1491 "sqlhist.tab.c" + break; + + case 38: +#line 172 "sqlhist.y" + { (yyval.expr) = add_filter(sb, (yyvsp[-1].expr), NULL, FILTER_NOT_GROUP); CHECK_RETURN_PTR((yyval.expr)); } +#line 1497 "sqlhist.tab.c" + break; + + case 39: +#line 173 "sqlhist.y" + { (yyval.expr) = add_filter(sb, (yyvsp[0].expr), NULL, FILTER_NOT_GROUP); CHECK_RETURN_PTR((yyval.expr)); } +#line 1503 "sqlhist.tab.c" + break; + + case 41: +#line 178 "sqlhist.y" + { (yyval.expr) = add_filter(sb, (yyvsp[-2].expr), (yyvsp[0].expr), FILTER_OR); CHECK_RETURN_PTR((yyval.expr)); } +#line 1509 "sqlhist.tab.c" + break; + + case 42: +#line 179 "sqlhist.y" + { (yyval.expr) = add_filter(sb, (yyvsp[-1].expr), NULL, FILTER_GROUP); CHECK_RETURN_PTR((yyval.expr)); } +#line 1515 "sqlhist.tab.c" + break; + + case 43: +#line 180 "sqlhist.y" + { (yyval.expr) = add_filter(sb, (yyvsp[-1].expr), NULL, FILTER_NOT_GROUP); CHECK_RETURN_PTR((yyval.expr)); } +#line 1521 "sqlhist.tab.c" + break; + + case 44: +#line 181 "sqlhist.y" + { (yyval.expr) = add_filter(sb, (yyvsp[0].expr), NULL, FILTER_NOT_GROUP); CHECK_RETURN_PTR((yyval.expr)); } +#line 1527 "sqlhist.tab.c" + break; + + case 46: +#line 186 "sqlhist.y" + { CHECK_RETURN_VAL(add_where(sb, (yyvsp[0].expr))); } +#line 1533 "sqlhist.tab.c" + break; + + case 56: +#line 219 "sqlhist.y" + { CHECK_RETURN_VAL(add_from(sb, (yyvsp[0].expr))); } +#line 1539 "sqlhist.tab.c" + break; + + case 57: +#line 234 "sqlhist.y" + { add_to(sb, (yyvsp[-2].expr)); } +#line 1545 "sqlhist.tab.c" + break; + + case 58: +#line 238 "sqlhist.y" + { CHECK_RETURN_VAL(add_match(sb, (yyvsp[-2].expr), (yyvsp[0].expr))); } +#line 1551 "sqlhist.tab.c" + break; + + case 59: +#line 239 "sqlhist.y" + { CHECK_RETURN_VAL(add_match(sb, (yyvsp[-2].expr), (yyvsp[0].expr))); } +#line 1557 "sqlhist.tab.c" + break; + + +#line 1561 "sqlhist.tab.c" + + default: break; + } + /* User semantic actions sometimes alter yychar, and that requires + that yytoken be updated with the new translation. We take the + approach of translating immediately before every use of yytoken. + One alternative is translating here after every semantic action, + but that translation would be missed if the semantic action invokes + YYABORT, YYACCEPT, or YYERROR immediately after altering yychar or + if it invokes YYBACKUP. In the case of YYABORT or YYACCEPT, an + incorrect destructor might then be invoked immediately. In the + case of YYERROR or YYBACKUP, subsequent parser actions might lead + to an incorrect destructor call or verbose syntax error message + before the lookahead is translated. */ + YY_SYMBOL_PRINT ("-> $$ =", YY_CAST (yysymbol_kind_t, yyr1[yyn]), &yyval, &yyloc); + + YYPOPSTACK (yylen); + yylen = 0; + + *++yyvsp = yyval; + + /* Now 'shift' the result of the reduction. Determine what state + that goes to, based on the state we popped back to and the rule + number reduced by. */ + { + const int yylhs = yyr1[yyn] - YYNTOKENS; + const int yyi = yypgoto[yylhs] + *yyssp; + yystate = (0 <= yyi && yyi <= YYLAST && yycheck[yyi] == *yyssp + ? yytable[yyi] + : yydefgoto[yylhs]); + } + + goto yynewstate; + + +/*--------------------------------------. +| yyerrlab -- here on detecting error. | +`--------------------------------------*/ +yyerrlab: + /* Make sure we have latest lookahead translation. See comments at + user semantic actions for why this is necessary. */ + yytoken = yychar == TRACEFS_EMPTY ? YYSYMBOL_YYEMPTY : YYTRANSLATE (yychar); + /* If not already recovering from an error, report this error. */ + if (!yyerrstatus) + { + ++yynerrs; + yyerror (sb, YY_("syntax error")); + } + + if (yyerrstatus == 3) + { + /* If just tried and failed to reuse lookahead token after an + error, discard it. */ + + if (yychar <= TRACEFS_EOF) + { + /* Return failure if at end of input. */ + if (yychar == TRACEFS_EOF) + YYABORT; + } + else + { + yydestruct ("Error: discarding", + yytoken, &yylval, sb); + yychar = TRACEFS_EMPTY; + } + } + + /* Else will try to reuse lookahead token after shifting the error + token. */ + goto yyerrlab1; + + +/*---------------------------------------------------. +| yyerrorlab -- error raised explicitly by YYERROR. | +`---------------------------------------------------*/ +yyerrorlab: + /* Pacify compilers when the user code never invokes YYERROR and the + label yyerrorlab therefore never appears in user code. */ + if (0) + YYERROR; + + /* Do not reclaim the symbols of the rule whose action triggered + this YYERROR. */ + YYPOPSTACK (yylen); + yylen = 0; + YY_STACK_PRINT (yyss, yyssp); + yystate = *yyssp; + goto yyerrlab1; + + +/*-------------------------------------------------------------. +| yyerrlab1 -- common code for both syntax error and YYERROR. | +`-------------------------------------------------------------*/ +yyerrlab1: + yyerrstatus = 3; /* Each real token shifted decrements this. */ + + /* Pop stack until we find a state that shifts the error token. */ + for (;;) + { + yyn = yypact[yystate]; + if (!yypact_value_is_default (yyn)) + { + yyn += YYSYMBOL_YYerror; + if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYSYMBOL_YYerror) + { + yyn = yytable[yyn]; + if (0 < yyn) + break; + } + } + + /* Pop the current state because it cannot handle the error token. */ + if (yyssp == yyss) + YYABORT; + + + yydestruct ("Error: popping", + YY_ACCESSING_SYMBOL (yystate), yyvsp, sb); + YYPOPSTACK (1); + yystate = *yyssp; + YY_STACK_PRINT (yyss, yyssp); + } + + YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN + *++yyvsp = yylval; + YY_IGNORE_MAYBE_UNINITIALIZED_END + + + /* Shift the error token. */ + YY_SYMBOL_PRINT ("Shifting", YY_ACCESSING_SYMBOL (yyn), yyvsp, yylsp); + + yystate = yyn; + goto yynewstate; + + +/*-------------------------------------. +| yyacceptlab -- YYACCEPT comes here. | +`-------------------------------------*/ +yyacceptlab: + yyresult = 0; + goto yyreturn; + + +/*-----------------------------------. +| yyabortlab -- YYABORT comes here. | +`-----------------------------------*/ +yyabortlab: + yyresult = 1; + goto yyreturn; + + +#if !defined yyoverflow +/*-------------------------------------------------. +| yyexhaustedlab -- memory exhaustion comes here. | +`-------------------------------------------------*/ +yyexhaustedlab: + yyerror (sb, YY_("memory exhausted")); + yyresult = 2; + /* Fall through. */ +#endif + + +/*-----------------------------------------------------. +| yyreturn -- parsing is finished, return the result. | +`-----------------------------------------------------*/ +yyreturn: + if (yychar != TRACEFS_EMPTY) + { + /* Make sure we have latest lookahead translation. See comments at + user semantic actions for why this is necessary. */ + yytoken = YYTRANSLATE (yychar); + yydestruct ("Cleanup: discarding lookahead", + yytoken, &yylval, sb); + } + /* Do not reclaim the symbols of the rule whose action triggered + this YYABORT or YYACCEPT. */ + YYPOPSTACK (yylen); + YY_STACK_PRINT (yyss, yyssp); + while (yyssp != yyss) + { + yydestruct ("Cleanup: popping", + YY_ACCESSING_SYMBOL (+*yyssp), yyvsp, sb); + YYPOPSTACK (1); + } +#ifndef yyoverflow + if (yyss != yyssa) + YYSTACK_FREE (yyss); +#endif + + return yyresult; +} + +#line 248 "sqlhist.y" + diff --git a/src/sqlhist.tab.h b/src/sqlhist.tab.h new file mode 100644 index 0000000..b02a782 --- /dev/null +++ b/src/sqlhist.tab.h @@ -0,0 +1,118 @@ +/* A Bison parser, made by GNU Bison 3.6.4. */ + +/* Bison interface for Yacc-like parsers in C + + Copyright (C) 1984, 1989-1990, 2000-2015, 2018-2020 Free Software Foundation, + Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +/* As a special exception, you may create a larger work that contains + part or all of the Bison parser skeleton and distribute that work + under terms of your choice, so long as that work isn't itself a + parser generator using the skeleton or a modified version thereof + as a parser skeleton. Alternatively, if you modify or redistribute + the parser skeleton itself, you may (at your option) remove this + special exception, which will cause the skeleton and the resulting + Bison output files to be licensed under the GNU General Public + License without this special exception. + + This special exception was added by the Free Software Foundation in + version 2.2 of Bison. */ + +/* DO NOT RELY ON FEATURES THAT ARE NOT DOCUMENTED in the manual, + especially those whose name start with YY_ or yy_. They are + private implementation details that can be changed or removed. */ + +#ifndef YY_TRACEFS_SQLHIST_TAB_H_INCLUDED +# define YY_TRACEFS_SQLHIST_TAB_H_INCLUDED +/* Debug traces. */ +#ifndef TRACEFS_DEBUG +# if defined YYDEBUG +#if YYDEBUG +# define TRACEFS_DEBUG 1 +# else +# define TRACEFS_DEBUG 0 +# endif +# else /* ! defined YYDEBUG */ +# define TRACEFS_DEBUG 1 +# endif /* ! defined YYDEBUG */ +#endif /* ! defined TRACEFS_DEBUG */ +#if TRACEFS_DEBUG +extern int tracefs_debug; +#endif + +/* Token kinds. */ +#ifndef TRACEFS_TOKENTYPE +# define TRACEFS_TOKENTYPE + enum tracefs_tokentype + { + TRACEFS_EMPTY = -2, + TRACEFS_EOF = 0, /* "end of file" */ + TRACEFS_error = 256, /* error */ + TRACEFS_UNDEF = 257, /* "invalid token" */ + AS = 258, /* AS */ + SELECT = 259, /* SELECT */ + FROM = 260, /* FROM */ + JOIN = 261, /* JOIN */ + ON = 262, /* ON */ + WHERE = 263, /* WHERE */ + PARSE_ERROR = 264, /* PARSE_ERROR */ + CAST = 265, /* CAST */ + NUMBER = 266, /* NUMBER */ + field_type = 267, /* field_type */ + STRING = 268, /* STRING */ + FIELD = 269, /* FIELD */ + LE = 270, /* LE */ + GE = 271, /* GE */ + EQ = 272, /* EQ */ + NEQ = 273, /* NEQ */ + AND = 274, /* AND */ + OR = 275 /* OR */ + }; + typedef enum tracefs_tokentype tracefs_token_kind_t; +#endif + +/* Value type. */ +#if ! defined TRACEFS_STYPE && ! defined TRACEFS_STYPE_IS_DECLARED +union TRACEFS_STYPE +{ +#line 46 "sqlhist.y" + + int s32; + char *string; + long number; + void *expr; + +#line 99 "sqlhist.tab.h" + +}; +typedef union TRACEFS_STYPE TRACEFS_STYPE; +# define TRACEFS_STYPE_IS_TRIVIAL 1 +# define TRACEFS_STYPE_IS_DECLARED 1 +#endif + + + +int tracefs_parse (struct sqlhist_bison *sb); +/* "%code provides" blocks. */ +#line 37 "sqlhist.y" + + #define YYSTYPE TRACEFS_STYPE + #define yylex tracefs_lex + #define yyerror tracefs_error + +#line 117 "sqlhist.tab.h" + +#endif /* !YY_TRACEFS_SQLHIST_TAB_H_INCLUDED */ diff --git a/src/sqlhist.y b/src/sqlhist.y new file mode 100644 index 0000000..fade9a4 --- /dev/null +++ b/src/sqlhist.y @@ -0,0 +1,248 @@ +%{ +#include +#include +#include +#include + +#include "sqlhist-parse.h" + +#define scanner sb->scanner + +extern int yylex(YYSTYPE *yylval, void *); +extern void yyerror(struct sqlhist_bison *, char *fmt, ...); + +#define CHECK_RETURN_PTR(x) \ + do { \ + if (!(x)) { \ + printf("FAILED MEMORY: %s\n", #x); \ + return -ENOMEM; \ + } \ + } while (0) + +#define CHECK_RETURN_VAL(x) \ + do { \ + if ((x) < 0) { \ + printf("FAILED MEMORY: %s\n", #x); \ + return -ENOMEM; \ + } \ + } while (0) + +%} + +%define api.pure + +/* Change the globals to use tracefs_ prefix */ +%define api.prefix{tracefs_} +%code provides +{ + #define YYSTYPE TRACEFS_STYPE + #define yylex tracefs_lex + #define yyerror tracefs_error +} + +%lex-param {void *scanner} +%parse-param {struct sqlhist_bison *sb} + +%union { + int s32; + char *string; + long number; + void *expr; +} + +%token AS SELECT FROM JOIN ON WHERE PARSE_ERROR CAST +%token NUMBER field_type +%token STRING +%token FIELD +%token LE GE EQ NEQ AND OR + +%left '+' '-' +%left '*' '/' +%left '<' '>' +%left AND OR + +%type name label + +%type selection_expr field item named_field +%type selection_addition +%type compare compare_list compare_cmds compare_items +%type compare_and_or +%type str_val val + +%% + +start : + select_statement + ; + +label : AS name { CHECK_RETURN_PTR($$ = store_str(sb, $2)); } + | name { CHECK_RETURN_PTR($$ = store_str(sb, $1)); } + ; + +select : SELECT { table_start(sb); } + ; + +select_statement : + select selection_list table_exp + ; + +selection_list : + selection + | selection ',' selection_list + ; + +selection : + selection_expr + { + CHECK_RETURN_VAL(add_selection(sb, $1, NULL)); + } + | selection_expr label + { + CHECK_RETURN_VAL(add_selection(sb, $1, $2)); + } + ; + +selection_expr : + field + | '(' field ')' { $$ = $2; } + | selection_addition + | '(' selection_addition ')' { $$ = $2; } + | CAST '(' field AS FIELD ')' { + $$ = add_cast(sb, $3, $5); + CHECK_RETURN_PTR($$); + } + ; + +selection_addition : + field '+' field + { + $$ = add_compare(sb, $1, $3, COMPARE_ADD); + CHECK_RETURN_PTR($$); + } + | field '-' field + { + $$ = add_compare(sb, $1, $3, COMPARE_SUB); + CHECK_RETURN_PTR($$); + } + ; + +item : + named_field + | field + ; + +field : + FIELD { $$ = add_field(sb, $1, NULL); CHECK_RETURN_PTR($$); } + ; + +named_field : + FIELD label { $$ = add_field(sb, $1, $2); CHECK_RETURN_PTR($$); } + ; + +name : + FIELD + ; + +str_val : + STRING { $$ = add_string(sb, $1); CHECK_RETURN_PTR($$); } + ; + +val : + str_val + | NUMBER { $$ = add_number(sb, $1); CHECK_RETURN_PTR($$); } + ; + + +compare : + field '<' val { $$ = add_filter(sb, $1, $3, FILTER_LT); CHECK_RETURN_PTR($$); } + | field '>' val { $$ = add_filter(sb, $1, $3, FILTER_GT); CHECK_RETURN_PTR($$); } + | field LE val { $$ = add_filter(sb, $1, $3, FILTER_LE); CHECK_RETURN_PTR($$); } + | field GE val { $$ = add_filter(sb, $1, $3, FILTER_GE); CHECK_RETURN_PTR($$); } + | field '=' val { $$ = add_filter(sb, $1, $3, FILTER_EQ); CHECK_RETURN_PTR($$); } + | field EQ val { $$ = add_filter(sb, $1, $3, FILTER_EQ); CHECK_RETURN_PTR($$); } + | field NEQ val { $$ = add_filter(sb, $1, $3, FILTER_NE); CHECK_RETURN_PTR($$); } + | field "!=" val { $$ = add_filter(sb, $1, $3, FILTER_NE); CHECK_RETURN_PTR($$); } + | field '&' val { $$ = add_filter(sb, $1, $3, FILTER_BIN_AND); CHECK_RETURN_PTR($$); } + | field '~' str_val { $$ = add_filter(sb, $1, $3, FILTER_STR_CMP); CHECK_RETURN_PTR($$); } +; + +compare_and_or : + compare_and_or OR compare_and_or { $$ = add_filter(sb, $1, $3, FILTER_OR); CHECK_RETURN_PTR($$); } + | compare_and_or AND compare_and_or { $$ = add_filter(sb, $1, $3, FILTER_AND); CHECK_RETURN_PTR($$); } + | '!' '(' compare_and_or ')' { $$ = add_filter(sb, $3, NULL, FILTER_NOT_GROUP); CHECK_RETURN_PTR($$); } + | '!' compare { $$ = add_filter(sb, $2, NULL, FILTER_NOT_GROUP); CHECK_RETURN_PTR($$); } + | compare + ; + +compare_items : + compare_items OR compare_items { $$ = add_filter(sb, $1, $3, FILTER_OR); CHECK_RETURN_PTR($$); } + | '(' compare_and_or ')' { $$ = add_filter(sb, $2, NULL, FILTER_GROUP); CHECK_RETURN_PTR($$); } + | '!' '(' compare_and_or ')' { $$ = add_filter(sb, $3, NULL, FILTER_NOT_GROUP); CHECK_RETURN_PTR($$); } + | '!' compare { $$ = add_filter(sb, $2, NULL, FILTER_NOT_GROUP); CHECK_RETURN_PTR($$); } + | compare + ; + +compare_cmds : + compare_items { CHECK_RETURN_VAL(add_where(sb, $1)); } + ; + +/* + * Top level AND is equal to ',' but the compare_cmds in them must + * all be of for the same event (start or end exclusive). + * That is, OR is not to be used between start and end events. + */ +compare_list : + compare_cmds + | compare_cmds ',' compare_list + | compare_cmds AND compare_list + ; + +where_clause : + WHERE compare_list + ; + +opt_where_clause : + /* empty */ + | where_clause +; + +opt_join_clause : + /* empty set */ + | join_clause + ; + +table_exp : + from_clause opt_join_clause opt_where_clause + ; + +from_clause : + FROM item { CHECK_RETURN_VAL(add_from(sb, $2)); } + +/* + * Select from a from clause confuses the variable parsing. + * disable it for now. + + | FROM '(' select_statement ')' label + { + from_table_end($5); + $$ = store_printf("FROM (%s) AS %s", $3, $5); + } +*/ + ; + +join_clause : + JOIN item ON match_clause { add_to(sb, $2); } + ; + +match : + item '=' item { CHECK_RETURN_VAL(add_match(sb, $1, $3)); } + | item EQ item { CHECK_RETURN_VAL(add_match(sb, $1, $3)); } + + ; + +match_clause : + match + | match ',' match_clause + ; + +%% diff --git a/src/tracefs-dynevents.c b/src/tracefs-dynevents.c new file mode 100644 index 0000000..7a3c45c --- /dev/null +++ b/src/tracefs-dynevents.c @@ -0,0 +1,779 @@ +// SPDX-License-Identifier: LGPL-2.1 +/* + * Copyright (C) 2021 VMware Inc, Steven Rostedt + * + * Updates: + * Copyright (C) 2021, VMware, Tzvetomir Stoyanov + * + */ +#include +#include +#include +#include +#include +#include +#include + +#include "tracefs.h" +#include "tracefs-local.h" + +#define DYNEVENTS_EVENTS "dynamic_events" +#define KPROBE_EVENTS "kprobe_events" +#define UPROBE_EVENTS "uprobe_events" +#define SYNTH_EVENTS "synthetic_events" +#define DYNEVENTS_DEFAULT_GROUP "dynamic" + +#define EVENT_INDEX(B) (ffs(B) - 1) + +struct dyn_events_desc; +static int dyn_generic_parse(struct dyn_events_desc *, + const char *, char *, struct tracefs_dynevent **); +static int dyn_synth_parse(struct dyn_events_desc *, + const char *, char *, struct tracefs_dynevent **); +static int dyn_generic_del(struct dyn_events_desc *, struct tracefs_dynevent *); +static int dyn_synth_del(struct dyn_events_desc *, struct tracefs_dynevent *); + +struct dyn_events_desc { + enum tracefs_dynevent_type type; + const char *file; + const char *prefix; + int (*del)(struct dyn_events_desc *desc, struct tracefs_dynevent *dyn); + int (*parse)(struct dyn_events_desc *desc, const char *group, + char *line, struct tracefs_dynevent **ret_dyn); +} dynevents[] = { + {TRACEFS_DYNEVENT_KPROBE, KPROBE_EVENTS, "p", dyn_generic_del, dyn_generic_parse}, + {TRACEFS_DYNEVENT_KRETPROBE, KPROBE_EVENTS, "r", dyn_generic_del, dyn_generic_parse}, + {TRACEFS_DYNEVENT_UPROBE, UPROBE_EVENTS, "p", dyn_generic_del, dyn_generic_parse}, + {TRACEFS_DYNEVENT_URETPROBE, UPROBE_EVENTS, "r", dyn_generic_del, dyn_generic_parse}, + {TRACEFS_DYNEVENT_EPROBE, "", "e", dyn_generic_del, dyn_generic_parse}, + {TRACEFS_DYNEVENT_SYNTH, SYNTH_EVENTS, "", dyn_synth_del, dyn_synth_parse}, +}; + + + +static int dyn_generic_del(struct dyn_events_desc *desc, struct tracefs_dynevent *dyn) +{ + char *str; + int ret; + + if (dyn->system) + ret = asprintf(&str, "-:%s/%s", dyn->system, dyn->event); + else + ret = asprintf(&str, "-:%s", dyn->event); + + if (ret < 0) + return -1; + + ret = tracefs_instance_file_append(NULL, desc->file, str); + free(str); + + return ret < 0 ? ret : 0; +} + +/** + * tracefs_dynevent_free - Free a dynamic event context + * @devent: Pointer to a dynamic event context + * + * The dynamic event, described by this context, is not + * removed from the system by this API. It only frees the memory. + */ +void tracefs_dynevent_free(struct tracefs_dynevent *devent) +{ + if (!devent) + return; + free(devent->system); + free(devent->event); + free(devent->address); + free(devent->format); + free(devent->prefix); + free(devent->trace_file); + free(devent); +} + +static void parse_prefix(char *word, char **prefix, char **system, char **name) +{ + char *sav; + + *prefix = NULL; + *system = NULL; + *name = NULL; + + *prefix = strtok_r(word, ":", &sav); + *system = strtok_r(NULL, "/", &sav); + if (!(*system)) + return; + + *name = strtok_r(NULL, " \t", &sav); + if (!(*name)) { + *name = *system; + *system = NULL; + } +} + +/* + * Parse lines from dynamic_events, kprobe_events and uprobe_events files + * PREFIX[:[SYSTEM/]EVENT] [ADDRSS] [FORMAT] + */ +static int dyn_generic_parse(struct dyn_events_desc *desc, const char *group, + char *line, struct tracefs_dynevent **ret_dyn) +{ + struct tracefs_dynevent *dyn; + char *word; + char *format = NULL; + char *address = NULL; + char *system; + char *prefix; + char *event; + char *sav; + + if (strncmp(line, desc->prefix, strlen(desc->prefix))) + return -1; + + word = strtok_r(line, " \t", &sav); + if (!word || *word == '\0') + return -1; + + parse_prefix(word, &prefix, &system, &event); + if (!prefix) + return -1; + + if (desc->type != TRACEFS_DYNEVENT_SYNTH) { + address = strtok_r(NULL, " \t", &sav); + if (!address || *address == '\0') + return -1; + } + + format = strtok_r(NULL, "", &sav); + + /* KPROBEs and UPROBEs share the same prefix, check the format */ + if (desc->type & (TRACEFS_DYNEVENT_UPROBE | TRACEFS_DYNEVENT_URETPROBE)) { + if (!strchr(address, '/')) + return -1; + } + + if (group && (!system || strcmp(group, system) != 0)) + return -1; + + if (!ret_dyn) + return 0; + + dyn = calloc(1, sizeof(*dyn)); + if (!dyn) + return -1; + + dyn->type = desc->type; + dyn->trace_file = strdup(desc->file); + if (!dyn->trace_file) + goto error; + + dyn->prefix = strdup(prefix); + if (!dyn->prefix) + goto error; + + if (system) { + dyn->system = strdup(system); + if (!dyn->system) + goto error; + } + + if (event) { + dyn->event = strdup(event); + if (!dyn->event) + goto error; + } + + if (address) { + dyn->address = strdup(address); + if (!dyn->address) + goto error; + } + + if (format) { + dyn->format = strdup(format); + if (!dyn->format) + goto error; + } + + *ret_dyn = dyn; + return 0; +error: + tracefs_dynevent_free(dyn); + return -1; +} + +static int dyn_synth_del(struct dyn_events_desc *desc, struct tracefs_dynevent *dyn) +{ + char *str; + int ret; + + if (!strcmp(desc->file, DYNEVENTS_EVENTS)) + return dyn_generic_del(desc, dyn); + + ret = asprintf(&str, "!%s", dyn->event); + if (ret < 0) + return -1; + + ret = tracefs_instance_file_append(NULL, desc->file, str); + free(str); + + return ret < 0 ? ret : 0; +} + +/* + * Parse lines from synthetic_events file + * EVENT ARG [ARG] + */ +static int dyn_synth_parse(struct dyn_events_desc *desc, const char *group, + char *line, struct tracefs_dynevent **ret_dyn) +{ + struct tracefs_dynevent *dyn; + char *format; + char *event; + char *sav; + + if (!strcmp(desc->file, DYNEVENTS_EVENTS)) + return dyn_generic_parse(desc, group, line, ret_dyn); + + /* synthetic_events file has slightly different syntax */ + event = strtok_r(line, " \t", &sav); + if (!event || *event == '\0') + return -1; + + format = strtok_r(NULL, "", &sav); + if (!format || *format == '\0') + return -1; + + if (!ret_dyn) + return 0; + + dyn = calloc(1, sizeof(*dyn)); + if (!dyn) + return -1; + + dyn->type = desc->type; + dyn->trace_file = strdup(desc->file); + if (!dyn->trace_file) + goto error; + + dyn->event = strdup(event); + if (!dyn->event) + goto error; + + dyn->format = strdup(format+1); + if (!dyn->format) + goto error; + + *ret_dyn = dyn; + return 0; +error: + tracefs_dynevent_free(dyn); + return -1; +} + +static void init_devent_desc(void) +{ + int i; + + BUILD_BUG_ON(ARRAY_SIZE(dynevents) != EVENT_INDEX(TRACEFS_DYNEVENT_MAX)); + + if (!tracefs_file_exists(NULL, DYNEVENTS_EVENTS)) + return; + + /* Use ftrace dynamic_events, if available */ + for (i = 0; i < EVENT_INDEX(TRACEFS_DYNEVENT_MAX); i++) + dynevents[i].file = DYNEVENTS_EVENTS; + + dynevents[EVENT_INDEX(TRACEFS_DYNEVENT_SYNTH)].prefix = "s"; +} + +static struct dyn_events_desc *get_devent_desc(enum tracefs_dynevent_type type) +{ + + static bool init; + + if (type >= TRACEFS_DYNEVENT_MAX) + return NULL; + + if (!init) { + init_devent_desc(); + init = true; + } + + return &dynevents[EVENT_INDEX(type)]; +} + +/** + * dynevent_alloc - Allocate new dynamic event + * @type: Type of the dynamic event + * @system: The system name (NULL for the default dynamic) + * @event: Name of the event + * @addr: The function and offset (or address) to insert the probe + * @format: The format string to define the probe. + * + * Allocate a dynamic event context that will be in the @system group + * (or dynamic if @system is NULL). Have the name of @event and + * will be associated to @addr, if applicable for that event type + * (function name, with or without offset, or a address). And the @format will + * define the format of the kprobe. + * The dynamic event is not created in the system. + * + * Return a pointer to a dynamic event context on success, or NULL on error. + * The returned pointer must be freed with tracefs_dynevent_free() + * + * errno will be set to EINVAL if event is NULL. + */ +__hidden struct tracefs_dynevent * +dynevent_alloc(enum tracefs_dynevent_type type, const char *system, + const char *event, const char *address, const char *format) +{ + struct tracefs_dynevent *devent; + struct dyn_events_desc *desc; + + if (!event) { + errno = EINVAL; + return NULL; + } + + desc = get_devent_desc(type); + if (!desc || !desc->file) { + errno = ENOTSUP; + return NULL; + } + + devent = calloc(1, sizeof(*devent)); + if (!devent) + return NULL; + + devent->type = type; + devent->trace_file = strdup(desc->file); + if (!devent->trace_file) + goto err; + + if (!system) + system = DYNEVENTS_DEFAULT_GROUP; + devent->system = strdup(system); + if (!devent->system) + goto err; + + devent->event = strdup(event); + if (!devent->event) + goto err; + + devent->prefix = strdup(desc->prefix); + if (!devent->prefix) + goto err; + + if (address) { + devent->address = strdup(address); + if (!devent->address) + goto err; + } + if (format) { + devent->format = strdup(format); + if (!devent->format) + goto err; + } + + return devent; +err: + tracefs_dynevent_free(devent); + return NULL; +} + +/** + * tracefs_dynevent_create - Create a dynamic event in the system + * @devent: Pointer to a dynamic event context, describing the event + * + * Return 0 on success, or -1 on error. + */ +int tracefs_dynevent_create(struct tracefs_dynevent *devent) +{ + char *str; + int ret; + + if (!devent) + return -1; + + if (devent->system && devent->system[0]) + ret = asprintf(&str, "%s%s%s/%s %s %s\n", + devent->prefix, strlen(devent->prefix) ? ":" : "", + devent->system, devent->event, + devent->address ? devent->address : "", + devent->format ? devent->format : ""); + else + ret = asprintf(&str, "%s%s%s %s %s\n", + devent->prefix, strlen(devent->prefix) ? ":" : "", + devent->event, + devent->address ? devent->address : "", + devent->format ? devent->format : ""); + if (ret < 0) + return -1; + + ret = tracefs_instance_file_append(NULL, devent->trace_file, str); + free(str); + + return ret < 0 ? ret : 0; +} + +static void disable_events(const char *system, const char *event, + char **list) +{ + struct tracefs_instance *instance; + int i; + + /* + * Note, this will not fail even on error. + * That is because even if something fails, it may still + * work enough to clear the kprobes. If that's the case + * the clearing after the loop will succeed and the function + * is a success, even though other parts had failed. If + * one of the kprobe events is enabled in one of the + * instances that fail, then the clearing will fail too + * and the function will return an error. + */ + + tracefs_event_disable(NULL, system, event); + /* No need to test results */ + + if (!list) + return; + + for (i = 0; list[i]; i++) { + instance = tracefs_instance_alloc(NULL, list[i]); + /* If this fails, try the next one */ + if (!instance) + continue; + tracefs_event_disable(instance, system, event); + tracefs_instance_free(instance); + } +} + +/** + * tracefs_dynevent_destroy - Remove a dynamic event from the system + * @devent: A dynamic event context, describing the dynamic event that will be deleted. + * @force: Will attempt to disable all events before removing them. + * + * The dynamic event context is not freed by this API. It only removes the event from the system. + * If there are any enabled events, and @force is not set, then it will error with -1 and errno + * to be EBUSY. + * + * Return 0 on success, or -1 on error. + */ +int tracefs_dynevent_destroy(struct tracefs_dynevent *devent, bool force) +{ + struct dyn_events_desc *desc; + char **instance_list; + + if (!devent) + return -1; + + if (force) { + instance_list = tracefs_instances(NULL); + disable_events(devent->system, devent->event, instance_list); + tracefs_list_free(instance_list); + } + + desc = get_devent_desc(devent->type); + if (!desc) + return -1; + + return desc->del(desc, devent); +} + +static int get_all_dynevents(enum tracefs_dynevent_type type, const char *system, + struct tracefs_dynevent ***ret_all) +{ + struct dyn_events_desc *desc; + struct tracefs_dynevent *devent, **tmp, **all = NULL; + char *content; + int count = 0; + char *line; + char *next; + int ret; + + desc = get_devent_desc(type); + if (!desc) + return -1; + + content = tracefs_instance_file_read(NULL, desc->file, NULL); + if (!content) + return -1; + + line = content; + do { + next = strchr(line, '\n'); + if (next) + *next = '\0'; + ret = desc->parse(desc, system, line, ret_all ? &devent : NULL); + if (!ret) { + if (ret_all) { + tmp = realloc(all, (count + 1) * sizeof(*tmp)); + if (!tmp) + goto error; + all = tmp; + all[count] = devent; + } + count++; + } + line = next + 1; + } while (next); + + free(content); + if (ret_all) + *ret_all = all; + return count; + +error: + free(content); + free(all); + return -1; +} + +/** + * tracefs_dynevent_list_free - Deletes an array of pointers to dynamic event contexts + * @events: An array of pointers to dynamic event contexts. The last element of the array + * must be a NULL pointer. + */ +void tracefs_dynevent_list_free(struct tracefs_dynevent **events) +{ + int i; + + if (!events) + return; + + for (i = 0; events[i]; i++) + tracefs_dynevent_free(events[i]); + + free(events); +} + +/** + * tracefs_dynevent_get_all - return an array of pointers to dynamic events of given types + * @types: Dynamic event type, or bitmask of dynamic event types. If 0 is passed, all types + * are considered. + * @system: Get events from that system only. If @system is NULL, events from all systems + * are returned. + * + * Returns an array of pointers to dynamic events of given types that exist in the system. + * The array must be freed with tracefs_dynevent_list_free(). If there are no events a NULL + * pointer is returned. + */ +struct tracefs_dynevent ** +tracefs_dynevent_get_all(unsigned int types, const char *system) +{ + struct tracefs_dynevent **events, **tmp, **all_events = NULL; + int count, all = 0; + int i; + + for (i = 1; i < TRACEFS_DYNEVENT_MAX; i <<= 1) { + if (types) { + if (i > types) + break; + if (!(types & i)) + continue; + } + count = get_all_dynevents(i, system, &events); + if (count > 0) { + tmp = realloc(all_events, (all + count + 1) * sizeof(*tmp)); + if (!tmp) + goto error; + all_events = tmp; + memcpy(all_events + all, events, count * sizeof(*events)); + all += count; + /* Add a NULL pointer at the end */ + all_events[all] = NULL; + free(events); + } + } + + return all_events; + +error: + if (all_events) { + for (i = 0; i < all; i++) + free(all_events[i]); + free(all_events); + } + return NULL; +} + +/** + * tracefs_dynevent_get - return a single dynamic event if it exists + * @type; Dynamic event type + * @system: Get events from that system only. May be NULL. + * @event: Get event of the system type (may not be NULL) + * + * Returns the dynamic event of the given @type and @system for with the @event + * name. If @system is NULL, it will return the first dynamic event that it finds + * that matches the @event name. + * + * The returned event must be freed with tracefs_dynevent_free(). + * NULL is returned if no event match is found, or other error. + */ +struct tracefs_dynevent * +tracefs_dynevent_get(enum tracefs_dynevent_type type, const char *system, + const char *event) +{ + struct tracefs_dynevent **events; + struct tracefs_dynevent *devent = NULL; + int count; + int i; + + if (!event) { + errno = -EINVAL; + return NULL; + } + + count = get_all_dynevents(type, system, &events); + if (count <= 0) + return NULL; + + for (i = 0; i < count; i++) { + if (strcmp(events[i]->event, event) == 0) + break; + } + if (i < count) { + devent = events[i]; + events[i] = NULL; + } + + tracefs_dynevent_list_free(events); + + return devent; +} + +/** + * tracefs_dynevent_destroy_all - removes all dynamic events of given types from the system + * @types: Dynamic event type, or bitmask of dynamic event types. If 0 is passed, all types + * are considered. + * @force: Will attempt to disable all events before removing them. + * + * Will remove all dynamic events of the given types from the system. If there are any enabled + * events, and @force is not set, then the removal of these will fail. If @force is set, then + * it will attempt to disable all the events in all instances before removing them. + * + * Returns zero if all requested events are removed successfully, or -1 if some of them are not + * removed. + */ +int tracefs_dynevent_destroy_all(unsigned int types, bool force) +{ + struct tracefs_dynevent **all; + int ret = 0; + int i; + + all = tracefs_dynevent_get_all(types, NULL); + if (!all) + return 0; + + for (i = 0; all[i]; i++) { + if (tracefs_dynevent_destroy(all[i], force)) + ret = -1; + } + + tracefs_dynevent_list_free(all); + + return ret; +} + +/** + * dynevent_get_count - Count dynamic events of given types and system + * @types: Dynamic event type, or bitmask of dynamic event types. If 0 is passed, all types + * are considered. + * @system: Count events from that system only. If @system is NULL, events from all systems + * are counted. + * + * Return the count of requested dynamic events + */ +__hidden int dynevent_get_count(unsigned int types, const char *system) +{ + int count, all = 0; + int i; + + for (i = 1; i < TRACEFS_DYNEVENT_MAX; i <<= 1) { + if (types) { + if (i > types) + break; + if (!(types & i)) + continue; + } + count = get_all_dynevents(i, system, NULL); + if (count > 0) + all += count; + } + + return all; +} + +static enum tracefs_dynevent_type +dynevent_info(struct tracefs_dynevent *dynevent, char **system, + char **event, char **prefix, char **addr, char **format) +{ + char **lv[] = { system, event, prefix, addr, format }; + char **rv[] = { &dynevent->system, &dynevent->event, &dynevent->prefix, + &dynevent->address, &dynevent->format }; + int i; + + for (i = 0; i < ARRAY_SIZE(lv); i++) { + if (lv[i]) { + if (*rv[i]) { + *lv[i] = strdup(*rv[i]); + if (!*lv[i]) + goto error; + } else { + *lv[i] = NULL; + } + } + } + + return dynevent->type; + +error: + for (i--; i >= 0; i--) { + if (lv[i]) + free(*lv[i]); + } + + return TRACEFS_DYNEVENT_UNKNOWN; +} + +/** + * tracefs_dynevent_info - return details of a dynamic event + * @dynevent: A dynamic event context, describing given dynamic event. + * @group: return, group in which the dynamic event is configured + * @event: return, name of the dynamic event + * @prefix: return, prefix string of the dynamic event + * @addr: return, the function and offset (or address) of the dynamic event + * @format: return, the format string of the dynamic event + * + * Returns the type of the dynamic event, or TRACEFS_DYNEVENT_UNKNOWN in case of an error. + * Any of the @group, @event, @prefix, @addr and @format parameters are optional. + * If a valid pointer is passed, in case of success - a string is allocated and returned. + * These strings must be freed with free(). + */ +enum tracefs_dynevent_type +tracefs_dynevent_info(struct tracefs_dynevent *dynevent, char **system, + char **event, char **prefix, char **addr, char **format) +{ + if (!dynevent) + return TRACEFS_DYNEVENT_UNKNOWN; + + return dynevent_info(dynevent, system, event, prefix, addr, format); +} + +/** + * tracefs_dynevent_get_event - return tep event representing the given dynamic event + * @tep: a handle to the trace event parser context that holds the events + * @dynevent: a dynamic event context, describing given dynamic event. + * + * Returns a pointer to a tep event describing the given dynamic event. The pointer + * is managed by the @tep handle and must not be freed. In case of an error, or in case + * the requested dynamic event is missing in the @tep handler - NULL is returned. + */ +struct tep_event * +tracefs_dynevent_get_event(struct tep_handle *tep, struct tracefs_dynevent *dynevent) +{ + if (!tep || !dynevent || !dynevent->event) + return NULL; + + return get_tep_event(tep, dynevent->system, dynevent->event); +} diff --git a/src/tracefs-eprobes.c b/src/tracefs-eprobes.c new file mode 100644 index 0000000..cc25f8e --- /dev/null +++ b/src/tracefs-eprobes.c @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: LGPL-2.1 +/* + * Copyright (C) 2021, VMware, Tzvetomir Stoyanov + * + */ +#include +#include + +#include "tracefs.h" +#include "tracefs-local.h" + +#define EPROBE_DEFAULT_GROUP "eprobes" + +/** + * tracefs_eprobe_alloc - Allocate new eprobe + * @system: The system name (NULL for the default eprobes) + * @event: The name of the event to create + * @target_system: The system of the target event + * @target_event: The name of the target event + * @fetchargs: String with arguments, that will be fetched from @target_event + * + * Allocate an eprobe context that will be in the @system group (or eprobes if + * @system is NULL). Have the name of @event. The new eprobe will be attached to + * given @target_event which is in the given @target_system. The arguments + * described in @fetchargs will fetched from the @target_event. + * + * The eprobe is not created in the system. + * + * Return a pointer to a eprobe context on success, or NULL on error. + * The returned pointer must be freed with tracefs_dynevent_free() + * + */ +struct tracefs_dynevent * +tracefs_eprobe_alloc(const char *system, const char *event, + const char *target_system, const char *target_event, const char *fetchargs) +{ + struct tracefs_dynevent *kp; + char *target; + + if (!event || !target_system || !target_event) { + errno = EINVAL; + return NULL; + } + + if (!system) + system = EPROBE_DEFAULT_GROUP; + + if (asprintf(&target, "%s.%s", target_system, target_event) < 0) + return NULL; + + kp = dynevent_alloc(TRACEFS_DYNEVENT_EPROBE, system, event, target, fetchargs); + free(target); + + return kp; +} + diff --git a/src/tracefs-events.c b/src/tracefs-events.c new file mode 100644 index 0000000..c2adf41 --- /dev/null +++ b/src/tracefs-events.c @@ -0,0 +1,1515 @@ +// SPDX-License-Identifier: LGPL-2.1 +/* + * Copyright (C) 2008, 2009, 2010 Red Hat Inc, Steven Rostedt + * + * Updates: + * Copyright (C) 2019, VMware, Tzvetomir Stoyanov + * + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "tracefs.h" +#include "tracefs-local.h" + +static struct follow_event *root_followers; +static int nr_root_followers; + +static struct follow_event *root_missed_followers; +static int nr_root_missed_followers; + +struct cpu_iterate { + struct tracefs_cpu *tcpu; + struct tep_record record; + struct tep_event *event; + struct kbuffer *kbuf; + void *page; + int psize; + int cpu; +}; + +static int read_kbuf_record(struct cpu_iterate *cpu) +{ + unsigned long long ts; + void *ptr; + + if (!cpu || !cpu->kbuf) + return -1; + ptr = kbuffer_read_event(cpu->kbuf, &ts); + if (!ptr) + return -1; + + memset(&cpu->record, 0, sizeof(cpu->record)); + cpu->record.ts = ts; + cpu->record.size = kbuffer_event_size(cpu->kbuf); + cpu->record.record_size = kbuffer_curr_size(cpu->kbuf); + cpu->record.missed_events = kbuffer_missed_events(cpu->kbuf); + cpu->record.cpu = cpu->cpu; + cpu->record.data = ptr; + cpu->record.ref_count = 1; + + kbuffer_next_event(cpu->kbuf, NULL); + + return 0; +} + +int read_next_page(struct tep_handle *tep, struct cpu_iterate *cpu) +{ + enum kbuffer_long_size long_size; + enum kbuffer_endian endian; + int r; + + if (!cpu->tcpu) + return -1; + + r = tracefs_cpu_buffered_read(cpu->tcpu, cpu->page, true); + /* + * tracefs_cpu_buffered_read() only reads in full subbuffer size, + * but this wants partial buffers as well. If the function returns + * empty (-1 for EAGAIN), try tracefs_cpu_read() next, as that can + * read partially filled buffers too, but isn't as efficient. + */ + if (r <= 0) + r = tracefs_cpu_read(cpu->tcpu, cpu->page, true); + if (r <= 0) + return -1; + + if (!cpu->kbuf) { + if (tep_is_file_bigendian(tep)) + endian = KBUFFER_ENDIAN_BIG; + else + endian = KBUFFER_ENDIAN_LITTLE; + + if (tep_get_header_page_size(tep) == 8) + long_size = KBUFFER_LSIZE_8; + else + long_size = KBUFFER_LSIZE_4; + + cpu->kbuf = kbuffer_alloc(long_size, endian); + if (!cpu->kbuf) + return -1; + } + + kbuffer_load_subbuffer(cpu->kbuf, cpu->page); + if (kbuffer_subbuffer_size(cpu->kbuf) > r) { + tracefs_warning("%s: page_size > %d", __func__, r); + return -1; + } + + return 0; +} + +int read_next_record(struct tep_handle *tep, struct cpu_iterate *cpu) +{ + int id; + + do { + while (!read_kbuf_record(cpu)) { + id = tep_data_type(tep, &(cpu->record)); + cpu->event = tep_find_event(tep, id); + if (cpu->event) + return 0; + } + } while (!read_next_page(tep, cpu)); + + return -1; +} + +/** + * tracefs_follow_missed_events - Add callback for missed events for iterators + * @instance: The instance to follow + * @callback: The function to call when missed events is detected + * @callback_data: The data to pass to @callback + * + * This attaches a callback to an @instance or the root instance if @instance + * is NULL, where if tracefs_iterate_raw_events() is called, that if missed + * events are detected, it will call @callback, with the following parameters: + * @event: The event pointer of the record with the missing events + * @record; The event instance of @event. + * @cpu: The cpu that the event happened on. + * @callback_data: The same as @callback_data passed to the function. + * + * If the count of missing events is available, @record->missed_events + * will have a positive number holding the number of missed events since + * the last event on the same CPU, or just -1 if that number is unknown + * but missed events did happen. + * + * Returns 0 on success and -1 on error. + */ +int tracefs_follow_missed_events(struct tracefs_instance *instance, + int (*callback)(struct tep_event *, + struct tep_record *, + int, void *), + void *callback_data) +{ + struct follow_event **followers; + struct follow_event *follower; + struct follow_event follow; + int *nr_followers; + + follow.event = NULL; + follow.callback = callback; + follow.callback_data = callback_data; + + if (instance) { + followers = &instance->missed_followers; + nr_followers = &instance->nr_missed_followers; + } else { + followers = &root_missed_followers; + nr_followers = &nr_root_missed_followers; + } + follower = realloc(*followers, sizeof(*follower) * + ((*nr_followers) + 1)); + if (!follower) + return -1; + + *followers = follower; + follower[(*nr_followers)++] = follow; + + return 0; +} + +static int call_missed_events(struct tracefs_instance *instance, + struct tep_event *event, struct tep_record *record, int cpu) +{ + struct follow_event *followers; + int nr_followers; + int ret = 0; + int i; + + if (instance) { + followers = instance->missed_followers; + nr_followers = instance->nr_missed_followers; + } else { + followers = root_missed_followers; + nr_followers = nr_root_missed_followers; + } + + if (!followers) + return 0; + + for (i = 0; i < nr_followers; i++) { + ret |= followers[i].callback(event, record, + cpu, followers[i].callback_data); + } + + return ret; +} + +static int call_followers(struct tracefs_instance *instance, + struct tep_event *event, struct tep_record *record, int cpu) +{ + struct follow_event *followers; + int nr_followers; + int ret = 0; + int i; + + if (record->missed_events) + ret = call_missed_events(instance, event, record, cpu); + if (ret) + return ret; + + if (instance) { + followers = instance->followers; + nr_followers = instance->nr_followers; + } else { + followers = root_followers; + nr_followers = nr_root_followers; + } + + if (!followers) + return 0; + + for (i = 0; i < nr_followers; i++) { + if (followers[i].event == event) + ret |= followers[i].callback(event, record, + cpu, followers[i].callback_data); + } + + return ret; +} + +static int read_cpu_pages(struct tep_handle *tep, struct tracefs_instance *instance, + struct cpu_iterate *cpus, int count, + int (*callback)(struct tep_event *, + struct tep_record *, + int, void *), + void *callback_context, + bool *keep_going) +{ + bool has_data = false; + int ret; + int i, j; + + for (i = 0; i < count; i++) { + ret = read_next_record(tep, cpus + i); + if (!ret) + has_data = true; + } + + while (has_data && *(volatile bool *)keep_going) { + j = count; + for (i = 0; i < count; i++) { + if (!cpus[i].event) + continue; + if (j == count || cpus[j].record.ts > cpus[i].record.ts) + j = i; + } + if (j < count) { + if (call_followers(instance, cpus[j].event, &cpus[j].record, cpus[j].cpu)) + break; + if (callback && + callback(cpus[j].event, &cpus[j].record, cpus[j].cpu, callback_context)) + break; + cpus[j].event = NULL; + read_next_record(tep, cpus + j); + } else { + has_data = false; + } + } + + return 0; +} + +static int open_cpu_files(struct tracefs_instance *instance, cpu_set_t *cpus, + int cpu_size, struct cpu_iterate **all_cpus, int *count) +{ + struct tracefs_cpu *tcpu; + struct cpu_iterate *tmp; + int nr_cpus; + int cpu; + int i = 0; + + *all_cpus = NULL; + + nr_cpus = sysconf(_SC_NPROCESSORS_CONF); + for (cpu = 0; cpu < nr_cpus; cpu++) { + if (cpus && !CPU_ISSET_S(cpu, cpu_size, cpus)) + continue; + tcpu = tracefs_cpu_open(instance, cpu, true); + tmp = realloc(*all_cpus, (i + 1) * sizeof(*tmp)); + if (!tmp) { + i--; + goto error; + } + + *all_cpus = tmp; + + memset(tmp + i, 0, sizeof(*tmp)); + + if (!tcpu) + goto error; + + tmp[i].tcpu = tcpu; + tmp[i].cpu = cpu; + tmp[i].psize = tracefs_cpu_read_size(tcpu); + tmp[i].page = malloc(tmp[i].psize); + + if (!tmp[i++].page) + goto error; + } + *count = i; + return 0; + error: + tmp = *all_cpus; + for (; i >= 0; i--) { + tracefs_cpu_close(tmp[i].tcpu); + free(tmp[i].page); + } + free(tmp); + *all_cpus = NULL; + return -1; +} + +/** + * tracefs_follow_event - Add callback for specific events for iterators + * @tep: a handle to the trace event parser context + * @instance: The instance to follow + * @system: The system of the event to track + * @event_name: The name of the event to track + * @callback: The function to call when the event is hit in an iterator + * @callback_data: The data to pass to @callback + * + * This attaches a callback to an @instance or the root instance if @instance + * is NULL, where if tracefs_iterate_raw_events() is called, that if the specified + * event is hit, it will call @callback, with the following parameters: + * @event: The event pointer that was found by @system and @event_name. + * @record; The event instance of @event. + * @cpu: The cpu that the event happened on. + * @callback_data: The same as @callback_data passed to the function. + * + * Returns 0 on success and -1 on error. + */ +int tracefs_follow_event(struct tep_handle *tep, struct tracefs_instance *instance, + const char *system, const char *event_name, + int (*callback)(struct tep_event *, + struct tep_record *, + int, void *), + void *callback_data) +{ + struct follow_event **followers; + struct follow_event *follower; + struct follow_event follow; + int *nr_followers; + + if (!tep) { + errno = EINVAL; + return -1; + } + + follow.event = tep_find_event_by_name(tep, system, event_name); + if (!follow.event) { + errno = ENOENT; + return -1; + } + + follow.callback = callback; + follow.callback_data = callback_data; + + if (instance) { + followers = &instance->followers; + nr_followers = &instance->nr_followers; + } else { + followers = &root_followers; + nr_followers = &nr_root_followers; + } + follower = realloc(*followers, sizeof(*follower) * + ((*nr_followers) + 1)); + if (!follower) + return -1; + + *followers = follower; + follower[(*nr_followers)++] = follow; + + return 0; +} + +static bool top_iterate_keep_going; + +/* + * tracefs_iterate_raw_events - Iterate through events in trace_pipe_raw, + * per CPU trace buffers + * @tep: a handle to the trace event parser context + * @instance: ftrace instance, can be NULL for the top instance + * @cpus: Iterate only through the buffers of CPUs, set in the mask. + * If NULL, iterate through all CPUs. + * @cpu_size: size of @cpus set + * @callback: A user function, called for each record from the file + * @callback_context: A custom context, passed to the user callback function + * + * If the @callback returns non-zero, the iteration stops - in that case all + * records from the current page will be lost from future reads + * The events are iterated in sorted order, oldest first. + * + * Returns -1 in case of an error, or 0 otherwise + */ +int tracefs_iterate_raw_events(struct tep_handle *tep, + struct tracefs_instance *instance, + cpu_set_t *cpus, int cpu_size, + int (*callback)(struct tep_event *, + struct tep_record *, + int, void *), + void *callback_context) +{ + bool *keep_going = instance ? &instance->iterate_keep_going : + &top_iterate_keep_going; + struct follow_event *followers; + struct cpu_iterate *all_cpus; + int count = 0; + int ret; + int i; + + (*(volatile bool *)keep_going) = true; + + if (!tep) + return -1; + + if (instance) + followers = instance->followers; + else + followers = root_followers; + if (!callback && !followers) + return -1; + + ret = open_cpu_files(instance, cpus, cpu_size, &all_cpus, &count); + if (ret < 0) + goto out; + ret = read_cpu_pages(tep, instance, all_cpus, count, + callback, callback_context, + keep_going); + +out: + if (all_cpus) { + for (i = 0; i < count; i++) { + kbuffer_free(all_cpus[i].kbuf); + tracefs_cpu_close(all_cpus[i].tcpu); + free(all_cpus[i].page); + } + free(all_cpus); + } + + return ret; +} + +/** + * tracefs_iterate_stop - stop the iteration over the raw events. + * @instance: ftrace instance, can be NULL for top tracing instance. + */ +void tracefs_iterate_stop(struct tracefs_instance *instance) +{ + if (instance) + instance->iterate_keep_going = false; + else + top_iterate_keep_going = false; +} + +static int add_list_string(char ***list, const char *name) +{ + char **tmp; + + tmp = tracefs_list_add(*list, name); + if (!tmp) { + tracefs_list_free(*list); + *list = NULL; + return -1; + } + + *list = tmp; + return 0; +} + +__hidden char *trace_append_file(const char *dir, const char *name) +{ + char *file; + int ret; + + ret = asprintf(&file, "%s/%s", dir, name); + + return ret < 0 ? NULL : file; +} + +static int event_file(char **path, const char *system, + const char *event, const char *file) +{ + if (!system || !event || !file) + return -1; + + return asprintf(path, "events/%s/%s/%s", + system, event, file); +} + +/** + * tracefs_event_get_file - return a file in an event directory + * @instance: The instance the event is in (NULL for top level) + * @system: The system name that the event file is in + * @event: The event name of the event + * @file: The name of the file in the event directory. + * + * Returns a path to a file in the event director. + * or NULL on error. The path returned must be freed with + * tracefs_put_tracing_file(). + */ +char *tracefs_event_get_file(struct tracefs_instance *instance, + const char *system, const char *event, + const char *file) +{ + char *instance_path; + char *path; + int ret; + + ret = event_file(&path, system, event, file); + if (ret < 0) + return NULL; + + instance_path = tracefs_instance_get_file(instance, path); + free(path); + + return instance_path; +} + +/** + * tracefs_event_file_read - read the content from an event file + * @instance: The instance the event is in (NULL for top level) + * @system: The system name that the event file is in + * @event: The event name of the event + * @file: The name of the file in the event directory. + * @psize: the size of the content read. + * + * Reads the content of the event file that is passed via the + * arguments and returns the content. + * + * Return a string containing the content of the file or NULL + * on error. The string returned must be freed with free(). + */ +char *tracefs_event_file_read(struct tracefs_instance *instance, + const char *system, const char *event, + const char *file, int *psize) +{ + char *content; + char *path; + int ret; + + ret = event_file(&path, system, event, file); + if (ret < 0) + return NULL; + + content = tracefs_instance_file_read(instance, path, psize); + free(path); + return content; +} + +/** + * tracefs_event_file_write - write to an event file + * @instance: The instance the event is in (NULL for top level) + * @system: The system name that the event file is in + * @event: The event name of the event + * @file: The name of the file in the event directory. + * @str: The string to write into the file + * + * Writes the content of @str to a file in the instance directory. + * The content of the file will be overwritten by @str. + * + * Return 0 on success, and -1 on error. + */ +int tracefs_event_file_write(struct tracefs_instance *instance, + const char *system, const char *event, + const char *file, const char *str) +{ + char *path; + int ret; + + ret = event_file(&path, system, event, file); + if (ret < 0) + return -1; + + ret = tracefs_instance_file_write(instance, path, str); + free(path); + return ret; +} + +/** + * tracefs_event_file_append - write to an event file + * @instance: The instance the event is in (NULL for top level) + * @system: The system name that the event file is in + * @event: The event name of the event + * @file: The name of the file in the event directory. + * @str: The string to write into the file + * + * Writes the content of @str to a file in the instance directory. + * The content of @str will be appended to the content of the file. + * The current content should not be lost. + * + * Return 0 on success, and -1 on error. + */ +int tracefs_event_file_append(struct tracefs_instance *instance, + const char *system, const char *event, + const char *file, const char *str) +{ + char *path; + int ret; + + ret = event_file(&path, system, event, file); + if (ret < 0) + return -1; + + ret = tracefs_instance_file_append(instance, path, str); + free(path); + return ret; +} + +/** + * tracefs_event_file_clear - clear an event file + * @instance: The instance the event is in (NULL for top level) + * @system: The system name that the event file is in + * @event: The event name of the event + * @file: The name of the file in the event directory. + * + * Clears the content of the event file. That is, it is opened + * with O_TRUNC and then closed. + * + * Return 0 on success, and -1 on error. + */ +int tracefs_event_file_clear(struct tracefs_instance *instance, + const char *system, const char *event, + const char *file) +{ + char *path; + int ret; + + ret = event_file(&path, system, event, file); + if (ret < 0) + return -1; + + ret = tracefs_instance_file_clear(instance, path); + free(path); + return ret; +} + +/** + * tracefs_event_file_exits - test if a file exists + * @instance: The instance the event is in (NULL for top level) + * @system: The system name that the event file is in + * @event: The event name of the event + * @file: The name of the file in the event directory. + * + * Return true if the file exists, false if it odes not or + * an error occurred. + */ +bool tracefs_event_file_exists(struct tracefs_instance *instance, + const char *system, const char *event, + const char *file) +{ + char *path; + bool ret; + + if (event_file(&path, system, event, file) < 0) + return false; + + ret = tracefs_file_exists(instance, path); + free(path); + return ret; +} + +/** + * tracefs_event_systems - return list of systems for tracing + * @tracing_dir: directory holding the "events" directory + * if NULL, top tracing directory is used + * + * Returns an allocated list of system names. Both the names and + * the list must be freed with tracefs_list_free() + * The list returned ends with a "NULL" pointer + */ +char **tracefs_event_systems(const char *tracing_dir) +{ + struct dirent *dent; + char **systems = NULL; + char *events_dir; + struct stat st; + DIR *dir; + int ret; + + if (!tracing_dir) + tracing_dir = tracefs_tracing_dir(); + + if (!tracing_dir) + return NULL; + + events_dir = trace_append_file(tracing_dir, "events"); + if (!events_dir) + return NULL; + + /* + * Search all the directories in the events directory, + * and collect the ones that have the "enable" file. + */ + ret = stat(events_dir, &st); + if (ret < 0 || !S_ISDIR(st.st_mode)) + goto out_free; + + dir = opendir(events_dir); + if (!dir) + goto out_free; + + while ((dent = readdir(dir))) { + const char *name = dent->d_name; + char *enable; + char *sys; + + if (strcmp(name, ".") == 0 || + strcmp(name, "..") == 0) + continue; + + sys = trace_append_file(events_dir, name); + ret = stat(sys, &st); + if (ret < 0 || !S_ISDIR(st.st_mode)) { + free(sys); + continue; + } + + enable = trace_append_file(sys, "enable"); + + ret = stat(enable, &st); + if (ret >= 0) { + if (add_list_string(&systems, name) < 0) + goto out_free; + } + free(enable); + free(sys); + } + + closedir(dir); + + out_free: + free(events_dir); + return systems; +} + +/** + * tracefs_system_events - return list of events for system + * @tracing_dir: directory holding the "events" directory + * @system: the system to return the events for + * + * Returns an allocated list of event names. Both the names and + * the list must be freed with tracefs_list_free() + * The list returned ends with a "NULL" pointer + */ +char **tracefs_system_events(const char *tracing_dir, const char *system) +{ + struct dirent *dent; + char **events = NULL; + char *system_dir = NULL; + struct stat st; + DIR *dir; + int ret; + + if (!tracing_dir) + tracing_dir = tracefs_tracing_dir(); + + if (!tracing_dir || !system) + return NULL; + + asprintf(&system_dir, "%s/events/%s", tracing_dir, system); + if (!system_dir) + return NULL; + + ret = stat(system_dir, &st); + if (ret < 0 || !S_ISDIR(st.st_mode)) + goto out_free; + + dir = opendir(system_dir); + if (!dir) + goto out_free; + + while ((dent = readdir(dir))) { + const char *name = dent->d_name; + char *event; + + if (strcmp(name, ".") == 0 || + strcmp(name, "..") == 0) + continue; + + event = trace_append_file(system_dir, name); + ret = stat(event, &st); + if (ret < 0 || !S_ISDIR(st.st_mode)) { + free(event); + continue; + } + + if (add_list_string(&events, name) < 0) + goto out_free; + + free(event); + } + + closedir(dir); + + out_free: + free(system_dir); + + return events; +} + +/** + * tracefs_tracers - returns an array of available tracers + * @tracing_dir: The directory that contains the tracing directory + * + * Returns an allocate list of plugins. The array ends with NULL + * Both the plugin names and array must be freed with tracefs_list_free() + */ +char **tracefs_tracers(const char *tracing_dir) +{ + char *available_tracers; + struct stat st; + char **plugins = NULL; + char *buf; + char *str, *saveptr; + char *plugin; + int slen; + int len; + int ret; + + if (!tracing_dir) + tracing_dir = tracefs_tracing_dir(); + + if (!tracing_dir) + return NULL; + + available_tracers = trace_append_file(tracing_dir, "available_tracers"); + if (!available_tracers) + return NULL; + + ret = stat(available_tracers, &st); + if (ret < 0) + goto out_free; + + len = str_read_file(available_tracers, &buf, true); + if (len <= 0) + goto out_free; + + for (str = buf; ; str = NULL) { + plugin = strtok_r(str, " ", &saveptr); + if (!plugin) + break; + slen = strlen(plugin); + if (!slen) + continue; + + /* chop off any newlines */ + if (plugin[slen - 1] == '\n') + plugin[slen - 1] = '\0'; + + /* Skip the non tracers */ + if (strcmp(plugin, "nop") == 0 || + strcmp(plugin, "none") == 0) + continue; + + if (add_list_string(&plugins, plugin) < 0) + break; + } + free(buf); + + out_free: + free(available_tracers); + + return plugins; +} + +static int load_events(struct tep_handle *tep, + const char *tracing_dir, const char *system, bool check) +{ + int ret = 0, failure = 0; + char **events = NULL; + struct stat st; + int len = 0; + int i; + + if (!tracing_dir) + tracing_dir = tracefs_tracing_dir(); + + events = tracefs_system_events(tracing_dir, system); + if (!events) + return -ENOENT; + + for (i = 0; events[i]; i++) { + char *format; + char *buf; + + ret = asprintf(&format, "%s/events/%s/%s/format", + tracing_dir, system, events[i]); + if (ret < 0) { + failure = -ENOMEM; + break; + } + + ret = stat(format, &st); + if (ret < 0) + goto next_event; + + /* check if event is already added, to avoid duplicates */ + if (check && tep_find_event_by_name(tep, system, events[i])) + goto next_event; + + len = str_read_file(format, &buf, true); + if (len <= 0) + goto next_event; + + ret = tep_parse_event(tep, buf, len, system); + free(buf); +next_event: + free(format); + if (ret) + failure = ret; + } + + tracefs_list_free(events); + return failure; +} + +__hidden int trace_rescan_events(struct tep_handle *tep, + const char *tracing_dir, const char *system) +{ + /* ToDo: add here logic for deleting removed events from tep handle */ + return load_events(tep, tracing_dir, system, true); +} + +__hidden int trace_load_events(struct tep_handle *tep, + const char *tracing_dir, const char *system) +{ + return load_events(tep, tracing_dir, system, false); +} + +__hidden struct tep_event *get_tep_event(struct tep_handle *tep, + const char *system, const char *name) +{ + struct tep_event *event; + + /* Check if event exists in the system */ + if (!tracefs_event_file_exists(NULL, system, name, "format")) + return NULL; + + /* If the event is already loaded in the tep, return it */ + event = tep_find_event_by_name(tep, system, name); + if (event) + return event; + + /* Try to load any new events from the given system */ + if (trace_rescan_events(tep, NULL, system)) + return NULL; + + return tep_find_event_by_name(tep, system, name); +} + +static int read_header(struct tep_handle *tep, const char *tracing_dir) +{ + struct stat st; + char *header; + char *buf; + int len; + int ret = -1; + + header = trace_append_file(tracing_dir, "events/header_page"); + + ret = stat(header, &st); + if (ret < 0) + goto out; + + len = str_read_file(header, &buf, true); + if (len <= 0) + goto out; + + tep_parse_header_page(tep, buf, len, sizeof(long)); + + free(buf); + + ret = 0; + out: + free(header); + return ret; +} + +static bool contains(const char *name, const char * const *names) +{ + if (!names) + return false; + for (; *names; names++) + if (strcmp(name, *names) == 0) + return true; + return false; +} + +static void load_kallsyms(struct tep_handle *tep) +{ + char *buf; + + if (str_read_file("/proc/kallsyms", &buf, false) <= 0) + return; + + tep_parse_kallsyms(tep, buf); + free(buf); +} + +static int load_saved_cmdlines(const char *tracing_dir, + struct tep_handle *tep, bool warn) +{ + char *path; + char *buf; + int ret; + + path = trace_append_file(tracing_dir, "saved_cmdlines"); + if (!path) + return -1; + + ret = str_read_file(path, &buf, false); + free(path); + if (ret <= 0) + return -1; + + ret = tep_parse_saved_cmdlines(tep, buf); + free(buf); + + return ret; +} + +static void load_printk_formats(const char *tracing_dir, + struct tep_handle *tep) +{ + char *path; + char *buf; + int ret; + + path = trace_append_file(tracing_dir, "printk_formats"); + if (!path) + return; + + ret = str_read_file(path, &buf, false); + free(path); + if (ret <= 0) + return; + + tep_parse_printk_formats(tep, buf); + free(buf); +} + +/* + * Do a best effort attempt to load kallsyms, saved_cmdlines and + * printk_formats. If they can not be loaded, then this will not + * do the mappings. But this does not fail the loading of events. + */ +static void load_mappings(const char *tracing_dir, + struct tep_handle *tep) +{ + load_kallsyms(tep); + + /* If there's no tracing_dir no reason to go further */ + if (!tracing_dir) + tracing_dir = tracefs_tracing_dir(); + + if (!tracing_dir) + return; + + load_saved_cmdlines(tracing_dir, tep, false); + load_printk_formats(tracing_dir, tep); +} + +int tracefs_load_cmdlines(const char *tracing_dir, struct tep_handle *tep) +{ + + if (!tracing_dir) + tracing_dir = tracefs_tracing_dir(); + + if (!tracing_dir) + return -1; + + return load_saved_cmdlines(tracing_dir, tep, true); +} + +static int fill_local_events_system(const char *tracing_dir, + struct tep_handle *tep, + const char * const *sys_names, + int *parsing_failures) +{ + char **systems = NULL; + int ret; + int i; + + if (!tracing_dir) + tracing_dir = tracefs_tracing_dir(); + if (!tracing_dir) + return -1; + + systems = tracefs_event_systems(tracing_dir); + if (!systems) + return -1; + + ret = read_header(tep, tracing_dir); + if (ret < 0) { + ret = -1; + goto out; + } + + if (parsing_failures) + *parsing_failures = 0; + + for (i = 0; systems[i]; i++) { + if (sys_names && !contains(systems[i], sys_names)) + continue; + ret = trace_load_events(tep, tracing_dir, systems[i]); + if (ret && parsing_failures) + (*parsing_failures)++; + } + + /* Include ftrace, as it is excluded for not having "enable" file */ + if (!sys_names || contains("ftrace", sys_names)) + trace_load_events(tep, tracing_dir, "ftrace"); + + load_mappings(tracing_dir, tep); + + /* always succeed because parsing failures are not critical */ + ret = 0; +out: + tracefs_list_free(systems); + return ret; +} + +static void set_tep_cpus(const char *tracing_dir, struct tep_handle *tep) +{ + struct stat st; + char path[PATH_MAX]; + int cpus = sysconf(_SC_NPROCESSORS_CONF); + int max_cpu = 0; + int ret; + int i; + + if (!tracing_dir) + tracing_dir = tracefs_tracing_dir(); + + /* + * Paranoid: in case sysconf() above does not work. + * And we also only care about the number of tracing + * buffers that exist. If cpus is 32, but the top half + * is offline, there may only be 16 tracing buffers. + * That's what we want to know. + */ + for (i = 0; !cpus || i < cpus; i++) { + snprintf(path, PATH_MAX, "%s/per_cpu/cpu%d", tracing_dir, i); + ret = stat(path, &st); + if (!ret && S_ISDIR(st.st_mode)) + max_cpu = i + 1; + else if (i >= cpus) + break; + } + + if (!max_cpu) + max_cpu = cpus; + + tep_set_cpus(tep, max_cpu); +} + +/** + * tracefs_local_events_system - create a tep from the events of the specified subsystem. + * + * @tracing_dir: The directory that contains the events. + * @sys_name: Array of system names, to load the events from. + * The last element from the array must be NULL + * + * Returns a tep structure that contains the tep local to + * the system. + */ +struct tep_handle *tracefs_local_events_system(const char *tracing_dir, + const char * const *sys_names) +{ + struct tep_handle *tep = NULL; + + tep = tep_alloc(); + if (!tep) + return NULL; + + if (fill_local_events_system(tracing_dir, tep, sys_names, NULL)) { + tep_free(tep); + tep = NULL; + } + + set_tep_cpus(tracing_dir, tep); + + /* Set the long size for this tep handle */ + tep_set_long_size(tep, tep_get_header_page_size(tep)); + + return tep; +} + +/** + * tracefs_local_events - create a tep from the events on system + * @tracing_dir: The directory that contains the events. + * + * Returns a tep structure that contains the teps local to + * the system. + */ +struct tep_handle *tracefs_local_events(const char *tracing_dir) +{ + return tracefs_local_events_system(tracing_dir, NULL); +} + +/** + * tracefs_fill_local_events - Fill a tep with the events on system + * @tracing_dir: The directory that contains the events. + * @tep: Allocated tep handler which will be filled + * @parsing_failures: return number of failures while parsing the event files + * + * Returns whether the operation succeeded + */ +int tracefs_fill_local_events(const char *tracing_dir, + struct tep_handle *tep, int *parsing_failures) +{ + return fill_local_events_system(tracing_dir, tep, + NULL, parsing_failures); +} + +static bool match(const char *str, regex_t *re) +{ + return regexec(re, str, 0, NULL, 0) == 0; +} + +enum event_state { + STATE_INIT, + STATE_ENABLED, + STATE_DISABLED, + STATE_MIXED, + STATE_ERROR, +}; + +static int read_event_state(struct tracefs_instance *instance, const char *file, + enum event_state *state) +{ + char *val; + int ret = 0; + + if (*state == STATE_ERROR) + return -1; + + val = tracefs_instance_file_read(instance, file, NULL); + if (!val) + return -1; + + switch (val[0]) { + case '0': + switch (*state) { + case STATE_INIT: + *state = STATE_DISABLED; + break; + case STATE_ENABLED: + *state = STATE_MIXED; + break; + default: + break; + } + break; + case '1': + switch (*state) { + case STATE_INIT: + *state = STATE_ENABLED; + break; + case STATE_DISABLED: + *state = STATE_MIXED; + break; + default: + break; + } + break; + case 'X': + *state = STATE_MIXED; + break; + default: + *state = TRACEFS_ERROR; + ret = -1; + break; + } + free(val); + + return ret; +} + +static int enable_disable_event(struct tracefs_instance *instance, + const char *system, const char *event, + bool enable, enum event_state *state) +{ + const char *str = enable ? "1" : "0"; + char *system_event; + int ret; + + ret = asprintf(&system_event, "events/%s/%s/enable", system, event); + if (ret < 0) + return ret; + + if (state) + ret = read_event_state(instance, system_event, state); + else + ret = tracefs_instance_file_write(instance, system_event, str); + free(system_event); + + return ret; +} + +static int enable_disable_system(struct tracefs_instance *instance, + const char *system, bool enable, + enum event_state *state) +{ + const char *str = enable ? "1" : "0"; + char *system_path; + int ret; + + ret = asprintf(&system_path, "events/%s/enable", system); + if (ret < 0) + return ret; + + if (state) + ret = read_event_state(instance, system_path, state); + else + ret = tracefs_instance_file_write(instance, system_path, str); + free(system_path); + + return ret; +} + +static int enable_disable_all(struct tracefs_instance *instance, + bool enable) +{ + const char *str = enable ? "1" : "0"; + int ret; + + ret = tracefs_instance_file_write(instance, "events/enable", str); + return ret < 0 ? ret : 0; +} + +static int make_regex(regex_t *re, const char *match) +{ + int len = strlen(match); + char str[len + 3]; + char *p = &str[0]; + + if (!len || match[0] != '^') + *(p++) = '^'; + + strcpy(p, match); + p += len; + + if (!len || match[len-1] != '$') + *(p++) = '$'; + + *p = '\0'; + + return regcomp(re, str, REG_ICASE|REG_NOSUB); +} + +static int event_enable_disable(struct tracefs_instance *instance, + const char *system, const char *event, + bool enable, enum event_state *state) +{ + regex_t system_re, event_re; + char **systems; + char **events = NULL; + int ret = -1; + int s, e; + + /* Handle all events first */ + if (!system && !event) + return enable_disable_all(instance, enable); + + systems = tracefs_event_systems(NULL); + if (!systems) + goto out_free; + + if (system) { + ret = make_regex(&system_re, system); + if (ret < 0) + goto out_free; + } + if (event) { + ret = make_regex(&event_re, event); + if (ret < 0) { + if (system) + regfree(&system_re); + goto out_free; + } + } + + ret = -1; + for (s = 0; systems[s]; s++) { + if (system && !match(systems[s], &system_re)) + continue; + + /* Check for the short cut first */ + if (!event) { + ret = enable_disable_system(instance, systems[s], enable, state); + if (ret < 0) + break; + ret = 0; + continue; + } + + events = tracefs_system_events(NULL, systems[s]); + if (!events) + continue; /* Error? */ + + for (e = 0; events[e]; e++) { + if (!match(events[e], &event_re)) + continue; + ret = enable_disable_event(instance, systems[s], + events[e], enable, state); + if (ret < 0) + break; + ret = 0; + } + tracefs_list_free(events); + events = NULL; + } + if (system) + regfree(&system_re); + if (event) + regfree(&event_re); + + out_free: + tracefs_list_free(systems); + tracefs_list_free(events); + return ret; +} + +/** + * tracefs_event_enable - enable specified events + * @instance: ftrace instance, can be NULL for the top instance + * @system: A regex of a system (NULL to match all systems) + * @event: A regex of the event in the system (NULL to match all events) + * + * This will enable events that match the @system and @event. + * If both @system and @event are NULL, then it will enable all events. + * If @system is NULL, it will look at all systems for matching events + * to @event. + * If @event is NULL, then it will enable all events in the systems + * that match @system. + * + * Returns 0 on success, and -1 if it encountered an error, + * or if no events matched. If no events matched, then -1 is set + * but errno will not be. + */ +int tracefs_event_enable(struct tracefs_instance *instance, + const char *system, const char *event) +{ + return event_enable_disable(instance, system, event, true, NULL); +} + +int tracefs_event_disable(struct tracefs_instance *instance, + const char *system, const char *event) +{ + return event_enable_disable(instance, system, event, false, NULL); +} + +/** + * tracefs_event_is_enabled - return if the event is enabled or not + * @instance: ftrace instance, can be NULL for the top instance + * @system: The name of the system to check + * @event: The name of the event to check + * + * Checks is an event or multiple events are enabled. + * + * If @system is NULL, then it will check all the systems where @event is + * a match. + * + * If @event is NULL, then it will check all events where @system is a match. + * + * If both @system and @event are NULL, then it will check all events + * + * Returns TRACEFS_ALL_ENABLED if all matching are enabled. + * Returns TRACEFS_SOME_ENABLED if some are enabled and some are not + * Returns TRACEFS_ALL_DISABLED if none of the events are enabled. + * Returns TRACEFS_ERROR if there is an error reading the events. + */ +enum tracefs_enable_state +tracefs_event_is_enabled(struct tracefs_instance *instance, + const char *system, const char *event) +{ + enum event_state state = STATE_INIT; + int ret; + + ret = event_enable_disable(instance, system, event, false, &state); + + if (ret < 0) + return TRACEFS_ERROR; + + switch (state) { + case STATE_ENABLED: + return TRACEFS_ALL_ENABLED; + case STATE_DISABLED: + return TRACEFS_ALL_DISABLED; + case STATE_MIXED: + return TRACEFS_SOME_ENABLED; + default: + return TRACEFS_ERROR; + } +} diff --git a/src/tracefs-filter.c b/src/tracefs-filter.c new file mode 100644 index 0000000..a3dd77b --- /dev/null +++ b/src/tracefs-filter.c @@ -0,0 +1,807 @@ +// SPDX-License-Identifier: LGPL-2.1 +/* + * Copyright (C) 2021 VMware Inc, Steven Rostedt + * + * Updates: + * Copyright (C) 2021, VMware, Tzvetomir Stoyanov + * + */ +#include +#include +#include +#include + +#include "tracefs.h" +#include "tracefs-local.h" + +enum { + S_START, + S_COMPARE, + S_NOT, + S_CONJUNCTION, + S_OPEN_PAREN, + S_CLOSE_PAREN, +}; + +static const struct tep_format_field common_timestamp = { + .type = "u64", + .name = "common_timestamp", + .size = 8, +}; + +static const struct tep_format_field common_timestamp_usecs = { + .type = "u64", + .name = "common_timestamp.usecs", + .size = 8, +}; + +static const struct tep_format_field common_comm = { + .type = "char *", + .name = "common_comm", + .size = 16, +}; + +/* + * This also must be able to accept fields that are OK via the histograms, + * such as common_timestamp. + */ +static const struct tep_format_field *get_event_field(struct tep_event *event, + const char *field_name) +{ + const struct tep_format_field *field; + + if (!strcmp(field_name, TRACEFS_TIMESTAMP)) + return &common_timestamp; + + if (!strcmp(field_name, TRACEFS_TIMESTAMP_USECS)) + return &common_timestamp_usecs; + + field = tep_find_any_field(event, field_name); + if (!field && (!strcmp(field_name, "COMM") || !strcmp(field_name, "comm"))) + return &common_comm; + + return field; +} + +__hidden bool +trace_verify_event_field(struct tep_event *event, + const char *field_name, + const struct tep_format_field **ptr_field) +{ + const struct tep_format_field *field; + + field = get_event_field(event, field_name); + if (!field) { + errno = ENODEV; + return false; + } + + if (ptr_field) + *ptr_field = field; + + return true; +} + +__hidden int trace_test_state(int state) +{ + switch (state) { + case S_START: + case S_CLOSE_PAREN: + case S_COMPARE: + return 0; + } + + errno = EBADE; + return -1; +} + +static int append_filter(char **filter, unsigned int *state, + unsigned int *open_parens, + struct tep_event *event, + enum tracefs_filter type, + const char *field_name, + enum tracefs_compare compare, + const char *val) +{ + const struct tep_format_field *field; + bool is_string; + char *conj = "||"; + char *tmp; + + switch (type) { + case TRACEFS_FILTER_COMPARE: + switch (*state) { + case S_START: + case S_OPEN_PAREN: + case S_CONJUNCTION: + case S_NOT: + break; + default: + goto inval; + } + break; + + case TRACEFS_FILTER_AND: + conj = "&&"; + /* Fall through */ + case TRACEFS_FILTER_OR: + switch (*state) { + case S_COMPARE: + case S_CLOSE_PAREN: + break; + default: + goto inval; + } + /* Don't lose old filter on failure */ + tmp = strdup(*filter); + if (!tmp) + return -1; + tmp = append_string(tmp, NULL, conj); + if (!tmp) + return -1; + free(*filter); + *filter = tmp; + *state = S_CONJUNCTION; + return 0; + + case TRACEFS_FILTER_NOT: + switch (*state) { + case S_START: + case S_OPEN_PAREN: + case S_CONJUNCTION: + case S_NOT: + break; + default: + goto inval; + } + if (*filter) { + tmp = strdup(*filter); + tmp = append_string(tmp, NULL, "!"); + } else { + tmp = strdup("!"); + } + if (!tmp) + return -1; + free(*filter); + *filter = tmp; + *state = S_NOT; + return 0; + + case TRACEFS_FILTER_OPEN_PAREN: + switch (*state) { + case S_START: + case S_OPEN_PAREN: + case S_NOT: + case S_CONJUNCTION: + break; + default: + goto inval; + } + if (*filter) { + tmp = strdup(*filter); + tmp = append_string(tmp, NULL, "("); + } else { + tmp = strdup("("); + } + if (!tmp) + return -1; + free(*filter); + *filter = tmp; + *state = S_OPEN_PAREN; + (*open_parens)++; + return 0; + + case TRACEFS_FILTER_CLOSE_PAREN: + switch (*state) { + case S_CLOSE_PAREN: + case S_COMPARE: + break; + default: + goto inval; + } + if (!*open_parens) + goto inval; + + tmp = strdup(*filter); + if (!tmp) + return -1; + tmp = append_string(tmp, NULL, ")"); + if (!tmp) + return -1; + free(*filter); + *filter = tmp; + *state = S_CLOSE_PAREN; + (*open_parens)--; + return 0; + } + + if (!field_name || !val) + goto inval; + + if (!trace_verify_event_field(event, field_name, &field)) + return -1; + + is_string = field->flags & TEP_FIELD_IS_STRING; + + if (!is_string && (field->flags & TEP_FIELD_IS_ARRAY)) + goto inval; + + if (*filter) { + tmp = strdup(*filter); + if (!tmp) + return -1; + tmp = append_string(tmp, NULL, field_name); + } else { + tmp = strdup(field_name); + } + + switch (compare) { + case TRACEFS_COMPARE_EQ: tmp = append_string(tmp, NULL, " == "); break; + case TRACEFS_COMPARE_NE: tmp = append_string(tmp, NULL, " != "); break; + case TRACEFS_COMPARE_RE: + if (!is_string) + goto inval; + tmp = append_string(tmp, NULL, "~"); + break; + default: + if (is_string) + goto inval; + } + + switch (compare) { + case TRACEFS_COMPARE_GT: tmp = append_string(tmp, NULL, " > "); break; + case TRACEFS_COMPARE_GE: tmp = append_string(tmp, NULL, " >= "); break; + case TRACEFS_COMPARE_LT: tmp = append_string(tmp, NULL, " < "); break; + case TRACEFS_COMPARE_LE: tmp = append_string(tmp, NULL, " <= "); break; + case TRACEFS_COMPARE_AND: tmp = append_string(tmp, NULL, " & "); break; + default: break; + } + + tmp = append_string(tmp, NULL, val); + + if (!tmp) + return -1; + + free(*filter); + *filter = tmp; + *state = S_COMPARE; + + return 0; +inval: + errno = EINVAL; + return -1; +} + +static int count_parens(char *filter, unsigned int *state) +{ + bool backslash = false; + int quote = 0; + int open = 0; + int i; + + if (!filter) + return 0; + + for (i = 0; filter[i]; i++) { + if (quote) { + if (backslash) + backslash = false; + else if (filter[i] == '\\') + backslash = true; + else if (quote == filter[i]) + quote = 0; + continue; + } + + switch (filter[i]) { + case '(': + *state = S_OPEN_PAREN; + open++; + break; + case ')': + *state = S_CLOSE_PAREN; + open--; + break; + case '\'': + case '"': + *state = S_COMPARE; + quote = filter[i]; + break; + case '!': + switch (filter[i+1]) { + case '=': + case '~': + *state = S_COMPARE; + i++; + break; + default: + *state = S_NOT; + } + break; + case '&': + case '|': + if (filter[i] == filter[i+1]) { + *state = S_CONJUNCTION; + i++; + break; + } + /* Fall through */ + case '0' ... '9': + case 'a' ... 'z': + case 'A' ... 'Z': + case '_': case '+': case '-': case '*': case '/': + *state = S_COMPARE; + break; + } + } + return open; +} + +__hidden int trace_append_filter(char **filter, unsigned int *state, + unsigned int *open_parens, + struct tep_event *event, + enum tracefs_filter type, + const char *field_name, + enum tracefs_compare compare, + const char *val) +{ + return append_filter(filter, state, open_parens, event, type, + field_name, compare, val); +} + +/** + * tracefs_filter_string_append - create or append a filter for an event + * @event: tep_event to create / append a filter for + * @filter: Pointer to string to append to (pointer to NULL to create) + * @type: The type of element to add to the filter + * @field: For @type == TRACEFS_FILTER_COMPARE, the field to compare + * @compare: For @type == TRACEFS_FILTER_COMPARE, how to compare @field to @val + * @val: For @type == TRACEFS_FILTER_COMPARE, what value @field is to be + * + * This will put together a filter string for the starting event + * of @synth. It check to make sure that what is added is correct compared + * to the filter that is already built. + * + * @type can be: + * TRACEFS_FILTER_COMPARE: See below + * TRACEFS_FILTER_AND: Append "&&" to the filter + * TRACEFS_FILTER_OR: Append "||" to the filter + * TRACEFS_FILTER_NOT: Append "!" to the filter + * TRACEFS_FILTER_OPEN_PAREN: Append "(" to the filter + * TRACEFS_FILTER_CLOSE_PAREN: Append ")" to the filter + * + * For all types except TRACEFS_FILTER_COMPARE, the @field, @compare, + * and @val are ignored. + * + * For @type == TRACEFS_FILTER_COMPARE. + * + * @field is the name of the field for the start event to compare. + * If it is not a field for the start event, this return an + * error. + * + * @compare can be one of: + * TRACEFS_COMPARE_EQ: Test @field == @val + * TRACEFS_COMPARE_NE: Test @field != @val + * TRACEFS_COMPARE_GT: Test @field > @val + * TRACEFS_COMPARE_GE: Test @field >= @val + * TRACEFS_COMPARE_LT: Test @field < @val + * TRACEFS_COMPARE_LE: Test @field <= @val + * TRACEFS_COMPARE_RE: Test @field ~ @val + * TRACEFS_COMPARE_AND: Test @field & @val + * + * If the @field is of type string, and @compare is not + * TRACEFS_COMPARE_EQ, TRACEFS_COMPARE_NE or TRACEFS_COMPARE_RE, + * then this will return an error. + * + * Various other checks are made, for instance, if more CLOSE_PARENs + * are added than existing OPEN_PARENs. Or if AND is added after an + * OPEN_PAREN or another AND or an OR or a NOT. + * + * Returns 0 on success and -1 on failure. + */ +int tracefs_filter_string_append(struct tep_event *event, char **filter, + enum tracefs_filter type, + const char *field, enum tracefs_compare compare, + const char *val) +{ + unsigned int open_parens; + unsigned int state = 0; + char *str = NULL; + int open; + int ret; + + if (!filter) { + errno = EINVAL; + return -1; + } + + open = count_parens(*filter, &state); + if (open < 0) { + errno = EINVAL; + return -1; + } + + if (*filter) { + /* append_filter() will free filter on error */ + str = strdup(*filter); + if (!str) + return -1; + } + open_parens = open; + + ret = append_filter(&str, &state, &open_parens, + event, type, field, compare, val); + if (!ret) { + free(*filter); + *filter = str; + } + + return ret; +} + +static int error_msg(char **err, char *str, + const char *filter, int i, const char *msg) +{ + char ws[i+2]; + char *errmsg; + + free(str); + + /* msg is NULL for parsing append_filter failing */ + if (!msg) { + switch(errno) { + case ENODEV: + msg = "field not valid"; + break; + default: + msg = "Invalid filter"; + + } + } else + errno = EINVAL; + + if (!err) + return -1; + + if (!filter) { + *err = strdup(msg); + return -1; + } + + memset(ws, ' ', i); + ws[i] = '^'; + ws[i+1] = '\0'; + + errmsg = strdup(filter); + errmsg = append_string(errmsg, "\n", ws); + errmsg = append_string(errmsg, "\n", msg); + errmsg = append_string(errmsg, NULL, "\n"); + + *err = errmsg; + return -1; +} + +static int get_field_end(const char *filter, int i, int *end) +{ + int start_i = i; + + for (; filter[i]; i++) { + switch(filter[i]) { + case '0' ... '9': + if (i == start_i) + return 0; + /* Fall through */ + case 'a' ... 'z': + case 'A' ... 'Z': + case '_': + continue; + default: + *end = i; + return i - start_i; + } + } + *end = i; + return i - start_i; +} + +static int get_compare(const char *filter, int i, enum tracefs_compare *cmp) +{ + int start_i = i; + + for (; filter[i]; i++) { + if (!isspace(filter[i])) + break; + } + + switch(filter[i]) { + case '=': + if (filter[i+1] != '=') + goto err; + *cmp = TRACEFS_COMPARE_EQ; + i++; + break; + case '!': + if (filter[i+1] == '=') { + *cmp = TRACEFS_COMPARE_NE; + i++; + break; + } + if (filter[i+1] == '~') { + /* todo! */ + } + goto err; + case '>': + if (filter[i+1] == '=') { + *cmp = TRACEFS_COMPARE_GE; + i++; + break; + } + *cmp = TRACEFS_COMPARE_GT; + break; + case '<': + if (filter[i+1] == '=') { + *cmp = TRACEFS_COMPARE_LE; + i++; + break; + } + *cmp = TRACEFS_COMPARE_LT; + break; + case '~': + *cmp = TRACEFS_COMPARE_RE; + break; + case '&': + *cmp = TRACEFS_COMPARE_AND; + break; + default: + goto err; + } + i++; + + for (; filter[i]; i++) { + if (!isspace(filter[i])) + break; + } + return i - start_i; + err: + return start_i - i; /* negative or zero */ +} + +static int get_val_end(const char *filter, int i, int *end) +{ + bool backslash = false; + int start_i = i; + int quote; + + switch (filter[i]) { + case '0': + i++; + if (tolower(filter[i+1]) != 'x' && + !isdigit(filter[i+1])) + break; + /* fall through */ + case '1' ... '9': + switch (tolower(filter[i])) { + case 'x': + for (i++; filter[i]; i++) { + if (!isxdigit(filter[i])) + break; + } + break; + case '0': + for (i++; filter[i]; i++) { + if (filter[i] < '0' || + filter[i] > '7') + break; + } + break; + default: + for (i++; filter[i]; i++) { + if (!isdigit(filter[i])) + break; + } + break; + } + break; + case '"': + case '\'': + quote = filter[i]; + for (i++; filter[i]; i++) { + if (backslash) { + backslash = false; + continue; + } + switch (filter[i]) { + case '\\': + backslash = true; + continue; + case '"': + case '\'': + if (filter[i] == quote) + break; + continue; + default: + continue; + } + break; + } + if (filter[i]) + i++; + break; + default: + break; + } + + *end = i; + return i - start_i; +} + +/** + * tracefs_filter_string_verify - verify a given filter works for an event + * @event: The event to test the given filter for + * @filter: The filter to test + * @err: Error message for syntax errors (NULL to ignore) + * + * Parse the @filter to verify that it is valid for the given @event. + * + * Returns 0 on succes and -1 on error, and except for memory allocation + * errors, @err will be allocated with an error message. It must + * be freed with free(). + */ +int tracefs_filter_string_verify(struct tep_event *event, const char *filter, + char **err) +{ + enum tracefs_filter filter_type; + enum tracefs_compare compare; + char *str = NULL; + char buf[(filter ? strlen(filter) : 0) + 1]; + char *field; + char *val; + unsigned int state = 0; + unsigned int open = 0; + int len; + int end; + int n; + int i; + + if (!filter) + return error_msg(err, str, NULL, 0, "No filter given"); + + len = strlen(filter); + + for (i = 0; i < len; i++) { + field = NULL; + val = NULL; + compare = 0; + + switch (filter[i]) { + case '(': + filter_type = TRACEFS_FILTER_OPEN_PAREN; + break; + case ')': + filter_type = TRACEFS_FILTER_CLOSE_PAREN; + break; + case '!': + filter_type = TRACEFS_FILTER_NOT; + break; + case '&': + case '|': + + if (filter[i] == filter[i+1]) { + i++; + if (filter[i] == '&') + filter_type = TRACEFS_FILTER_AND; + else + filter_type = TRACEFS_FILTER_OR; + break; + } + if (filter[i] == '|') + return error_msg(err, str, filter, i, + "Invalid op"); + + return error_msg(err, str, filter, i, + "Invalid location for '&'"); + default: + if (isspace(filter[i])) + continue; + + field = buf; + + n = get_field_end(filter, i, &end); + if (!n) + return error_msg(err, str, filter, i, + "Invalid field name"); + + strncpy(field, filter+i, n); + + i += n; + field[n++] = '\0'; + + val = field + n; + + n = get_compare(filter, i, &compare); + if (n <= 0) + return error_msg(err, str, filter, i - n, + "Invalid compare"); + + i += n; + get_val_end(filter, i, &end); + n = end - i; + if (!n) + return error_msg(err, str, filter, i, + "Invalid value"); + strncpy(val, filter + i, n); + val[n] = '\0'; + i += n - 1; + + filter_type = TRACEFS_FILTER_COMPARE; + break; + } + n = append_filter(&str, &state, &open, + event, filter_type, field, compare, val); + + if (n < 0) + return error_msg(err, str, filter, i, NULL); + } + + if (open) + return error_msg(err, str, filter, i, + "Not enough closed parenthesis"); + switch (state) { + case S_COMPARE: + case S_CLOSE_PAREN: + break; + default: + return error_msg(err, str, filter, i, + "Unfinished filter"); + } + + free(str); + return 0; +} + +/** + * tracefs_event_filter_apply - apply given filter on event in given instance + * @instance: The instance in which the filter will be applied (NULL for toplevel). + * @event: The event to apply the filter on. + * @filter: The filter to apply. + * + * Apply the @filter to given @event in givem @instance. The @filter string + * should be created with tracefs_filter_string_append(). + * + * Returns 0 on succes and -1 on error. + */ +int tracefs_event_filter_apply(struct tracefs_instance *instance, + struct tep_event *event, const char *filter) +{ + return tracefs_event_file_write(instance, event->system, event->name, + "filter", filter); +} + +/** + * tracefs_event_filter_clear - clear the filter on event in given instance + * @instance: The instance in which the filter will be applied (NULL for toplevel). + * @event: The event to apply the filter on. + * + * Returns 0 on succes and -1 on error. + */ +int tracefs_event_filter_clear(struct tracefs_instance *instance, + struct tep_event *event) +{ + return tracefs_event_file_write(instance, event->system, event->name, + "filter", "0"); +} + +/** Deprecated **/ +int tracefs_event_append_filter(struct tep_event *event, char **filter, + enum tracefs_filter type, + const char *field, enum tracefs_compare compare, + const char *val) +{ + return tracefs_filter_string_append(event, filter, type, field, + compare, val); +} +int tracefs_event_verify_filter(struct tep_event *event, const char *filter, + char **err) +{ + return tracefs_filter_string_verify(event, filter, err); +} diff --git a/src/tracefs-hist.c b/src/tracefs-hist.c new file mode 100644 index 0000000..fb6231e --- /dev/null +++ b/src/tracefs-hist.c @@ -0,0 +1,2401 @@ +// SPDX-License-Identifier: LGPL-2.1 +/* + * Copyright (C) 2021 VMware Inc, Steven Rostedt + * + * Updates: + * Copyright (C) 2021, VMware, Tzvetomir Stoyanov + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tracefs.h" +#include "tracefs-local.h" + +#define HIST_FILE "hist" + +#define ASCENDING ".ascending" +#define DESCENDING ".descending" + +#define SYNTHETIC_GROUP "synthetic" + +struct tracefs_hist { + struct tep_handle *tep; + struct tep_event *event; + char *system; + char *event_name; + char *name; + char **keys; + char **values; + char **sort; + char *filter; + int size; + unsigned int filter_parens; + unsigned int filter_state; +}; + +/* + * tracefs_hist_get_name - get the name of the histogram + * @hist: The histogram to get the name for + * + * Returns name string owned by @hist on success, or NULL on error. + */ +const char *tracefs_hist_get_name(struct tracefs_hist *hist) +{ + return hist ? hist->name : NULL; +} + +/* + * tracefs_hist_get_event - get the event name of the histogram + * @hist: The histogram to get the event name for + * + * Returns event name string owned by @hist on success, or NULL on error. + */ +const char *tracefs_hist_get_event(struct tracefs_hist *hist) +{ + return hist ? hist->event_name : NULL; +} + +/* + * tracefs_hist_get_system - get the system name of the histogram + * @hist: The histogram to get the system name for + * + * Returns system name string owned by @hist on success, or NULL on error. + */ +const char *tracefs_hist_get_system(struct tracefs_hist *hist) +{ + return hist ? hist->system : NULL; +} + +static void add_list(struct trace_seq *seq, const char *start, + char **list) +{ + int i; + + trace_seq_puts(seq, start); + for (i = 0; list[i]; i++) { + if (i) + trace_seq_putc(seq, ','); + trace_seq_puts(seq, list[i]); + } +} + +static void add_hist_commands(struct trace_seq *seq, struct tracefs_hist *hist, + enum tracefs_hist_command command) +{ + if (command == TRACEFS_HIST_CMD_DESTROY) + trace_seq_putc(seq, '!'); + + add_list(seq, "hist:keys=", hist->keys); + + if (hist->values) + add_list(seq, ":vals=", hist->values); + + if (hist->sort) + add_list(seq, ":sort=", hist->sort); + + if (hist->size) + trace_seq_printf(seq, ":size=%d", hist->size); + + switch(command) { + case TRACEFS_HIST_CMD_START: break; + case TRACEFS_HIST_CMD_PAUSE: trace_seq_puts(seq, ":pause"); break; + case TRACEFS_HIST_CMD_CONT: trace_seq_puts(seq, ":cont"); break; + case TRACEFS_HIST_CMD_CLEAR: trace_seq_puts(seq, ":clear"); break; + default: break; + } + + if (hist->name) + trace_seq_printf(seq, ":name=%s", hist->name); + + if (hist->filter) + trace_seq_printf(seq, " if %s", hist->filter); + +} + +/* + * trace_hist_echo_cmd - show how to start the histogram + * @seq: A trace_seq to store the commands to create + * @hist: The histogram to write into the trigger file + * @command: If not zero, can pause, continue or clear the histogram + * + * This shows the echo commands to create the histogram for an event + * with the given fields. + * + * Returns 0 on succes -1 on error. + */ +int +tracefs_hist_echo_cmd(struct trace_seq *seq, struct tracefs_instance *instance, + struct tracefs_hist *hist, + enum tracefs_hist_command command) +{ + const char *system = hist->system; + const char *event = hist->event_name; + char *path; + + if (!hist->keys) { + errno = -EINVAL; + return -1; + } + + path = tracefs_event_get_file(instance, system, event, "trigger"); + if (!path) + return -1; + + trace_seq_puts(seq, "echo '"); + + add_hist_commands(seq, hist, command); + + trace_seq_printf(seq, "' > %s\n", path); + + tracefs_put_tracing_file(path); + + return 0; +} + +/* + * tracefs_hist_command - Create, start, pause, destroy a histogram for an event + * @instance: The instance the histogram will be in (NULL for toplevel) + * @hist: The histogram to write into the trigger file + * @command: Command to perform on a histogram. + * + * Creates, pause, continue, clears, or destroys a histogram. + * + * Returns 0 on succes -1 on error. + */ +int tracefs_hist_command(struct tracefs_instance *instance, + struct tracefs_hist *hist, + enum tracefs_hist_command command) +{ + const char *system = hist->system; + const char *event = hist->event_name; + struct trace_seq seq; + int ret; + + if (!tracefs_event_file_exists(instance, system, event, HIST_FILE)) + return -1; + + errno = -EINVAL; + if (!hist->keys) + return -1; + + trace_seq_init(&seq); + + add_hist_commands(&seq, hist, command); + + trace_seq_putc(&seq, '\n'); + trace_seq_terminate(&seq); + + ret = -1; + if (seq.state == TRACE_SEQ__GOOD) + ret = tracefs_event_file_append(instance, system, event, + "trigger", seq.buffer); + + trace_seq_destroy(&seq); + + return ret < 0 ? -1 : 0; +} + +/** + * tracefs_hist_free - free a tracefs_hist element + * @hist: The histogram to free + */ +void tracefs_hist_free(struct tracefs_hist *hist) +{ + if (!hist) + return; + + tep_unref(hist->tep); + free(hist->system); + free(hist->event_name); + free(hist->name); + free(hist->filter); + tracefs_list_free(hist->keys); + tracefs_list_free(hist->values); + tracefs_list_free(hist->sort); + free(hist); +} + +/** + * tracefs_hist_alloc - Initialize one-dimensional histogram + * @tep: The tep handle that has the @system and @event. + * @system: The system the histogram event is in. + * @event_name: The name of the event that the histogram will be attached to. + * @key: The primary key the histogram will use + * @type: The format type of the key. + * + * Will initialize a histogram descriptor that will be attached to + * the @system/@event with the given @key as the primary. This only + * initializes the descriptor, it does not start the histogram + * in the kernel. + * + * Returns an initialized histogram on success. + * NULL on failure. + */ +struct tracefs_hist * +tracefs_hist_alloc(struct tep_handle *tep, + const char *system, const char *event_name, + const char *key, enum tracefs_hist_key_type type) +{ + struct tracefs_hist_axis axis[] = {{key, type}, {NULL, 0}}; + + return tracefs_hist_alloc_nd(tep, system, event_name, axis); +} + +/** + * tracefs_hist_alloc_2d - Initialize two-dimensional histogram + * @tep: The tep handle that has the @system and @event. + * @system: The system the histogram event is in. + * @event: The event that the histogram will be attached to. + * @key1: The first primary key the histogram will use + * @type1: The format type of the first key. + * @key2: The second primary key the histogram will use + * @type2: The format type of the second key. + * + * Will initialize a histogram descriptor that will be attached to + * the @system/@event with the given @key1 and @key2 as the primaries. + * This only initializes the descriptor, it does not start the histogram + * in the kernel. + * + * Returns an initialized histogram on success. + * NULL on failure. + */ +struct tracefs_hist * +tracefs_hist_alloc_2d(struct tep_handle *tep, + const char *system, const char *event_name, + const char *key1, enum tracefs_hist_key_type type1, + const char *key2, enum tracefs_hist_key_type type2) +{ + struct tracefs_hist_axis axis[] = {{key1, type1}, + {key2, type2}, + {NULL, 0}}; + + return tracefs_hist_alloc_nd(tep, system, event_name, axis); +} + +static struct tracefs_hist * +hist_alloc_nd(struct tep_handle *tep, + const char *system, const char *event_name, + struct tracefs_hist_axis *axes, + struct tracefs_hist_axis_cnt *axes_cnt) +{ + struct tep_event *event; + struct tracefs_hist *hist; + + if (!system || !event_name) + return NULL; + + event = tep_find_event_by_name(tep, system, event_name); + if (!event) + return NULL; + + hist = calloc(1, sizeof(*hist)); + if (!hist) + return NULL; + + tep_ref(tep); + hist->tep = tep; + hist->event = event; + hist->system = strdup(system); + hist->event_name = strdup(event_name); + if (!hist->system || !hist->event_name) + goto fail; + + for (; axes && axes->key; axes++) + if (tracefs_hist_add_key(hist, axes->key, axes->type) < 0) + goto fail; + + for (; axes_cnt && axes_cnt->key; axes_cnt++) + if (tracefs_hist_add_key_cnt(hist, axes_cnt->key, axes_cnt->type, axes_cnt->cnt) < 0) + goto fail; + + return hist; + + fail: + tracefs_hist_free(hist); + return NULL; +} +/** + * tracefs_hist_alloc_nd - Initialize N-dimensional histogram + * @tep: The tep handle that has the @system and @event. + * @system: The system the histogram event is in + * @event: The event that the histogram will be attached to + * @axes: An array of histogram axes, terminated by a {NULL, 0} entry + * + * Will initialize a histogram descriptor that will be attached to + * the @system/@event. This only initializes the descriptor with the given + * @axes keys as primaries. This only initializes the descriptor, it does + * not start the histogram in the kernel. + * + * Returns an initialized histogram on success. + * NULL on failure. + */ +struct tracefs_hist * +tracefs_hist_alloc_nd(struct tep_handle *tep, + const char *system, const char *event_name, + struct tracefs_hist_axis *axes) +{ + return hist_alloc_nd(tep, system, event_name, axes, NULL); +} + +/** + * tracefs_hist_alloc_nd_cnt - Initialize N-dimensional histogram + * @tep: The tep handle that has the @system and @event. + * @system: The system the histogram event is in + * @event: The event that the histogram will be attached to + * @axes: An array of histogram axes, terminated by a {NULL, 0} entry + * + * Will initialize a histogram descriptor that will be attached to + * the @system/@event. This only initializes the descriptor with the given + * @axes keys as primaries. This only initializes the descriptor, it does + * not start the histogram in the kernel. + * + * Returns an initialized histogram on success. + * NULL on failure. + */ +struct tracefs_hist * +tracefs_hist_alloc_nd_cnt(struct tep_handle *tep, + const char *system, const char *event_name, + struct tracefs_hist_axis_cnt *axes) +{ + return hist_alloc_nd(tep, system, event_name, NULL, axes); +} + +/** + * tracefs_hist_add_key - add to a key to a histogram + * @hist: The histogram to add the key to. + * @key: The name of the key field. + * @type: The type of the key format. + * @cnt: Some types require a counter, for those, this is used + * + * This adds a secondary or tertiary key to the histogram. + * + * Returns 0 on success, -1 on error. + */ +int tracefs_hist_add_key_cnt(struct tracefs_hist *hist, const char *key, + enum tracefs_hist_key_type type, int cnt) +{ + bool use_key = false; + char *key_type = NULL; + char **new_list; + int ret = -1; + + switch (type) { + case TRACEFS_HIST_KEY_NORMAL: + use_key = true; + ret = 0; + break; + case TRACEFS_HIST_KEY_HEX: + ret = asprintf(&key_type, "%s.hex", key); + break; + case TRACEFS_HIST_KEY_SYM: + ret = asprintf(&key_type, "%s.sym", key); + break; + case TRACEFS_HIST_KEY_SYM_OFFSET: + ret = asprintf(&key_type, "%s.sym-offset", key); + break; + case TRACEFS_HIST_KEY_SYSCALL: + ret = asprintf(&key_type, "%s.syscall", key); + break; + case TRACEFS_HIST_KEY_EXECNAME: + ret = asprintf(&key_type, "%s.execname", key); + break; + case TRACEFS_HIST_KEY_LOG: + ret = asprintf(&key_type, "%s.log2", key); + break; + case TRACEFS_HIST_KEY_USECS: + ret = asprintf(&key_type, "%s.usecs", key); + break; + case TRACEFS_HIST_KEY_BUCKETS: + ret = asprintf(&key_type, "%s.buckets=%d", key, cnt); + break; + case TRACEFS_HIST_KEY_MAX: + /* error */ + break; + } + + if (ret < 0) + return -1; + + new_list = tracefs_list_add(hist->keys, use_key ? key : key_type); + free(key_type); + if (!new_list) + return -1; + + hist->keys = new_list; + + return 0; +} + +/** + * tracefs_hist_add_key - add to a key to a histogram + * @hist: The histogram to add the key to. + * @key: The name of the key field. + * @type: The type of the key format. + * + * This adds a secondary or tertiary key to the histogram. + * + * Returns 0 on success, -1 on error. + */ +int tracefs_hist_add_key(struct tracefs_hist *hist, const char *key, + enum tracefs_hist_key_type type) +{ + return tracefs_hist_add_key_cnt(hist, key, type, 0); +} + +/** + * tracefs_hist_add_value - add to a value to a histogram + * @hist: The histogram to add the value to. + * @key: The name of the value field. + * + * This adds a value field to the histogram. + * + * Returns 0 on success, -1 on error. + */ +int tracefs_hist_add_value(struct tracefs_hist *hist, const char *value) +{ + char **new_list; + + new_list = tracefs_list_add(hist->values, value); + if (!new_list) + return -1; + + hist->values = new_list; + + return 0; +} + +/** + * tracefs_hist_add_name - name a histogram + * @hist: The histogram to name. + * @name: The name of the histogram. + * + * Adds a name to the histogram. Named histograms will share their + * data with other events that have the same name as if it was + * a single histogram. + * + * If the histogram already has a name, this will fail. + * + * Returns 0 on success, -1 on error. + */ +int tracefs_hist_add_name(struct tracefs_hist *hist, const char *name) +{ + if (hist->name) + return -1; + + hist->name = strdup(name); + + return hist->name ? 0 : -1; +} + +static char ** +add_sort_key(struct tracefs_hist *hist, const char *sort_key, char **list) +{ + char **key_list = hist->keys; + char **val_list = hist->values; + int i; + + if (strcmp(sort_key, TRACEFS_HIST_HITCOUNT) == 0) + goto out; + + for (i = 0; key_list[i]; i++) { + if (strcmp(key_list[i], sort_key) == 0) + break; + } + + if (!key_list[i] && val_list) { + for (i = 0; val_list[i]; i++) { + if (strcmp(val_list[i], sort_key) == 0) + break; + if (!val_list[i]) + return NULL; + } + } + + + out: + return tracefs_list_add(list, sort_key); +} + +/** + * tracefs_hist_add_sort_key - add a key for sorting the histogram + * @hist: The histogram to add the sort key to + * @sort_key: The key to sort + * + * Call the function to add to the list of sort keys of the histohram in + * the order of priority that the keys would be sorted on output. + * + * Returns 0 on success, -1 on error. + */ +int tracefs_hist_add_sort_key(struct tracefs_hist *hist, + const char *sort_key) +{ + char **list = hist->sort; + + if (!hist || !sort_key) + return -1; + + list = add_sort_key(hist, sort_key, hist->sort); + if (!list) + return -1; + + hist->sort = list; + + return 0; +} + +/** + * tracefs_hist_set_sort_key - set a key for sorting the histogram + * @hist: The histogram to set the sort key to + * @sort_key: The key to sort (and the strings after it) + * Last one must be NULL. + * + * Set a list of sort keys in the order of priority that the + * keys would be sorted on output. Keys must be added first. + * + * Returns 0 on success, -1 on error. + */ +int tracefs_hist_set_sort_key(struct tracefs_hist *hist, + const char *sort_key, ...) +{ + char **list = NULL; + char **tmp; + va_list ap; + + if (!hist || !sort_key) + return -1; + + tmp = add_sort_key(hist, sort_key, list); + if (!tmp) + goto fail; + list = tmp; + + va_start(ap, sort_key); + for (;;) { + sort_key = va_arg(ap, const char *); + if (!sort_key) + break; + tmp = add_sort_key(hist, sort_key, list); + if (!tmp) + goto fail; + list = tmp; + } + va_end(ap); + + tracefs_list_free(hist->sort); + hist->sort = list; + + return 0; + fail: + tracefs_list_free(list); + return -1; +} + +static int end_match(const char *sort_key, const char *ending) +{ + int key_len = strlen(sort_key); + int end_len = strlen(ending); + + if (key_len <= end_len) + return 0; + + sort_key += key_len - end_len; + + return strcmp(sort_key, ending) == 0 ? key_len - end_len : 0; +} + +/** + * tracefs_hist_sort_key_direction - set direction of a sort key + * @hist: The histogram to modify. + * @sort_str: The sort key to set the direction for + * @dir: The direction to set the sort key to. + * + * Returns 0 on success, and -1 on error; + */ +int tracefs_hist_sort_key_direction(struct tracefs_hist *hist, + const char *sort_str, + enum tracefs_hist_sort_direction dir) +{ + char **sort = hist->sort; + char *sort_key; + char *direct; + int match; + int i; + + if (!sort) + return -1; + + for (i = 0; sort[i]; i++) { + if (strcmp(sort[i], sort_str) == 0) + break; + } + if (!sort[i]) + return -1; + + sort_key = sort[i]; + + switch (dir) { + case TRACEFS_HIST_SORT_ASCENDING: + direct = ASCENDING; + break; + case TRACEFS_HIST_SORT_DESCENDING: + direct = DESCENDING; + break; + default: + return -1; + } + + match = end_match(sort_key, ASCENDING); + if (match) { + /* Already match? */ + if (dir == TRACEFS_HIST_SORT_ASCENDING) + return 0; + } else { + match = end_match(sort_key, DESCENDING); + /* Already match? */ + if (match && dir == TRACEFS_HIST_SORT_DESCENDING) + return 0; + } + + if (match) + /* Clear the original text */ + sort_key[match] = '\0'; + + sort_key = realloc(sort_key, strlen(sort_key) + strlen(direct) + 1); + if (!sort_key) { + /* Failed to alloc, may need to put back the match */ + sort_key = sort[i]; + if (match) + sort_key[match] = '.'; + return -1; + } + + strcat(sort_key, direct); + sort[i] = sort_key; + return 0; +} + +int tracefs_hist_append_filter(struct tracefs_hist *hist, + enum tracefs_filter type, + const char *field, + enum tracefs_compare compare, + const char *val) +{ + return trace_append_filter(&hist->filter, &hist->filter_state, + &hist->filter_parens, + hist->event, + type, field, compare, val); +} + +enum action_type { + ACTION_NONE, + ACTION_TRACE, + ACTION_SNAPSHOT, + ACTION_SAVE, +}; + +struct action { + struct action *next; + enum action_type type; + enum tracefs_synth_handler handler; + char *handle_field; + char *save; +}; + +struct name_hash { + struct name_hash *next; + char *name; + char *hash; +}; + +/* + * @name: name of the synthetic event + * @start_system: system of the starting event + * @start_event: the starting event + * @end_system: system of the ending event + * @end_event: the ending event + * @actions: List of actions to take + * @match_names: If a match set is to be a synthetic field, it has a name + * @start_match: list of keys in the start event that matches end event + * @end_match: list of keys in the end event that matches the start event + * @compare_names: The synthetic field names of the compared fields + * @start_compare: A list of compare fields in the start to compare to end + * @end_compare: A list of compare fields in the end to compare to start + * @compare_ops: The type of operations to perform between the start and end + * @start_names: The fields in the start event to record + * @end_names: The fields in the end event to record + * @start_filters: The fields in the end event to record + * @end_filters: The fields in the end event to record + * @start_parens: Current parenthesis level for start event + * @end_parens: Current parenthesis level for end event + * @new_format: onmatch().trace(synth_event,..) or onmatch().synth_event(...) + */ +struct tracefs_synth { + struct tracefs_instance *instance; + struct tep_handle *tep; + struct tep_event *start_event; + struct tep_event *end_event; + struct action *actions; + struct action **next_action; + struct tracefs_dynevent *dyn_event; + struct name_hash *name_hash[1 << HASH_BITS]; + char *start_hist; + char *end_hist; + char *name; + char **synthetic_fields; + char **synthetic_args; + char **start_selection; + char **start_keys; + char **end_keys; + char **start_vars; + char **end_vars; + char *start_filter; + char *end_filter; + unsigned int start_parens; + unsigned int start_state; + unsigned int end_parens; + unsigned int end_state; + int *start_type; + char arg_name[16]; + int arg_cnt; + bool new_format; +}; + + /* + * tracefs_synth_get_name - get the name of the synthetic event + * @synth: The synthetic event to get the name for + * + * Returns name string owned by @synth on success, or NULL on error. + */ +const char *tracefs_synth_get_name(struct tracefs_synth *synth) +{ + return synth ? synth->name : NULL; +} + +static void action_free(struct action *action) +{ + free(action->handle_field); + free(action->save); + free(action); +} + +static void free_name_hash(struct name_hash **hash) +{ + struct name_hash *item; + int i; + + for (i = 0; i < 1 << HASH_BITS; i++) { + while ((item = hash[i])) { + hash[i] = item->next; + free(item->name); + free(item->hash); + free(item); + } + } +} + +/** + * tracefs_synth_free - free the resources alloced to a synth + * @synth: The tracefs_synth descriptor + * + * Frees the resources allocated for a @synth created with + * tracefs_synth_alloc(). It does not touch the system. That is, + * any synthetic event created, will not be destroyed by this + * function. + */ +void tracefs_synth_free(struct tracefs_synth *synth) +{ + struct action *action; + + if (!synth) + return; + + free(synth->name); + free(synth->start_hist); + free(synth->end_hist); + tracefs_list_free(synth->synthetic_fields); + tracefs_list_free(synth->synthetic_args); + tracefs_list_free(synth->start_selection); + tracefs_list_free(synth->start_keys); + tracefs_list_free(synth->end_keys); + tracefs_list_free(synth->start_vars); + tracefs_list_free(synth->end_vars); + free_name_hash(synth->name_hash); + free(synth->start_filter); + free(synth->end_filter); + free(synth->start_type); + + tep_unref(synth->tep); + + while ((action = synth->actions)) { + synth->actions = action->next; + action_free(action); + } + tracefs_dynevent_free(synth->dyn_event); + + free(synth); +} + +static bool verify_event_fields(struct tep_event *start_event, + struct tep_event *end_event, + const char *start_field_name, + const char *end_field_name, + const struct tep_format_field **ptr_start_field) +{ + const struct tep_format_field *start_field; + const struct tep_format_field *end_field; + int start_flags, end_flags; + + if (!trace_verify_event_field(start_event, start_field_name, + &start_field)) + return false; + + if (end_event) { + if (!trace_verify_event_field(end_event, end_field_name, + &end_field)) + return false; + + /* A pointer can still match a long */ + start_flags = start_field->flags & ~TEP_FIELD_IS_POINTER; + end_flags = end_field->flags & ~TEP_FIELD_IS_POINTER; + + if (start_flags != end_flags || + start_field->size != end_field->size) { + errno = EBADE; + return false; + } + } + + if (ptr_start_field) + *ptr_start_field = start_field; + + return true; +} + +__hidden char *append_string(char *str, const char *space, const char *add) +{ + char *new; + int len; + + /* String must already be allocated */ + if (!str) + return NULL; + + len = strlen(str) + strlen(add) + 2; + if (space) + len += strlen(space); + + new = realloc(str, len); + if (!new) { + free(str); + return NULL; + } + str = new; + + if (space) + strcat(str, space); + strcat(str, add); + + return str; +} + +static char *add_synth_field(const struct tep_format_field *field, + const char *name) +{ + const char *type; + char size[64]; + char *str; + bool sign; + + if (field->flags & TEP_FIELD_IS_ARRAY) { + str = strdup("char"); + str = append_string(str, " ", name); + str = append_string(str, NULL, "["); + + if (!(field->flags & TEP_FIELD_IS_DYNAMIC)) { + snprintf(size, 64, "%d", field->size); + str = append_string(str, NULL, size); + } + return append_string(str, NULL, "];"); + } + + /* Synthetic events understand pid_t, gfp_t and bool */ + if (strcmp(field->type, "pid_t") == 0 || + strcmp(field->type, "gfp_t") == 0 || + strcmp(field->type, "bool") == 0) { + str = strdup(field->type); + str = append_string(str, " ", name); + return append_string(str, NULL, ";"); + } + + sign = field->flags & TEP_FIELD_IS_SIGNED; + + switch (field->size) { + case 1: + if (!sign) + type = "unsigned char"; + else + type = "char"; + break; + case 2: + if (sign) + type = "s16"; + else + type = "u16"; + break; + case 4: + if (sign) + type = "s32"; + else + type = "u32"; + break; + case 8: + if (sign) + type = "s64"; + else + type = "u64"; + break; + default: + errno = EBADF; + return NULL; + } + + str = strdup(type); + str = append_string(str, " ", name); + return append_string(str, NULL, ";"); +} + +static int add_var(char ***list, const char *name, const char *var, bool is_var) +{ + char **new; + char *assign; + int ret; + + if (is_var) + ret = asprintf(&assign, "%s=$%s", name, var); + else + ret = asprintf(&assign, "%s=%s", name, var); + + if (ret < 0) + return -1; + + new = tracefs_list_add(*list, assign); + free(assign); + + if (!new) + return -1; + *list = new; + return 0; +} + +__hidden struct tracefs_synth * +synth_init_from(struct tep_handle *tep, const char *start_system, + const char *start_event_name) +{ + struct tep_event *start_event; + struct tracefs_synth *synth; + + start_event = tep_find_event_by_name(tep, start_system, + start_event_name); + if (!start_event) { + errno = ENODEV; + return NULL; + } + + synth = calloc(1, sizeof(*synth)); + if (!synth) + return NULL; + + synth->start_event = start_event; + synth->next_action = &synth->actions; + + /* Hold onto a reference to this handler */ + tep_ref(tep); + synth->tep = tep; + + return synth; +} + +static int alloc_synthetic_event(struct tracefs_synth *synth) +{ + char *format; + const char *field; + int i; + + format = strdup(""); + if (!format) + return -1; + + for (i = 0; synth->synthetic_fields && synth->synthetic_fields[i]; i++) { + field = synth->synthetic_fields[i]; + format = append_string(format, i ? " " : NULL, field); + } + + synth->dyn_event = dynevent_alloc(TRACEFS_DYNEVENT_SYNTH, SYNTHETIC_GROUP, + synth->name, NULL, format); + free(format); + + return synth->dyn_event ? 0 : -1; +} + +/* + * See if it is onmatch().trace(synth_event,...) or + * onmatch().synth_event(...) + */ +static bool has_new_format() +{ + char *readme; + char *p; + int size; + + readme = tracefs_instance_file_read(NULL, "README", &size); + if (!readme) + return false; + + p = strstr(readme, "trace(,param list)"); + free(readme); + + return p != NULL; +} + +/** + * tracefs_synth_alloc - create a new tracefs_synth instance + * @tep: The tep handle that holds the events to work on + * @name: The name of the synthetic event being created + * @start_system: The name of the system of the start event (can be NULL) + * @start_event_name: The name of the start event + * @end_system: The name of the system of the end event (can be NULL) + * @end_event_name: The name of the end event + * @start_match_field: The name of the field in start event to match @end_match_field + * @end_match_field: The name of the field in end event to match @start_match_field + * @match_name: Name to call the fields that match (can be NULL) + * + * Creates a tracefs_synth instance that has the minimum requirements to + * create a synthetic event. + * + * @name is will be the name of the synthetic event that this can create. + * + * The start event is found with @start_system and @start_event_name. If + * @start_system is NULL, then the first event with @start_event_name will + * be used. + * + * The end event is found with @end_system and @end_event_name. If + * @end_system is NULL, then the first event with @end_event_name will + * be used. + * + * The @start_match_field is the field in the start event that will be used + * to match the @end_match_field of the end event. + * + * If @match_name is given, then the field that matched the start and + * end events will be passed an a field to the sythetic event with this + * as the field name. + * + * Returns an allocated tracefs_synth descriptor on success and NULL + * on error, with the following set in errno. + * + * ENOMEM - memory allocation failure. + * ENIVAL - a parameter is passed as NULL that should not be + * ENODEV - could not find an event or field + * EBADE - The start and end fields are not compatible to match + * + * Note, this does not modify the system. That is, the synthetic + * event on the system is not created. That needs to be done with + * tracefs_synth_create(). + */ +struct tracefs_synth *tracefs_synth_alloc(struct tep_handle *tep, + const char *name, + const char *start_system, + const char *start_event_name, + const char *end_system, + const char *end_event_name, + const char *start_match_field, + const char *end_match_field, + const char *match_name) +{ + struct tep_event *end_event; + struct tracefs_synth *synth; + int ret = 0; + + if (!tep || !name || !start_event_name || !end_event_name || + !start_match_field || !end_match_field) { + errno = EINVAL; + return NULL; + } + + synth = synth_init_from(tep, start_system, start_event_name); + if (!synth) + return NULL; + + end_event = tep_find_event_by_name(tep, end_system, + end_event_name); + if (!end_event) { + tep_unref(tep); + errno = ENODEV; + return NULL; + } + + synth->end_event = end_event; + + synth->name = strdup(name); + + ret = tracefs_synth_add_match_field(synth, start_match_field, + end_match_field, match_name); + + if (!synth->name || !synth->start_keys || !synth->end_keys || ret) { + tracefs_synth_free(synth); + synth = NULL; + } else + synth->new_format = has_new_format(); + + return synth; +} + +static int add_synth_fields(struct tracefs_synth *synth, + const struct tep_format_field *field, + const char *name, const char *var) +{ + char **list; + char *str; + int ret; + + str = add_synth_field(field, name ? : field->name); + if (!str) + return -1; + + if (!name) + name = var; + + list = tracefs_list_add(synth->synthetic_fields, str); + free(str); + if (!list) + return -1; + synth->synthetic_fields = list; + + ret = asprintf(&str, "$%s", var ? : name); + if (ret < 0) { + trace_list_pop(synth->synthetic_fields); + return -1; + } + + list = tracefs_list_add(synth->synthetic_args, str); + free(str); + if (!list) { + trace_list_pop(synth->synthetic_fields); + return -1; + } + + synth->synthetic_args = list; + + return 0; +} + +/** + * tracefs_synth_add_match_field - add another key to match events + * @synth: The tracefs_synth descriptor + * @start_match_field: The field of the start event to match the end event + * @end_match_field: The field of the end event to match the start event + * @name: The name to show in the synthetic event (NULL is allowed) + * + * This will add another set of keys to use for a match between + * the start event and the end event. + * + * Returns 0 on succes and -1 on error. + * On error, errno is set to: + * ENOMEM - memory allocation failure. + * ENIVAL - a parameter is passed as NULL that should not be + * ENODEV - could not find a field + * EBADE - The start and end fields are not compatible to match + */ +int tracefs_synth_add_match_field(struct tracefs_synth *synth, + const char *start_match_field, + const char *end_match_field, + const char *name) +{ + const struct tep_format_field *key_field; + char **list; + int ret; + + if (!synth || !start_match_field || !end_match_field) { + errno = EINVAL; + return -1; + } + + if (!verify_event_fields(synth->start_event, synth->end_event, + start_match_field, end_match_field, + &key_field)) + return -1; + + list = tracefs_list_add(synth->start_keys, start_match_field); + if (!list) + return -1; + + synth->start_keys = list; + + list = tracefs_list_add(synth->end_keys, end_match_field); + if (!list) { + trace_list_pop(synth->start_keys); + return -1; + } + synth->end_keys = list; + + if (!name) + return 0; + + ret = add_var(&synth->end_vars, name, end_match_field, false); + + if (ret < 0) + goto pop_lists; + + ret = add_synth_fields(synth, key_field, name, NULL); + if (ret < 0) + goto pop_lists; + + return 0; + + pop_lists: + trace_list_pop(synth->start_keys); + trace_list_pop(synth->end_keys); + return -1; +} + +static unsigned int make_rand(void) +{ + struct timeval tv; + unsigned long seed; + + gettimeofday(&tv, NULL); + seed = (tv.tv_sec + tv.tv_usec) + getpid(); + + /* taken from the rand(3) man page */ + seed = seed * 1103515245 + 12345; + return((unsigned)(seed/65536) % 32768); +} + +static char *new_name(struct tracefs_synth *synth, const char *name) +{ + int cnt = synth->arg_cnt + 1; + char *arg; + int ret; + + /* Create a unique argument name */ + if (!synth->arg_name[0]) { + /* make_rand() returns at most 32768 (total 13 bytes in use) */ + sprintf(synth->arg_name, "%u", make_rand()); + } + ret = asprintf(&arg, "__%s_%s_%d", name, synth->arg_name, cnt); + if (ret < 0) + return NULL; + + synth->arg_cnt = cnt; + return arg; +} + +static struct name_hash *find_name(struct tracefs_synth *synth, const char *name) +{ + unsigned int key = quick_hash(name); + struct name_hash *hash = synth->name_hash[key]; + + for (; hash; hash = hash->next) { + if (!strcmp(hash->name, name)) + return hash; + } + return NULL; +} + +static const char *hash_name(struct tracefs_synth *synth, const char *name) +{ + struct name_hash *hash; + int key; + + hash = find_name(synth, name); + if (hash) + return hash->hash; + + hash = malloc(sizeof(*hash)); + if (!hash) + return name; + + hash->hash = new_name(synth, name); + if (!hash->hash) { + free(hash); + return name; + } + + key = quick_hash(name); + hash->next = synth->name_hash[key]; + synth->name_hash[key] = hash; + + hash->name = strdup(name); + if (!hash->name) { + free(hash->hash); + free(hash); + return name; + } + return hash->hash; +} + +static char *new_arg(struct tracefs_synth *synth) +{ + return new_name(synth, "arg"); +} + +/** + * tracefs_synth_add_compare_field - add a comparison between start and end + * @synth: The tracefs_synth descriptor + * @start_compare_field: The field of the start event to compare to the end + * @end_compare_field: The field of the end event to compare to the start + * @calc - How to go about the comparing the fields. + * @name: The name to show in the synthetic event (must NOT be NULL) + * + * This will add a way to compare two different fields between the + * start end end events. + * + * The comparing between events is decided by @calc: + * TRACEFS_SYNTH_DELTA_END - name = end - start + * TRACEFS_SYNTH_DELTA_START - name = start - end + * TRACEFS_SYNTH_ADD - name = end + start + * + * Returns 0 on succes and -1 on error. + * On error, errno is set to: + * ENOMEM - memory allocation failure. + * ENIVAL - a parameter is passed as NULL that should not be + * ENODEV - could not find a field + * EBADE - The start and end fields are not compatible to compare + */ +int tracefs_synth_add_compare_field(struct tracefs_synth *synth, + const char *start_compare_field, + const char *end_compare_field, + enum tracefs_synth_calc calc, + const char *name) +{ + const struct tep_format_field *start_field; + const char *hname; + char *start_arg; + char *compare; + int ret; + + /* Compare fields require a name */ + if (!name || !start_compare_field || !end_compare_field) { + errno = -EINVAL; + return -1; + } + + if (!verify_event_fields(synth->start_event, synth->end_event, + start_compare_field, end_compare_field, + &start_field)) + return -1; + + /* Calculations are not allowed on string */ + if (start_field->flags & (TEP_FIELD_IS_ARRAY | + TEP_FIELD_IS_DYNAMIC)) { + errno = -EINVAL; + return -1; + } + + start_arg = new_arg(synth); + if (!start_arg) + return -1; + + ret = add_var(&synth->start_vars, start_arg, start_compare_field, false); + if (ret < 0) { + free(start_arg); + return -1; + } + + ret = -1; + switch (calc) { + case TRACEFS_SYNTH_DELTA_END: + ret = asprintf(&compare, "%s-$%s", end_compare_field, + start_arg); + break; + case TRACEFS_SYNTH_DELTA_START: + ret = asprintf(&compare, "$%s-%s", start_arg, + end_compare_field); + break; + case TRACEFS_SYNTH_ADD: + ret = asprintf(&compare, "%s+$%s", end_compare_field, + start_arg); + break; + } + free(start_arg); + if (ret < 0) + return -1; + + hname = hash_name(synth, name); + ret = add_var(&synth->end_vars, hname, compare, false); + if (ret < 0) + goto out_free; + + ret = add_synth_fields(synth, start_field, name, hname); + if (ret < 0) + goto out_free; + + out_free: + free(compare); + + return ret ? -1 : 0; +} + +__hidden int synth_add_start_field(struct tracefs_synth *synth, + const char *start_field, + const char *name, + enum tracefs_hist_key_type type, int count) +{ + const struct tep_format_field *field; + const char *var; + char *start_arg; + char **tmp; + int *types; + int len; + int ret; + + if (!synth || !start_field) { + errno = EINVAL; + return -1; + } + + if (!name) + name = start_field; + + var = hash_name(synth, name); + + if (!trace_verify_event_field(synth->start_event, start_field, &field)) + return -1; + + start_arg = new_arg(synth); + if (!start_arg) + return -1; + + ret = add_var(&synth->start_vars, start_arg, start_field, false); + if (ret) + goto out_free; + + ret = add_var(&synth->end_vars, var, start_arg, true); + if (ret) + goto out_free; + + ret = add_synth_fields(synth, field, name, var); + if (ret) + goto out_free; + + tmp = tracefs_list_add(synth->start_selection, start_field); + if (!tmp) { + ret = -1; + goto out_free; + } + synth->start_selection = tmp; + + len = tracefs_list_size(tmp); + if (len <= 0) { /* ?? */ + ret = -1; + goto out_free; + } + + types = realloc(synth->start_type, sizeof(*types) * len); + if (!types) { + ret = -1; + goto out_free; + } + synth->start_type = types; + synth->start_type[len - 1] = type; + + out_free: + free(start_arg); + return ret; +} + +/** + * tracefs_synth_add_start_field - add a start field to save + * @synth: The tracefs_synth descriptor + * @start_field: The field of the start event to save + * @name: The name to show in the synthetic event (if NULL @start_field is used) + * + * This adds a field named by @start_field of the start event to + * record in the synthetic event. + * + * Returns 0 on succes and -1 on error. + * On error, errno is set to: + * ENOMEM - memory allocation failure. + * ENIVAL - a parameter is passed as NULL that should not be + * ENODEV - could not find a field + */ +int tracefs_synth_add_start_field(struct tracefs_synth *synth, + const char *start_field, + const char *name) +{ + return synth_add_start_field(synth, start_field, name, 0, 0); +} + +/** + * tracefs_synth_add_end_field - add a end field to save + * @synth: The tracefs_synth descriptor + * @end_field: The field of the end event to save + * @name: The name to show in the synthetic event (if NULL @end_field is used) + * + * This adds a field named by @end_field of the start event to + * record in the synthetic event. + * + * Returns 0 on succes and -1 on error. + * On error, errno is set to: + * ENOMEM - memory allocation failure. + * ENIVAL - a parameter is passed as NULL that should not be + * ENODEV - could not find a field + */ +int tracefs_synth_add_end_field(struct tracefs_synth *synth, + const char *end_field, + const char *name) +{ + const struct tep_format_field *field; + const char *hname = NULL; + char *tmp_var = NULL; + int ret; + + if (!synth || !end_field) { + errno = EINVAL; + return -1; + } + + if (name) { + if (strncmp(name, "__arg", 5) != 0) + hname = hash_name(synth, name); + else + hname = name; + } + + if (!name) + tmp_var = new_arg(synth); + + if (!trace_verify_event_field(synth->end_event, end_field, &field)) + return -1; + + ret = add_var(&synth->end_vars, name ? hname : tmp_var, end_field, false); + if (ret) + goto out; + + ret = add_synth_fields(synth, field, name, hname ? : tmp_var); + free(tmp_var); + out: + return ret; +} + +/** + * tracefs_synth_append_start_filter - create or append a filter + * @synth: The tracefs_synth descriptor + * @type: The type of element to add to the filter + * @field: For @type == TRACEFS_FILTER_COMPARE, the field to compare + * @compare: For @type == TRACEFS_FILTER_COMPARE, how to compare @field to @val + * @val: For @type == TRACEFS_FILTER_COMPARE, what value @field is to be + * + * This will put together a filter string for the starting event + * of @synth. It check to make sure that what is added is correct compared + * to the filter that is already built. + * + * @type can be: + * TRACEFS_FILTER_COMPARE: See below + * TRACEFS_FILTER_AND: Append "&&" to the filter + * TRACEFS_FILTER_OR: Append "||" to the filter + * TRACEFS_FILTER_NOT: Append "!" to the filter + * TRACEFS_FILTER_OPEN_PAREN: Append "(" to the filter + * TRACEFS_FILTER_CLOSE_PAREN: Append ")" to the filter + * + * For all types except TRACEFS_FILTER_COMPARE, the @field, @compare, + * and @val are ignored. + * + * For @type == TRACEFS_FILTER_COMPARE. + * + * @field is the name of the field for the start event to compare. + * If it is not a field for the start event, this return an + * error. + * + * @compare can be one of: + * TRACEFS_COMPARE_EQ: Test @field == @val + * TRACEFS_COMPARE_NE: Test @field != @val + * TRACEFS_COMPARE_GT: Test @field > @val + * TRACEFS_COMPARE_GE: Test @field >= @val + * TRACEFS_COMPARE_LT: Test @field < @val + * TRACEFS_COMPARE_LE: Test @field <= @val + * TRACEFS_COMPARE_RE: Test @field ~ @val + * TRACEFS_COMPARE_AND: Test @field & @val + * + * If the @field is of type string, and @compare is not + * TRACEFS_COMPARE_EQ, TRACEFS_COMPARE_NE or TRACEFS_COMPARE_RE, + * then this will return an error. + * + * Various other checks are made, for instance, if more CLOSE_PARENs + * are added than existing OPEN_PARENs. Or if AND is added after an + * OPEN_PAREN or another AND or an OR or a NOT. + * + * Returns 0 on success and -1 on failure. + */ +int tracefs_synth_append_start_filter(struct tracefs_synth *synth, + enum tracefs_filter type, + const char *field, + enum tracefs_compare compare, + const char *val) +{ + return trace_append_filter(&synth->start_filter, &synth->start_state, + &synth->start_parens, + synth->start_event, + type, field, compare, val); +} + +/** + * tracefs_synth_append_end_filter - create or append a filter + * @synth: The tracefs_synth descriptor + * @type: The type of element to add to the filter + * @field: For @type == TRACEFS_FILTER_COMPARE, the field to compare + * @compare: For @type == TRACEFS_FILTER_COMPARE, how to compare @field to @val + * @val: For @type == TRACEFS_FILTER_COMPARE, what value @field is to be + * + * Performs the same thing as tracefs_synth_append_start_filter() but + * for the @synth end event. + */ +int tracefs_synth_append_end_filter(struct tracefs_synth *synth, + enum tracefs_filter type, + const char *field, + enum tracefs_compare compare, + const char *val) +{ + return trace_append_filter(&synth->end_filter, &synth->end_state, + &synth->end_parens, + synth->end_event, + type, field, compare, val); +} + +static int test_max_var(struct tracefs_synth *synth, const char *var) +{ + char **vars = synth->end_vars; + char *p; + int len; + int i; + + len = strlen(var); + + /* Make sure the var is defined for the end event */ + for (i = 0; vars[i]; i++) { + p = strchr(vars[i], '='); + if (!p) + continue; + if (p - vars[i] != len) + continue; + if (!strncmp(var, vars[i], len)) + return 0; + } + errno = ENODEV; + return -1; +} + +static struct action *create_action(enum tracefs_synth_handler type, + struct tracefs_synth *synth, + const char *var) +{ + struct action *action; + int ret; + + switch (type) { + case TRACEFS_SYNTH_HANDLE_MAX: + case TRACEFS_SYNTH_HANDLE_CHANGE: + ret = test_max_var(synth, var); + if (ret < 0) + return NULL; + break; + default: + break; + } + + action = calloc(1, sizeof(*action)); + if (!action) + return NULL; + + if (var) { + ret = asprintf(&action->handle_field, "$%s", var); + if (!action->handle_field) { + free(action); + return NULL; + } + } + return action; +} + +static void add_action(struct tracefs_synth *synth, struct action *action) +{ + *synth->next_action = action; + synth->next_action = &action->next; +} + +/** + * tracefs_synth_trace - Execute the trace option + * @synth: The tracefs_synth descriptor + * @type: The type of handler to attach the trace action with + * @field: The field for handlers onmax and onchange (ignored otherwise) + * + * Add the action 'trace' for handlers onmatch, onmax and onchange. + * + * Returns 0 on succes, -1 on error. + */ +int tracefs_synth_trace(struct tracefs_synth *synth, + enum tracefs_synth_handler type, const char *field) +{ + struct action *action; + + if (!synth || (!field && (type != TRACEFS_SYNTH_HANDLE_MATCH))) { + errno = EINVAL; + return -1; + } + + action = create_action(type, synth, field); + if (!action) + return -1; + + action->type = ACTION_TRACE; + action->handler = type; + add_action(synth, action); + return 0; +} + +/** + * tracefs_synth_snapshot - create a snapshot command to the histogram + * @synth: The tracefs_synth descriptor + * @type: The type of handler to attach the snapshot action with + * @field: The field for handlers onmax and onchange + * + * Add the action to do a snapshot for handlers onmax and onchange. + * + * Returns 0 on succes, -1 on error. + */ +int tracefs_synth_snapshot(struct tracefs_synth *synth, + enum tracefs_synth_handler type, const char *field) +{ + struct action *action; + + if (!synth || !field || (type == TRACEFS_SYNTH_HANDLE_MATCH)) { + errno = EINVAL; + return -1; + } + + action = create_action(type, synth, field); + if (!action) + return -1; + + action->type = ACTION_SNAPSHOT; + action->handler = type; + add_action(synth, action); + return 0; +} + +/** + * tracefs_synth_save - create a save command to the histogram + * @synth: The tracefs_synth descriptor + * @type: The type of handler to attach the save action + * @max_field: The field for handlers onmax and onchange + * @fields: The fields to save for the end variable + * + * Add the action to save fields for handlers onmax and onchange. + * + * Returns 0 on succes, -1 on error. + */ +int tracefs_synth_save(struct tracefs_synth *synth, + enum tracefs_synth_handler type, const char *max_field, + char **fields) +{ + struct action *action; + char *save; + int i; + + if (!synth || !max_field || (type == TRACEFS_SYNTH_HANDLE_MATCH)) { + errno = EINVAL; + return -1; + } + + action = create_action(type, synth, max_field); + if (!action) + return -1; + + action->type = ACTION_SAVE; + action->handler = type; + *synth->next_action = action; + synth->next_action = &action->next; + + save = strdup(".save("); + if (!save) + goto error; + + for (i = 0; fields[i]; i++) { + char *delim = i ? "," : NULL; + + if (!trace_verify_event_field(synth->end_event, fields[i], NULL)) + goto error; + save = append_string(save, delim, fields[i]); + } + save = append_string(save, NULL, ")"); + if (!save) + goto error; + + action->save = save; + add_action(synth, action); + return 0; + error: + action_free(action); + free(save); + return -1; + return 0; +} + +static int remove_hist(struct tracefs_instance *instance, + struct tep_event *event, const char *hist) +{ + char *str; + int ret; + + ret = asprintf(&str, "!%s", hist); + if (ret < 0) + return -1; + + ret = tracefs_event_file_append(instance, event->system, event->name, + "trigger", str); + free(str); + return ret < 0 ? -1 : 0; +} + +static char *create_hist(char **keys, char **vars) +{ + char *hist = strdup("hist:keys="); + char *name; + int i; + + if (!hist) + return NULL; + + for (i = 0; keys[i]; i++) { + name = keys[i]; + if (i) + hist = append_string(hist, NULL, ","); + hist = append_string(hist, NULL, name); + } + + if (!vars) + return hist; + + hist = append_string(hist, NULL, ":"); + + for (i = 0; vars[i]; i++) { + name = vars[i]; + if (i) + hist = append_string(hist, NULL, ","); + hist = append_string(hist, NULL, name); + } + + return hist; +} + +static char *create_onmatch(char *hist, struct tracefs_synth *synth) +{ + hist = append_string(hist, NULL, ":onmatch("); + hist = append_string(hist, NULL, synth->start_event->system); + hist = append_string(hist, NULL, "."); + hist = append_string(hist, NULL, synth->start_event->name); + return append_string(hist, NULL, ")"); +} + +static char *create_trace(char *hist, struct tracefs_synth *synth) +{ + char *name; + int i; + + if (synth->new_format) { + hist = append_string(hist, NULL, ".trace("); + hist = append_string(hist, NULL, synth->name); + hist = append_string(hist, NULL, ","); + } else { + hist = append_string(hist, NULL, "."); + hist = append_string(hist, NULL, synth->name); + hist = append_string(hist, NULL, "("); + } + + for (i = 0; synth->synthetic_args && synth->synthetic_args[i]; i++) { + name = synth->synthetic_args[i]; + + if (i) + hist = append_string(hist, NULL, ","); + hist = append_string(hist, NULL, name); + } + + return append_string(hist, NULL, ")"); +} + +static char *create_max(char *hist, struct tracefs_synth *synth, char *field) +{ + hist = append_string(hist, NULL, ":onmax("); + hist = append_string(hist, NULL, field); + return append_string(hist, NULL, ")"); +} + +static char *create_change(char *hist, struct tracefs_synth *synth, char *field) +{ + hist = append_string(hist, NULL, ":onchange("); + hist = append_string(hist, NULL, field); + return append_string(hist, NULL, ")"); +} + +static char *create_actions(char *hist, struct tracefs_synth *synth) +{ + struct action *action; + + if (!synth->actions) { + /* Default is "onmatch" and "trace" */ + hist = create_onmatch(hist, synth); + return create_trace(hist, synth); + } + + for (action = synth->actions; action; action = action->next) { + switch (action->handler) { + case TRACEFS_SYNTH_HANDLE_MATCH: + hist = create_onmatch(hist, synth); + break; + case TRACEFS_SYNTH_HANDLE_MAX: + hist = create_max(hist, synth, action->handle_field); + break; + case TRACEFS_SYNTH_HANDLE_CHANGE: + hist = create_change(hist, synth, action->handle_field); + break; + default: + continue; + } + switch (action->type) { + case ACTION_TRACE: + hist = create_trace(hist, synth); + break; + case ACTION_SNAPSHOT: + hist = append_string(hist, NULL, ".snapshot()"); + break; + case ACTION_SAVE: + hist = append_string(hist, NULL, action->save); + break; + default: + continue; + } + } + return hist; +} + +static char *create_end_hist(struct tracefs_synth *synth) +{ + char *end_hist; + + end_hist = create_hist(synth->end_keys, synth->end_vars); + return create_actions(end_hist, synth); +} + +/* + * tracefs_synth_raw_fmt - show the raw format of a synthetic event + * @seq: A trace_seq to store the format string + * @synth: The synthetic event to read format from + * + * This shows the raw format that describes the synthetic event, including + * the format of the dynamic event and the start / end histograms. + * + * Returns 0 on succes -1 on error. + */ +int tracefs_synth_raw_fmt(struct trace_seq *seq, struct tracefs_synth *synth) +{ + if (!synth->dyn_event) + return -1; + + trace_seq_printf(seq, "%s", synth->dyn_event->format); + trace_seq_printf(seq, "\n%s", synth->start_hist); + trace_seq_printf(seq, "\n%s", synth->end_hist); + + return 0; +} + +/* + * tracefs_synth_show_event - show the dynamic event used by a synth event + * @synth: The synthetic event to read format from + * + * This shows the raw format of the dynamic event used by the synthetic event. + * + * Returns format string owned by @synth on success, or NULL on error. + */ +const char *tracefs_synth_show_event(struct tracefs_synth *synth) +{ + return synth->dyn_event ? synth->dyn_event->format : NULL; +} + +/* + * tracefs_synth_show_start_hist - show the start histogram used by a synth event + * @synth: The synthetic event to read format from + * + * This shows the raw format of the start histogram used by the synthetic event. + * + * Returns format string owned by @synth on success, or NULL on error. + */ +const char *tracefs_synth_show_start_hist(struct tracefs_synth *synth) +{ + return synth->start_hist; +} + +/* + * tracefs_synth_show_end_hist - show the end histogram used by a synth event + * @synth: The synthetic event to read format from + * + * This shows the raw format of the end histogram used by the synthetic event. + * + * Returns format string owned by @synth on success, or NULL on error. + */ +const char *tracefs_synth_show_end_hist(struct tracefs_synth *synth) +{ + return synth->end_hist; +} + +static char *append_filter(char *hist, char *filter, unsigned int parens) +{ + int i; + + if (!filter) + return hist; + + hist = append_string(hist, NULL, " if "); + hist = append_string(hist, NULL, filter); + for (i = 0; i < parens; i++) + hist = append_string(hist, NULL, ")"); + return hist; +} + +static int verify_state(struct tracefs_synth *synth) +{ + if (trace_test_state(synth->start_state) < 0 || + trace_test_state(synth->end_state) < 0) + return -1; + return 0; +} + +/** + * tracefs_synth_complete - tell if the tracefs_synth is complete or not + * @synth: The synthetic event to get the start hist from. + * + * Retruns true if the synthetic event @synth has both a start and + * end event (ie. a synthetic event, or just a histogram), and + * false otherwise. + */ +bool tracefs_synth_complete(struct tracefs_synth *synth) +{ + return synth && synth->start_event && synth->end_event; +} + +/** + * tracefs_synth_get_start_hist - Return the histogram of the start event + * @synth: The synthetic event to get the start hist from. + * + * On success, returns a tracefs_hist descriptor that holds the + * histogram information of the start_event of the synthetic event + * structure. Returns NULL on failure. + */ +struct tracefs_hist * +tracefs_synth_get_start_hist(struct tracefs_synth *synth) +{ + struct tracefs_hist *hist = NULL; + struct tep_handle *tep; + const char *system; + const char *event; + const char *key; + char **keys; + int *types; + int ret; + int i; + + if (!synth) { + errno = EINVAL; + return NULL; + } + + system = synth->start_event->system; + event = synth->start_event->name; + types = synth->start_type; + keys = synth->start_keys; + tep = synth->tep; + + if (!keys) + keys = synth->start_selection; + + if (!keys) + return NULL; + + for (i = 0; keys[i]; i++) { + int type = types ? types[i] : 0; + + if (type == HIST_COUNTER_TYPE) + continue; + + key = keys[i]; + + if (i) { + ret = tracefs_hist_add_key(hist, key, type); + if (ret < 0) { + tracefs_hist_free(hist); + return NULL; + } + } else { + hist = tracefs_hist_alloc(tep, system, event, + key, type); + if (!hist) + return NULL; + } + } + + if (!hist) + return NULL; + + for (i = 0; keys[i]; i++) { + int type = types ? types[i] : 0; + + if (type != HIST_COUNTER_TYPE) + continue; + + key = keys[i]; + + ret = tracefs_hist_add_value(hist, key); + if (ret < 0) { + tracefs_hist_free(hist); + return NULL; + } + } + + if (synth->start_filter) { + hist->filter = strdup(synth->start_filter); + if (!hist->filter) { + tracefs_hist_free(hist); + return NULL; + } + } + + return hist; +} + +/** + * tracefs_synth_create - creates the synthetic event on the system + * @synth: The tracefs_synth descriptor + * + * This creates the synthetic events. + * + * Returns 0 on succes and -1 on error. + * On error, errno is set to: + * ENOMEM - memory allocation failure. + * ENIVAL - a parameter is passed as NULL that should not be or a problem + * writing into the system. + */ +int tracefs_synth_create(struct tracefs_synth *synth) +{ + int ret; + + if (!synth) { + errno = EINVAL; + return -1; + } + + if (!synth->name || !synth->end_event) { + errno = EUNATCH; + return -1; + } + + if (verify_state(synth) < 0) + return -1; + + if (!synth->dyn_event && alloc_synthetic_event(synth)) + return -1; + if (tracefs_dynevent_create(synth->dyn_event)) + return -1; + + synth->start_hist = create_hist(synth->start_keys, synth->start_vars); + synth->start_hist = append_filter(synth->start_hist, synth->start_filter, + synth->start_parens); + if (!synth->start_hist) + goto remove_synthetic; + + synth->end_hist = create_end_hist(synth); + synth->end_hist = append_filter(synth->end_hist, synth->end_filter, + synth->end_parens); + if (!synth->end_hist) + goto remove_synthetic; + + ret = tracefs_event_file_append(synth->instance, synth->start_event->system, + synth->start_event->name, + "trigger", synth->start_hist); + if (ret < 0) + goto remove_synthetic; + + ret = tracefs_event_file_append(synth->instance, synth->end_event->system, + synth->end_event->name, + "trigger", synth->end_hist); + if (ret < 0) + goto remove_start_hist; + + return 0; + + remove_start_hist: + remove_hist(synth->instance, synth->start_event, synth->start_hist); + remove_synthetic: + tracefs_dynevent_destroy(synth->dyn_event, false); + return -1; +} + +/** + * tracefs_synth_destroy - delete the synthetic event from the system + * @synth: The tracefs_synth descriptor + * + * This will destroy a synthetic event created by tracefs_synth_create() + * with the same @synth. + * + * It will attempt to disable the synthetic event in its instance (top by default), + * but if other instances have it active, it is likely to fail, which will likely + * fail on all other parts of tearing down the synthetic event. + * + * Returns 0 on succes and -1 on error. + * On error, errno is set to: + * ENOMEM - memory allocation failure. + * ENIVAL - a parameter is passed as NULL that should not be or a problem + * writing into the system. + */ +int tracefs_synth_destroy(struct tracefs_synth *synth) +{ + char *hist; + int ret; + + if (!synth) { + errno = EINVAL; + return -1; + } + + if (!synth->name || !synth->end_event) { + errno = EUNATCH; + return -1; + } + + /* Try to disable the event if possible */ + tracefs_event_disable(synth->instance, "synthetic", synth->name); + + hist = create_end_hist(synth); + hist = append_filter(hist, synth->end_filter, + synth->end_parens); + if (!hist) + return -1; + ret = remove_hist(synth->instance, synth->end_event, hist); + free(hist); + + hist = create_hist(synth->start_keys, synth->start_vars); + hist = append_filter(hist, synth->start_filter, + synth->start_parens); + if (!hist) + return -1; + + ret = remove_hist(synth->instance, synth->start_event, hist); + free(hist); + + ret = tracefs_dynevent_destroy(synth->dyn_event, true); + + return ret ? -1 : 0; +} + +/** + * tracefs_synth_echo_cmd - show the command lines to create the synthetic event + * @seq: The trace_seq to store the command lines in + * @synth: The tracefs_synth descriptor + * + * This will list the "echo" commands that are equivalent to what would + * be executed by the tracefs_synth_create() command. + * + * Returns 0 on succes and -1 on error. + * On error, errno is set to: + * ENOMEM - memory allocation failure. + */ +int tracefs_synth_echo_cmd(struct trace_seq *seq, + struct tracefs_synth *synth) +{ + bool new_event = false; + char *hist = NULL; + char *path; + int ret = -1; + + if (!synth) { + errno = EINVAL; + return -1; + } + + if (!synth->name || !synth->end_event) { + errno = EUNATCH; + return -1; + } + + if (!synth->dyn_event) { + if (alloc_synthetic_event(synth)) + return -1; + new_event = true; + } + + path = trace_find_tracing_dir(false); + if (!path) + goto out_free; + + trace_seq_printf(seq, "echo '%s%s%s %s' >> %s/%s\n", + synth->dyn_event->prefix, + strlen(synth->dyn_event->prefix) ? ":" : "", + synth->dyn_event->event, + synth->dyn_event->format, path, synth->dyn_event->trace_file); + + tracefs_put_tracing_file(path); + path = tracefs_instance_get_dir(synth->instance); + + hist = create_hist(synth->start_keys, synth->start_vars); + hist = append_filter(hist, synth->start_filter, + synth->start_parens); + if (!hist) + goto out_free; + + trace_seq_printf(seq, "echo '%s' >> %s/events/%s/%s/trigger\n", + hist, path, synth->start_event->system, + synth->start_event->name); + free(hist); + hist = create_end_hist(synth); + hist = append_filter(hist, synth->end_filter, + synth->end_parens); + if (!hist) + goto out_free; + + trace_seq_printf(seq, "echo '%s' >> %s/events/%s/%s/trigger\n", + hist, path, synth->end_event->system, + synth->end_event->name); + + ret = 0; + out_free: + free(hist); + tracefs_put_tracing_file(path); + if (new_event) { + tracefs_dynevent_free(synth->dyn_event); + synth->dyn_event = NULL; + } + return ret; +} + +/** + * tracefs_synth_get_event - return tep event representing the given synthetic event + * @tep: a handle to the trace event parser context that holds the events + * @synth: a synthetic event context, describing given synthetic event. + * + * Returns a pointer to a tep event describing the given synthetic event. The pointer + * is managed by the @tep handle and must not be freed. In case of an error, or in case + * the requested synthetic event is missing in the @tep handler - NULL is returned. + */ +struct tep_event * +tracefs_synth_get_event(struct tep_handle *tep, struct tracefs_synth *synth) +{ + if (!tep || !synth || !synth->name) + return NULL; + + return get_tep_event(tep, SYNTHETIC_GROUP, synth->name); +} diff --git a/src/tracefs-instance.c b/src/tracefs-instance.c new file mode 100644 index 0000000..57f5c7f --- /dev/null +++ b/src/tracefs-instance.c @@ -0,0 +1,1241 @@ +// SPDX-License-Identifier: LGPL-2.1 +/* + * Copyright (C) 2008, 2009, 2010 Red Hat Inc, Steven Rostedt + * + * Updates: + * Copyright (C) 2019, VMware, Tzvetomir Stoyanov + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "tracefs.h" +#include "tracefs-local.h" + +enum { + FLAG_INSTANCE_NEWLY_CREATED = (1 << 0), + FLAG_INSTANCE_DELETED = (1 << 1), +}; + + +struct tracefs_options_mask toplevel_supported_opts; +struct tracefs_options_mask toplevel_enabled_opts; + +__hidden inline struct tracefs_options_mask * +supported_opts_mask(struct tracefs_instance *instance) +{ + return instance ? &instance->supported_opts : &toplevel_supported_opts; +} + +__hidden inline struct tracefs_options_mask * +enabled_opts_mask(struct tracefs_instance *instance) +{ + return instance ? &instance->enabled_opts : &toplevel_enabled_opts; +} + +/** + * instance_alloc - allocate a new ftrace instance + * @trace_dir - Full path to the tracing directory, where the instance is + * @name: The name of the instance (instance will point to this) + * + * Returns a newly allocated instance, or NULL in case of an error. + */ +static struct tracefs_instance *instance_alloc(const char *trace_dir, const char *name) +{ + struct tracefs_instance *instance; + + instance = calloc(1, sizeof(*instance)); + if (!instance) + goto error; + instance->trace_dir = strdup(trace_dir); + if (!instance->trace_dir) + goto error; + if (name) { + instance->name = strdup(name); + if (!instance->name) + goto error; + } + + if (pthread_mutex_init(&instance->lock, NULL) < 0) + goto error; + + instance->ftrace_filter_fd = -1; + instance->ftrace_notrace_fd = -1; + instance->ftrace_marker_fd = -1; + instance->ftrace_marker_raw_fd = -1; + + return instance; + +error: + if (instance) { + free(instance->name); + free(instance->trace_dir); + free(instance); + } + return NULL; +} + + +__hidden int trace_get_instance(struct tracefs_instance *instance) +{ + int ret; + + pthread_mutex_lock(&instance->lock); + if (instance->flags & FLAG_INSTANCE_DELETED) { + ret = -1; + } else { + instance->ref++; + ret = 0; + } + pthread_mutex_unlock(&instance->lock); + return ret; +} + +__hidden void trace_put_instance(struct tracefs_instance *instance) +{ + pthread_mutex_lock(&instance->lock); + if (--instance->ref < 0) + instance->flags |= FLAG_INSTANCE_DELETED; + pthread_mutex_unlock(&instance->lock); + + if (!(instance->flags & FLAG_INSTANCE_DELETED)) + return; + + if (instance->ftrace_filter_fd >= 0) + close(instance->ftrace_filter_fd); + + if (instance->ftrace_notrace_fd >= 0) + close(instance->ftrace_notrace_fd); + + if (instance->ftrace_marker_fd >= 0) + close(instance->ftrace_marker_fd); + + if (instance->ftrace_marker_raw_fd >= 0) + close(instance->ftrace_marker_raw_fd); + + free(instance->trace_dir); + free(instance->name); + pthread_mutex_destroy(&instance->lock); + free(instance); +} + +/** + * tracefs_instance_free - Free an instance, previously allocated by + tracefs_instance_create() + * @instance: Pointer to the instance to be freed + * + */ +void tracefs_instance_free(struct tracefs_instance *instance) +{ + if (!instance) + return; + + trace_put_instance(instance); +} + +static mode_t get_trace_file_permissions(char *name) +{ + mode_t rmode = 0; + struct stat st; + char *path; + int ret; + + path = tracefs_get_tracing_file(name); + if (!path) + return 0; + ret = stat(path, &st); + if (ret) + goto out; + rmode = st.st_mode & ACCESSPERMS; +out: + tracefs_put_tracing_file(path); + return rmode; +} + +/** + * tracefs_instance_is_new - Check if the instance is newly created by the library + * @instance: Pointer to an ftrace instance + * + * Returns true, if the ftrace instance is newly created by the library or + * false otherwise. + */ +bool tracefs_instance_is_new(struct tracefs_instance *instance) +{ + if (instance && (instance->flags & FLAG_INSTANCE_NEWLY_CREATED)) + return true; + return false; +} + +/** + * tracefs_instance_create - Create a new ftrace instance + * @name: Name of the instance to be created + * + * Allocates and initializes a new instance structure. If the instance does not + * exist in the system, create it. + * Returns a pointer to a newly allocated instance, or NULL in case of an error. + * The returned instance must be freed by tracefs_instance_free(). + */ +struct tracefs_instance *tracefs_instance_create(const char *name) +{ + struct tracefs_instance *inst = NULL; + char *path = NULL; + const char *tdir; + struct stat st; + mode_t mode; + int ret; + + tdir = tracefs_tracing_dir(); + if (!tdir) + return NULL; + inst = instance_alloc(tdir, name); + if (!inst) + return NULL; + + path = tracefs_instance_get_dir(inst); + ret = stat(path, &st); + if (ret < 0) { + /* Cannot create the top instance, if it does not exist! */ + if (!name) + goto error; + mode = get_trace_file_permissions("instances"); + if (mkdir(path, mode)) + goto error; + inst->flags |= FLAG_INSTANCE_NEWLY_CREATED; + } + tracefs_put_tracing_file(path); + return inst; + +error: + tracefs_instance_free(inst); + return NULL; +} + +/** + * tracefs_instance_alloc - Allocate an instance structure for existing trace instance + * @tracing_dir: full path to the system trace directory, where the new instance is + * if NULL, the default top tracing directory is used. + * @name: Name of the instance. + * + * Allocates and initializes a new instance structure. If the instance does not + * exist, do not create it and exit with error. + * Returns a pointer to a newly allocated instance, or NULL in case of an error + * or the requested instance does not exists. + * The returned instance must be freed by tracefs_instance_free(). + */ +struct tracefs_instance *tracefs_instance_alloc(const char *tracing_dir, + const char *name) +{ + struct tracefs_instance *inst = NULL; + char file[PATH_MAX]; + const char *tdir; + struct stat st; + int ret; + + if (tracing_dir) { + ret = stat(tracing_dir, &st); + if (ret < 0 || !S_ISDIR(st.st_mode)) + return NULL; + tdir = tracing_dir; + + } else + tdir = tracefs_tracing_dir(); + if (!tdir) + return NULL; + + if (name) { + sprintf(file, "%s/instances/%s", tdir, name); + ret = stat(file, &st); + if (ret < 0 || !S_ISDIR(st.st_mode)) + return NULL; + } + inst = instance_alloc(tdir, name); + + return inst; +} + +/** + * tracefs_instance_destroy - Remove a ftrace instance + * @instance: Pointer to the instance to be removed + * + * Returns -1 in case of an error, or 0 otherwise. + */ +int tracefs_instance_destroy(struct tracefs_instance *instance) +{ + char *path; + int ret = -1; + + if (!instance || !instance->name) { + tracefs_warning("Cannot remove top instance"); + return -1; + } + + path = tracefs_instance_get_dir(instance); + if (path) + ret = rmdir(path); + tracefs_put_tracing_file(path); + if (ret) { + pthread_mutex_lock(&instance->lock); + instance->flags |= FLAG_INSTANCE_DELETED; + pthread_mutex_unlock(&instance->lock); + } + + return ret; +} + +/** + * tracefs_instance_get_file - return the path to an instance file. + * @instance: ftrace instance, can be NULL for the top instance + * @file: name of file to return + * + * Returns the path of the @file for the given @instance, or NULL in + * case of an error. + * + * Must use tracefs_put_tracing_file() to free the returned string. + */ +char * +tracefs_instance_get_file(struct tracefs_instance *instance, const char *file) +{ + char *path = NULL; + int ret; + + if (!instance) + return tracefs_get_tracing_file(file); + if (!instance->name) + ret = asprintf(&path, "%s/%s", instance->trace_dir, file); + else + ret = asprintf(&path, "%s/instances/%s/%s", + instance->trace_dir, instance->name, file); + if (ret < 0) + return NULL; + + return path; +} + +/** + * tracefs_instance_get_dir - return the path to the instance directory. + * @instance: ftrace instance, can be NULL for the top instance + * + * Returns the full path to the instance directory + * + * Must use tracefs_put_tracing_file() to free the returned string. + */ +char *tracefs_instance_get_dir(struct tracefs_instance *instance) +{ + char *path = NULL; + int ret; + + if (!instance) /* Top instance of default system trace directory */ + return trace_find_tracing_dir(false); + + if (!instance->name) + return strdup(instance->trace_dir); + + ret = asprintf(&path, "%s/instances/%s", instance->trace_dir, instance->name); + if (ret < 0) { + tracefs_warning("Failed to allocate path for instance %s", + instance->name); + return NULL; + } + + return path; +} + +/** + * tracefs_instance_get_name - return the name of an instance + * @instance: ftrace instance + * + * Returns the name of the given @instance. + * The returned string must *not* be freed. + */ +const char *tracefs_instance_get_name(struct tracefs_instance *instance) +{ + if (instance) + return instance->name; + return NULL; +} + +/** + * tracefs_instance_get_buffer_size - return the buffer size of the ring buffer + * @instance: The instance to get the buffer size from + * @cpu: if less that zero, will return the total size, otherwise the cpu size + * + * Returns the buffer size. If @cpu is less than zero, it returns the total size + * of the ring buffer otherwise it returs the size of the buffer for the given + * CPU. + * + * Returns -1 on error. + */ +ssize_t tracefs_instance_get_buffer_size(struct tracefs_instance *instance, int cpu) +{ + unsigned long long size; + char *path; + char *val; + int ret; + + if (cpu < 0) { + val = tracefs_instance_file_read(instance, "buffer_total_size_kb", NULL); + } else { + ret = asprintf(&path, "per_cpu/cpu%d/buffer_size_kb", cpu); + if (ret < 0) + return ret; + + val = tracefs_instance_file_read(instance, path, NULL); + free(path); + } + + if (!val) + return -1; + + size = strtoull(val, NULL, 0); + free(val); + return size; +} + +int tracefs_instance_set_buffer_size(struct tracefs_instance *instance, size_t size, int cpu) +{ + char *path; + char *val; + int ret; + + ret = asprintf(&val, "%zd", size); + if (ret < 0) + return ret; + + if (cpu < 0) { + ret = tracefs_instance_file_write(instance, "buffer_size_kb", val); + } else { + ret = asprintf(&path, "per_cpu/cpu%d/buffer_size_kb", cpu); + if (ret < 0) { + free(val); + return ret; + } + + ret = tracefs_instance_file_write(instance, path, val); + free(path); + } + free(val); + + return ret < 0 ? -1 : 0; +} + +/** + * tracefs_instance_get_trace_dir - return the top trace directory, where the instance is confuigred + * @instance: ftrace instance + * + * Returns the top trace directory where the given @instance is configured. + * The returned string must *not* be freed. + */ +const char *tracefs_instance_get_trace_dir(struct tracefs_instance *instance) +{ + if (instance) + return instance->trace_dir; + return NULL; +} + +static int write_file(const char *file, const char *str, int flags) +{ + int ret = 0; + int fd; + + fd = open(file, flags); + if (fd < 0) { + tracefs_warning("Failed to open '%s'", file); + return -1; + } + + if (str) + ret = write(fd, str, strlen(str)); + + close(fd); + return ret; +} + +static int instance_file_write(struct tracefs_instance *instance, + const char *file, const char *str, int flags) +{ + struct stat st; + char *path; + int ret; + + path = tracefs_instance_get_file(instance, file); + if (!path) + return -1; + ret = stat(path, &st); + if (ret == 0) + ret = write_file(path, str, flags); + tracefs_put_tracing_file(path); + + return ret; +} + +/** + * tracefs_instance_file_write - Write in trace file of specific instance. + * @instance: ftrace instance, can be NULL for the top instance + * @file: name of the file + * @str: nul terminated string, that will be written in the file. + * + * Returns the number of written bytes, or -1 in case of an error + */ +int tracefs_instance_file_write(struct tracefs_instance *instance, + const char *file, const char *str) +{ + return instance_file_write(instance, file, str, O_WRONLY | O_TRUNC); +} + +/** + * tracefs_instance_file_append - Append to a trace file of specific instance. + * @instance: ftrace instance, can be NULL for the top instance. + * @file: name of the file. + * @str: nul terminated string, that will be appended to the file. + * + * Returns the number of appended bytes, or -1 in case of an error. + */ +int tracefs_instance_file_append(struct tracefs_instance *instance, + const char *file, const char *str) +{ + return instance_file_write(instance, file, str, O_WRONLY); +} + +/** + * tracefs_instance_file_clear - Clear a trace file of specific instance. + * Note, it only opens with O_TRUNC and closes the file. If the file has + * content that does not get cleared in this way, this will not have any + * effect. For example, set_ftrace_filter can have probes that are not + * cleared by O_TRUNC: + * + * echo "schedule:stacktrace" > set_ftrace_filter + * + * This function will not clear the above "set_ftrace_filter" after that + * command. + * @instance: ftrace instance, can be NULL for the top instance. + * @file: name of the file to clear. + * + * Returns 0 on success, or -1 in case of an error. + */ +int tracefs_instance_file_clear(struct tracefs_instance *instance, + const char *file) +{ + return instance_file_write(instance, file, NULL, O_WRONLY | O_TRUNC); +} + +/** + * tracefs_instance_file_read - Read from a trace file of specific instance. + * @instance: ftrace instance, can be NULL for the top instance + * @file: name of the file + * @psize: returns the number of bytes read + * + * Returns a pointer to a nul terminated string, read from the file, or NULL in + * case of an error. + * The return string must be freed by free() + */ +char *tracefs_instance_file_read(struct tracefs_instance *instance, + const char *file, int *psize) +{ + char *buf = NULL; + int size = 0; + char *path; + + path = tracefs_instance_get_file(instance, file); + if (!path) + return NULL; + + size = str_read_file(path, &buf, true); + + tracefs_put_tracing_file(path); + if (buf && psize) + *psize = size; + + return buf; +} + +/** + * tracefs_instance_file_read_number - Read long long integer from a trace file. + * @instance: ftrace instance, can be NULL for the top instance + * @file: name of the file + * @res: The integer from the file. + * + * Returns 0 if the reading is successful and the result is stored in res, -1 + * in case of an error. + */ +int tracefs_instance_file_read_number(struct tracefs_instance *instance, + const char *file, long long *res) +{ + long long num; + int ret = -1; + int size = 0; + char *endptr; + char *str; + + str = tracefs_instance_file_read(instance, file, &size); + if (size && str) { + errno = 0; + num = strtoll(str, &endptr, 0); + if (errno == 0 && str != endptr) { + *res = num; + ret = 0; + } + } + free(str); + return ret; +} + +/** + * tracefs_instance_file_open - Open a trace file for reading and writing + * @instance: ftrace instance, can be NULL for the top instance + * @file: name of the file + * @mode: file open flags, -1 for default O_RDWR + * + * Returns -1 in case of an error, or a valid file descriptor otherwise. + * The returned FD must be closed with close() + */ +int tracefs_instance_file_open(struct tracefs_instance *instance, + const char *file, int mode) +{ + int flags = O_RDWR; + int fd = -1; + char *path; + + path = tracefs_instance_get_file(instance, file); + if (!path) + return -1; + + if (mode >= 0) + flags = mode; + fd = open(path, flags); + tracefs_put_tracing_file(path); + + return fd; +} + +static bool check_file_exists(struct tracefs_instance *instance, + const char *name, bool dir) +{ + char file[PATH_MAX]; + struct stat st; + char *path; + int ret; + + path = tracefs_instance_get_dir(instance); + if (name) + snprintf(file, PATH_MAX, "%s/%s", path, name); + else + snprintf(file, PATH_MAX, "%s", path); + tracefs_put_tracing_file(path); + ret = stat(file, &st); + if (ret < 0) + return false; + + return !dir == !S_ISDIR(st.st_mode); +} + +/** + * tracefs_instance_exists - Check an instance with given name exists + * @name: name of the instance + * + * Returns true if the instance exists, false otherwise + * + */ +bool tracefs_instance_exists(const char *name) +{ + char file[PATH_MAX]; + + if (!name) + return false; + snprintf(file, PATH_MAX, "instances/%s", name); + return check_file_exists(NULL, file, true); +} + +/** + * tracefs_file_exists - Check if a file with given name exists in given instance + * @instance: ftrace instance, can be NULL for the top instance + * @name: name of the file + * + * Returns true if the file exists, false otherwise + * + * If a directory with the given name exists, false is returned. + */ +bool tracefs_file_exists(struct tracefs_instance *instance, const char *name) +{ + return check_file_exists(instance, name, false); +} + +/** + * tracefs_dir_exists - Check if a directory with given name exists in given instance + * @instance: ftrace instance, can be NULL for the top instance + * @name: name of the directory + * + * Returns true if the directory exists, false otherwise + */ +bool tracefs_dir_exists(struct tracefs_instance *instance, const char *name) +{ + return check_file_exists(instance, name, true); +} + +/** + * tracefs_instances_walk - Iterate through all ftrace instances in the system + * @callback: user callback, called for each instance. Instance name is passed + * as input parameter. If the @callback returns non-zero, + * the iteration stops. + * @context: user context, passed to the @callback. + * + * Returns -1 in case of an error, 1 if the iteration was stopped because of the + * callback return value or 0 otherwise. + */ +int tracefs_instances_walk(int (*callback)(const char *, void *), void *context) +{ + struct dirent *dent; + char *path = NULL; + DIR *dir = NULL; + struct stat st; + int fret = -1; + int ret; + + path = tracefs_get_tracing_file("instances"); + if (!path) + return -1; + ret = stat(path, &st); + if (ret < 0 || !S_ISDIR(st.st_mode)) + goto out; + + dir = opendir(path); + if (!dir) + goto out; + fret = 0; + while ((dent = readdir(dir))) { + char *instance; + + if (strcmp(dent->d_name, ".") == 0 || + strcmp(dent->d_name, "..") == 0) + continue; + instance = trace_append_file(path, dent->d_name); + ret = stat(instance, &st); + free(instance); + if (ret < 0 || !S_ISDIR(st.st_mode)) + continue; + if (callback(dent->d_name, context)) { + fret = 1; + break; + } + } + +out: + if (dir) + closedir(dir); + tracefs_put_tracing_file(path); + return fret; +} + +static inline bool match(const char *str, regex_t *re) +{ + if (!re) + return true; + return regexec(re, str, 0, NULL, 0) == 0; +} + +struct instance_list { + regex_t *re; + char **list; + int failed; +}; + +static int build_list(const char *name, void *data) +{ + struct instance_list *list = data; + char **instances; + int ret = -1; + + if (!match(name, list->re)) + return 0; + + instances = tracefs_list_add(list->list, name); + if (!instances) + goto out; + + list->list = instances; + ret = 0; + + out: + list->failed = ret; + return ret; +} + +/** + * tracefs_instances - return a list of instance names + * @regex: A regex of instances to filter on (NULL to match all) + * + * Returns a list of names of existing instances, that must be + * freed with tracefs_list_free(). Note, if there are no matches + * then an empty list will be returned (not NULL). + * NULL on error. + */ +char **tracefs_instances(const char *regex) +{ + struct instance_list list = { .re = NULL, .list = NULL }; + regex_t re; + int ret; + + if (regex) { + ret = regcomp(&re, regex, REG_ICASE|REG_NOSUB); + if (ret < 0) + return NULL; + list.re = &re; + } + + ret = tracefs_instances_walk(build_list, &list); + if (ret < 0 || list.failed) { + tracefs_list_free(list.list); + list.list = NULL; + } else { + /* No matches should produce an empty list */ + if (!list.list) + list.list = trace_list_create_empty(); + } + return list.list; +} + +/** + * tracefs_get_clock - Get the current trace clock + * @instance: ftrace instance, can be NULL for the top instance + * + * Returns the current trace clock of the given instance, or NULL in + * case of an error. + * The return string must be freed by free() + */ +char *tracefs_get_clock(struct tracefs_instance *instance) +{ + char *all_clocks = NULL; + char *ret = NULL; + int bytes = 0; + char *clock; + char *cont; + + all_clocks = tracefs_instance_file_read(instance, "trace_clock", &bytes); + if (!all_clocks || !bytes) + goto out; + + clock = strstr(all_clocks, "["); + if (!clock) + goto out; + clock++; + cont = strstr(clock, "]"); + if (!cont) + goto out; + *cont = '\0'; + + ret = strdup(clock); +out: + free(all_clocks); + return ret; +} + +/** + * tracefs_instance_set_affinity_raw - write a hex bitmask into the affinity + * @instance: The instance to set affinity to (NULL for top level) + * @mask: String containing the hex value to set the tracing affinity to. + * + * Sets the tracing affinity CPU mask for @instance. The @mask is the raw + * value that is used to write into the tracing system. + * + * Return 0 on success and -1 on error. + */ +int tracefs_instance_set_affinity_raw(struct tracefs_instance *instance, + const char *mask) +{ + return tracefs_instance_file_write(instance, "tracing_cpumask", mask); +} + +/** + * tracefs_instance_set_affinity_set - use a cpu_set to define tracing affinity + * @instance: The instance to set affinity to (NULL for top level) + * @set: A CPU set that describes the CPU affinity to set tracing to. + * @set_size: The size in bytes of @set (use CPU_ALLOC_SIZE() to get this value) + * + * Sets the tracing affinity CPU mask for @instance. The bits in @set will be + * used to set the CPUs to have tracing on. + * + * If @set is NULL, then all CPUs defined by sysconf(_SC_NPROCESSORS_CONF) + * will be set, and @set_size is ignored. + * + * Return 0 on success and -1 on error. + */ +int tracefs_instance_set_affinity_set(struct tracefs_instance *instance, + cpu_set_t *set, size_t set_size) +{ + struct trace_seq seq; + bool free_set = false; + bool hit = false; + int nr_cpus; + int cpu; + int ret = -1; + int w, n, i; + + trace_seq_init(&seq); + + /* NULL set means all CPUs to be set */ + if (!set) { + nr_cpus = sysconf(_SC_NPROCESSORS_CONF); + set = CPU_ALLOC(nr_cpus); + if (!set) + goto out; + set_size = CPU_ALLOC_SIZE(nr_cpus); + CPU_ZERO_S(set_size, set); + /* Set all CPUS */ + for (cpu = 0; cpu < nr_cpus; cpu++) + CPU_SET_S(cpu, set_size, set); + free_set = true; + } + /* Convert to a bitmask hex string */ + nr_cpus = (set_size + 1) * 8; + if (nr_cpus < 1) { + /* Must have at least one bit set */ + errno = EINVAL; + goto out; + } + /* Start backwards from 32 bits */ + for (w = ((nr_cpus + 31) / 32) - 1; w >= 0; w--) { + /* Now move one nibble at a time */ + for (n = 7; n >= 0; n--) { + int nibble = 0; + + if ((n * 4) + (w * 32) >= nr_cpus) + continue; + + /* One bit at a time */ + for (i = 3; i >= 0; i--) { + cpu = (w * 32) + (n * 4) + i; + if (cpu >= nr_cpus) + continue; + if (CPU_ISSET_S(cpu, set_size, set)) { + nibble |= 1 << i; + hit = true; + } + } + if (hit && trace_seq_printf(&seq, "%x", nibble) < 0) + goto out; + } + if (hit && w) + if (trace_seq_putc(&seq, ',') < 0) + goto out; + } + if (!hit) { + errno = EINVAL; + goto out; + } + trace_seq_terminate(&seq); + ret = tracefs_instance_set_affinity_raw(instance, seq.buffer); + out: + trace_seq_destroy(&seq); + if (free_set) + CPU_FREE(set); + return ret; +} + +/** + * tracefs_instance_set_affinity - Set the affinity defined by CPU values. + * @instance: The instance to set affinity to (NULL for top level) + * @cpu_str: A string of values that define what CPUs to set. + * + * Sets the tracing affinity CPU mask for @instance. The @cpu_str is a set + * of decimal numbers used to state which CPU should be part of the affinity + * mask. A range may also be specified via a hyphen. + * + * For example, "1,4,6-8" + * + * The numbers do not need to be in order. + * + * If @cpu_str is NULL, then all CPUs defined by sysconf(_SC_NPROCESSORS_CONF) + * will be set. + * + * Return 0 on success and -1 on error. + */ +int tracefs_instance_set_affinity(struct tracefs_instance *instance, + const char *cpu_str) +{ + cpu_set_t *set = NULL; + size_t set_size; + char *word; + char *cpus; + char *del; + char *c; + int max_cpu = 0; + int cpu1, cpu2; + int len; + int ret = -1; + + /* NULL cpu_str means to set all CPUs in the mask */ + if (!cpu_str) + return tracefs_instance_set_affinity_set(instance, NULL, 0); + + /* First, find out how many CPUs are needed */ + cpus = strdup(cpu_str); + if (!cpus) + return -1; + len = strlen(cpus) + 1; + for (word = strtok_r(cpus, ",", &del); word; word = strtok_r(NULL, ",", &del)) { + cpu1 = atoi(word); + if (cpu1 < 0) { + errno = EINVAL; + goto out; + } + if (cpu1 > max_cpu) + max_cpu = cpu1; + cpu2 = -1; + if ((c = strchr(word, '-'))) { + c++; + cpu2 = atoi(c); + if (cpu2 < cpu1) { + errno = EINVAL; + goto out; + } + if (cpu2 > max_cpu) + max_cpu = cpu2; + } + } + /* + * Now ideally, cpus should fit cpu_str as it was orginally allocated + * by strdup(). But I'm paranoid, and can imagine someone playing tricks + * with threads, and changes cpu_str from another thread and messes + * with this. At least only copy what we know is allocated. + */ + strncpy(cpus, cpu_str, len); + + set = CPU_ALLOC(max_cpu + 1); + if (!set) + goto out; + set_size = CPU_ALLOC_SIZE(max_cpu + 1); + CPU_ZERO_S(set_size, set); + + for (word = strtok_r(cpus, ",", &del); word; word = strtok_r(NULL, ",", &del)) { + cpu1 = atoi(word); + if (cpu1 < 0 || cpu1 > max_cpu) { + /* Someone playing games? */ + errno = EACCES; + goto out; + } + cpu2 = cpu1; + if ((c = strchr(word, '-'))) { + c++; + cpu2 = atoi(c); + if (cpu2 < cpu1 || cpu2 > max_cpu) { + errno = EACCES; + goto out; + } + } + for ( ; cpu1 <= cpu2; cpu1++) + CPU_SET(cpu1, set); + } + ret = tracefs_instance_set_affinity_set(instance, set, set_size); + out: + free(cpus); + CPU_FREE(set); + return ret; +} + +/** + * tracefs_instance_get_affinity_raw - read the affinity instance file + * @instance: The instance to get affinity of (NULL for top level) + * + * Reads the affinity file for @instance (or the top level if @instance + * is NULL) and returns it. The returned string must be freed with free(). + * + * Returns the affinity mask on success, and must be freed with free() + * or NULL on error. + */ +char *tracefs_instance_get_affinity_raw(struct tracefs_instance *instance) +{ + return tracefs_instance_file_read(instance, "tracing_cpumask", NULL); +} + +static inline int update_cpu_set(int cpus, int cpu_set, int cpu, + cpu_set_t *set, size_t set_size) +{ + int bit = 1 << cpu; + + if (!(cpus & bit)) + return 0; + + CPU_SET_S(cpu_set + cpu, set_size, set); + + /* + * It is possible that the passed in set_size is not big enough + * to hold the cpu we just set. If that's the case, do not report + * it as being set. + * + * The CPU_ISSET_S() should return false if the CPU given to it + * is bigger than the set itself. + */ + return CPU_ISSET_S(cpu_set + cpu, set_size, set) ? 1 : 0; +} + +/** + * tracefs_instance_get_affinity_set - Retrieve the cpuset of an instance affinity + * @instance: The instance to get affinity of (NULL for top level) + * @set: A CPU set to put the affinity into. + * @set_size: The size in bytes of @set (use CPU_ALLOC_SIZE() to get this value) + * + * Reads the affinity of a given instance and updates the CPU set by the + * instance. + * + * Returns the number of CPUS that are set, or -1 on error. + */ +int tracefs_instance_get_affinity_set(struct tracefs_instance *instance, + cpu_set_t *set, size_t set_size) +{ + char *affinity; + int cpu_set; + int cpus; + int cnt = 0; + int ch; + int i; + + if (!set || !set_size) { + errno = -EINVAL; + return -1; + } + + affinity = tracefs_instance_get_affinity_raw(instance); + if (!affinity) + return -1; + + /* + * The returned affinity should be a comma delimited + * hex string. Work backwards setting the values. + */ + cpu_set = 0; + i = strlen(affinity); + for (i--; i >= 0; i--) { + ch = affinity[i]; + if (isalnum(ch)) { + ch = tolower(ch); + if (isdigit(ch)) + cpus = ch - '0'; + else + cpus = ch - 'a' + 10; + + cnt += update_cpu_set(cpus, cpu_set, 0, set, set_size); + cnt += update_cpu_set(cpus, cpu_set, 1, set, set_size); + cnt += update_cpu_set(cpus, cpu_set, 2, set, set_size); + cnt += update_cpu_set(cpus, cpu_set, 3, set, set_size); + /* Next nibble */ + cpu_set += 4; + } + } + + free(affinity); + + return cnt; +} + +static inline int update_cpu(int cpus, int cpu_set, int cpu, int s, char **set) +{ + char *list; + int bit = 1 << cpu; + int ret; + + if (*set == (char *)-1) + return s; + + if (cpus & bit) { + /* If the previous CPU is set just return s */ + if (s >= 0) + return s; + /* Otherwise, return this cpu */ + return cpu_set + cpu; + } + + /* If the last CPU wasn't set, just return s */ + if (s < 0) + return s; + + /* Update the string */ + if (s == cpu_set + cpu - 1) { + ret = asprintf(&list, "%s%s%d", + *set ? *set : "", *set ? "," : "", s); + } else { + ret = asprintf(&list, "%s%s%d-%d", + *set ? *set : "", *set ? "," : "", + s, cpu_set + cpu - 1); + } + free(*set); + /* Force *set to be a failure */ + if (ret < 0) + *set = (char *)-1; + else + *set = list; + return -1; +} + +/** + * tracefs_instance_get_affinity - Retrieve a string of CPUs for instance affinity + * @instance: The instance to get affinity of (NULL for top level) + * + * Reads the affinity of a given instance and returns a CPU count of the + * instance. For example, if it reads "eb" it will return: + * "0-1,3,5-7" + * + * If no CPUs are set, an empty string is returned "\0", and it too needs + * to be freed. + * + * Returns an allocated string containing the CPU affinity in "human readable" + * format which needs to be freed with free(), or NULL on error. + */ +char *tracefs_instance_get_affinity(struct tracefs_instance *instance) +{ + char *affinity; + char *set = NULL; + int cpu_set; + int cpus; + int ch; + int s = -1; + int i; + + affinity = tracefs_instance_get_affinity_raw(instance); + if (!affinity) + return NULL; + + /* + * The returned affinity should be a comma delimited + * hex string. Work backwards setting the values. + */ + cpu_set = 0; + i = strlen(affinity); + for (i--; i >= 0; i--) { + ch = affinity[i]; + if (isalnum(ch)) { + ch = tolower(ch); + if (isdigit(ch)) + cpus = ch - '0'; + else + cpus = ch - 'a' + 10; + s = update_cpu(cpus, cpu_set, 0, s, &set); + s = update_cpu(cpus, cpu_set, 1, s, &set); + s = update_cpu(cpus, cpu_set, 2, s, &set); + s = update_cpu(cpus, cpu_set, 3, s, &set); + + if (set == (char *)-1) { + set = NULL; + goto out; + } + /* Next nibble */ + cpu_set += 4; + } + } + /* Clean up in case the last CPU is set */ + s = update_cpu(0, cpu_set, 0, s, &set); + + if (!set) + set = strdup(""); + out: + free(affinity); + + return set; +} diff --git a/src/tracefs-kprobes.c b/src/tracefs-kprobes.c new file mode 100644 index 0000000..a8c0163 --- /dev/null +++ b/src/tracefs-kprobes.c @@ -0,0 +1,198 @@ +// SPDX-License-Identifier: LGPL-2.1 +/* + * Copyright (C) 2021 VMware Inc, Steven Rostedt + * + * Updates: + * Copyright (C) 2021, VMware, Tzvetomir Stoyanov + * + */ +#include +#include +#include +#include +#include +#include +#include + +#include "tracefs.h" +#include "tracefs-local.h" + +#define KPROBE_EVENTS "kprobe_events" +#define KPROBE_DEFAULT_GROUP "kprobes" + +static struct tracefs_dynevent * +kprobe_alloc(enum tracefs_dynevent_type type, const char *system, const char *event, + const char *addr, const char *format) +{ + struct tracefs_dynevent *kp; + const char *sys = system; + const char *ename = event; + char *tmp; + + if (!addr) { + errno = EBADMSG; + return NULL; + } + if (!sys) + sys = KPROBE_DEFAULT_GROUP; + + if (!event) { + ename = strdup(addr); + if (!ename) + return NULL; + tmp = strchr(ename, ':'); + if (tmp) + *tmp = '\0'; + } + + kp = dynevent_alloc(type, sys, ename, addr, format); + if (!event) + free((char *)ename); + + return kp; +} + +/** + * tracefs_kprobe_alloc - Allocate new kprobe + * @system: The system name (NULL for the default kprobes) + * @event: The event to create (NULL to use @addr for the event) + * @addr: The function and offset (or address) to insert the probe + * @format: The format string to define the probe. + * + * Allocate a kprobe context that will be in the @system group (or kprobes if + * @system is NULL). Have the name of @event (or @addr if @event is NULL). Will + * be inserted to @addr (function name, with or without offset, or a address). + * And the @format will define the format of the kprobe. + * + * See the Linux documentation file under: + * Documentation/trace/kprobetrace.rst + * + * The kprobe is not created in the system. + * + * Return a pointer to a kprobe context on success, or NULL on error. + * The returned pointer must be freed with tracefs_dynevent_free() + * + * errno will be set to EBADMSG if addr is NULL. + */ +struct tracefs_dynevent * +tracefs_kprobe_alloc(const char *system, const char *event, const char *addr, const char *format) + +{ + return kprobe_alloc(TRACEFS_DYNEVENT_KPROBE, system, event, addr, format); +} + +/** + * tracefs_kretprobe_alloc - Allocate new kretprobe + * @system: The system name (NULL for the default kprobes) + * @event: The event to create (NULL to use @addr for the event) + * @addr: The function and offset (or address) to insert the retprobe + * @format: The format string to define the retprobe. + * @max: Maximum number of instances of the specified function that + * can be probed simultaneously, or 0 for the default value. + * + * Allocate a kretprobe that will be in the @system group (or kprobes if + * @system is NULL). Have the name of @event (or @addr if @event is + * NULL). Will be inserted to @addr (function name, with or without + * offset, or a address). And the @format will define the raw format + * of the kprobe. See the Linux documentation file under: + * Documentation/trace/kprobetrace.rst + * The kretprobe is not created in the system. + * + * Return a pointer to a kprobe context on success, or NULL on error. + * The returned pointer must be freed with tracefs_dynevent_free() + * + * errno will be set to EBADMSG if addr is NULL. + */ +struct tracefs_dynevent * +tracefs_kretprobe_alloc(const char *system, const char *event, + const char *addr, const char *format, unsigned int max) +{ + struct tracefs_dynevent *kp; + int ret; + + kp = kprobe_alloc(TRACEFS_DYNEVENT_KRETPROBE, system, event, addr, format); + if (!kp) + return NULL; + + if (!max) + return kp; + + free(kp->prefix); + kp->prefix = NULL; + ret = asprintf(&kp->prefix, "r%d:", max); + if (ret < 0) + goto error; + + return kp; +error: + tracefs_dynevent_free(kp); + return NULL; +} + +static int kprobe_raw(enum tracefs_dynevent_type type, const char *system, + const char *event, const char *addr, const char *format) +{ + static struct tracefs_dynevent *kp; + int ret; + + kp = kprobe_alloc(type, system, event, addr, format); + if (!kp) + return -1; + + ret = tracefs_dynevent_create(kp); + tracefs_dynevent_free(kp); + + return ret; +} + +/** + * tracefs_kprobe_raw - Create a kprobe using raw format + * @system: The system name (NULL for the default kprobes) + * @event: The event to create (NULL to use @addr for the event) + * @addr: The function and offset (or address) to insert the probe + * @format: The raw format string to define the probe. + * + * Create a kprobe that will be in the @system group (or kprobes if + * @system is NULL). Have the name of @event (or @addr if @event is + * NULL). Will be inserted to @addr (function name, with or without + * offset, or a address). And the @format will define the raw format + * of the kprobe. See the Linux documentation file under: + * Documentation/trace/kprobetrace.rst + * + * Return 0 on success, or -1 on error. + * If the syntex of @format was incorrect, running + * tracefs_error_last(NULL) may show what went wrong. + * + * errno will be set to EBADMSG if addr or format is NULL. + */ +int tracefs_kprobe_raw(const char *system, const char *event, + const char *addr, const char *format) +{ + return kprobe_raw(TRACEFS_DYNEVENT_KPROBE, system, event, addr, format); +} + +/** + * tracefs_kretprobe_raw - Create a kretprobe using raw format + * @system: The system name (NULL for the default kprobes) + * @event: The event to create (NULL to use @addr for the event) + * @addr: The function and offset (or address) to insert the retprobe + * @format: The raw format string to define the retprobe. + * + * Create a kretprobe that will be in the @system group (or kprobes if + * @system is NULL). Have the name of @event (or @addr if @event is + * NULL). Will be inserted to @addr (function name, with or without + * offset, or a address). And the @format will define the raw format + * of the kprobe. See the Linux documentation file under: + * Documentation/trace/kprobetrace.rst + * + * Return 0 on success, or -1 on error. + * If the syntex of @format was incorrect, running + * tracefs_error_last(NULL) may show what went wrong. + * + * errno will be set to EBADMSG if addr or format is NULL. + */ +int tracefs_kretprobe_raw(const char *system, const char *event, + const char *addr, const char *format) +{ + return kprobe_raw(TRACEFS_DYNEVENT_KRETPROBE, system, event, addr, format); +} diff --git a/src/tracefs-marker.c b/src/tracefs-marker.c new file mode 100644 index 0000000..61a07ab --- /dev/null +++ b/src/tracefs-marker.c @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: LGPL-2.1 +/* + * Copyright (C) 2021, VMware, Tzvetomir Stoyanov + * + */ + +#include +#include +#include +#include + +#include "tracefs.h" +#include "tracefs-local.h" + +/* File descriptors for Top level trace markers */ +static int ftrace_marker_fd = -1; +static int ftrace_marker_raw_fd = -1; + +static inline int *get_marker_fd(struct tracefs_instance *instance, bool raw) +{ + if (raw) + return instance ? &instance->ftrace_marker_raw_fd : &ftrace_marker_raw_fd; + return instance ? &instance->ftrace_marker_fd : &ftrace_marker_fd; +} + +static int marker_init(struct tracefs_instance *instance, bool raw) +{ + const char *file = raw ? "trace_marker_raw" : "trace_marker"; + pthread_mutex_t *lock = trace_get_lock(instance); + int *fd = get_marker_fd(instance, raw); + int ret; + + if (*fd >= 0) + return 0; + + /* + * The mutex is only to hold the integrity of the file descriptor + * to prevent opening it more than once, or closing the same + * file descriptor more than once. It does not protect against + * one thread closing the file descriptor and another thread + * writing to it. That is up to the application to prevent + * from happening. + */ + pthread_mutex_lock(lock); + /* The file could have been opened since we taken the lock */ + if (*fd < 0) + *fd = tracefs_instance_file_open(instance, file, O_WRONLY | O_CLOEXEC); + + ret = *fd < 0 ? -1 : 0; + pthread_mutex_unlock(lock); + + return ret; +} + +static void marker_close(struct tracefs_instance *instance, bool raw) +{ + pthread_mutex_t *lock = trace_get_lock(instance); + int *fd = get_marker_fd(instance, raw); + + pthread_mutex_lock(lock); + if (*fd >= 0) { + close(*fd); + *fd = -1; + } + pthread_mutex_unlock(lock); +} + +static int marker_write(struct tracefs_instance *instance, bool raw, void *data, int len) +{ + int *fd = get_marker_fd(instance, raw); + int ret; + + /* + * The lock does not need to be taken for writes. As a write + * does not modify the file descriptor. It's up to the application + * to prevent it from being closed if another thread is doing a write. + */ + if (!data || len < 1) + return -1; + if (*fd < 0) { + ret = marker_init(instance, raw); + if (ret < 0) + return ret; + } + + ret = write(*fd, data, len); + + return ret == len ? 0 : -1; +} + +/** + * tracefs_print_init - Open trace marker of selected instance for writing + * @instance: ftrace instance, can be NULL for top tracing instance. + * + * Returns 0 if the trace marker is opened successfully, or -1 in case of an error + */ +int tracefs_print_init(struct tracefs_instance *instance) +{ + return marker_init(instance, false); +} + +/** + * tracefs_vprintf - Write a formatted string in the trace marker + * @instance: ftrace instance, can be NULL for top tracing instance. + * @fmt: pritnf formatted string + * @ap: list of arguments for the formatted string + * + * If the trace marker of the desired instance is not open already, + * this API will open it for writing. It will stay open until + * tracefs_print_close() is called. + * + * Returns 0 if the string is written correctly, or -1 in case of an error + */ +int tracefs_vprintf(struct tracefs_instance *instance, const char *fmt, va_list ap) +{ + char *str = NULL; + int ret; + + ret = vasprintf(&str, fmt, ap); + if (ret < 0) + return ret; + ret = marker_write(instance, false, str, strlen(str)); + free(str); + + return ret; +} + +/** + * tracefs_printf - Write a formatted string in the trace marker + * @instance: ftrace instance, can be NULL for top tracing instance. + * @fmt: pritnf formatted string with variable arguments ... + * + * If the trace marker of the desired instance is not open already, + * this API will open it for writing. It will stay open until + * tracefs_print_close() is called. + * + * Returns 0 if the string is written correctly, or -1 in case of an error + */ +int tracefs_printf(struct tracefs_instance *instance, const char *fmt, ...) +{ + va_list ap; + int ret; + + va_start(ap, fmt); + ret = tracefs_vprintf(instance, fmt, ap); + va_end(ap); + + return ret; +} + +/** + * tracefs_print_close - Close trace marker of selected instance + * @instance: ftrace instance, can be NULL for top tracing instance. + * + * Closes the trace marker, previously opened with any of the other tracefs_print APIs + */ +void tracefs_print_close(struct tracefs_instance *instance) +{ + marker_close(instance, false); +} + +/** + * tracefs_binary_init - Open raw trace marker of selected instance for writing + * @instance: ftrace instance, can be NULL for top tracing instance. + * + * Returns 0 if the raw trace marker is opened successfully, or -1 in case of an error + */ +int tracefs_binary_init(struct tracefs_instance *instance) +{ + return marker_init(instance, true); +} + +/** + * tracefs_binary_write - Write binary data in the raw trace marker + * @instance: ftrace instance, can be NULL for top tracing instance. + * @data: binary data, that is going to be written in the trace marker + * @len: length of the @data + * + * If the raw trace marker of the desired instance is not open already, + * this API will open it for writing. It will stay open until + * tracefs_binary_close() is called. + * + * Returns 0 if the data is written correctly, or -1 in case of an error + */ +int tracefs_binary_write(struct tracefs_instance *instance, void *data, int len) +{ + return marker_write(instance, true, data, len); +} + +/** + * tracefs_binary_close - Close raw trace marker of selected instance + * @instance: ftrace instance, can be NULL for top tracing instance. + * + * Closes the raw trace marker, previously opened with any of the other tracefs_binary APIs + */ +void tracefs_binary_close(struct tracefs_instance *instance) +{ + marker_close(instance, true); +} diff --git a/src/tracefs-record.c b/src/tracefs-record.c new file mode 100644 index 0000000..b078c86 --- /dev/null +++ b/src/tracefs-record.c @@ -0,0 +1,611 @@ +// SPDX-License-Identifier: LGPL-2.1 +/* + * Copyright (C) 2022 Google Inc, Steven Rostedt + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "tracefs.h" +#include "tracefs-local.h" + +enum { + TC_STOP = 1 << 0, /* Stop reading */ + TC_PERM_NONBLOCK = 1 << 1, /* read is always non blocking */ + TC_NONBLOCK = 1 << 2, /* read is non blocking */ +}; + +struct tracefs_cpu { + int fd; + int flags; + int nfds; + int ctrl_pipe[2]; + int splice_pipe[2]; + int pipe_size; + int subbuf_size; + int buffered; + int splice_read_flags; +}; + +/** + * tracefs_cpu_alloc_fd - create a tracefs_cpu instance for an existing fd + * @fd: The file descriptor to attach the tracefs_cpu to + * @subbuf_size: The expected size to read the subbuffer with + * @nonblock: If true, the file will be opened in O_NONBLOCK mode + * + * Return a descriptor that can read the tracefs trace_pipe_raw file + * that is associated with the given @fd and must be read in @subbuf_size. + * + * Returns NULL on error. + */ +struct tracefs_cpu * +tracefs_cpu_alloc_fd(int fd, int subbuf_size, bool nonblock) +{ + struct tracefs_cpu *tcpu; + int mode = O_RDONLY; + int ret; + + tcpu = calloc(1, sizeof(*tcpu)); + if (!tcpu) + return NULL; + + if (nonblock) { + mode |= O_NONBLOCK; + tcpu->flags |= TC_NONBLOCK | TC_PERM_NONBLOCK; + } + + tcpu->splice_pipe[0] = -1; + tcpu->splice_pipe[1] = -1; + + tcpu->fd = fd; + + tcpu->subbuf_size = subbuf_size; + + if (tcpu->flags & TC_PERM_NONBLOCK) { + tcpu->ctrl_pipe[0] = -1; + tcpu->ctrl_pipe[1] = -1; + } else { + /* ctrl_pipe is used to break out of blocked reads */ + ret = pipe(tcpu->ctrl_pipe); + if (ret < 0) + goto fail; + if (tcpu->ctrl_pipe[0] > tcpu->fd) + tcpu->nfds = tcpu->ctrl_pipe[0] + 1; + else + tcpu->nfds = tcpu->fd + 1; + } + + return tcpu; + fail: + free(tcpu); + return NULL; +} + +/** + * tracefs_cpu_open - open an instance raw trace file + * @instance: the instance (NULL for toplevel) of the cpu raw file to open + * @cpu: The CPU that the raw trace file is associated with + * @nonblock: If true, the file will be opened in O_NONBLOCK mode + * + * Return a descriptor that can read the tracefs trace_pipe_raw file + * for a give @cpu in a given @instance. + * + * Returns NULL on error. + */ +struct tracefs_cpu * +tracefs_cpu_open(struct tracefs_instance *instance, int cpu, bool nonblock) +{ + struct tracefs_cpu *tcpu; + struct tep_handle *tep; + char path[128]; + char *buf; + int mode = O_RDONLY; + int subbuf_size; + int len; + int ret; + int fd; + + if (nonblock) + mode |= O_NONBLOCK; + + sprintf(path, "per_cpu/cpu%d/trace_pipe_raw", cpu); + + fd = tracefs_instance_file_open(instance, path, mode); + if (fd < 0) + return NULL; + + tep = tep_alloc(); + if (!tep) + goto fail; + + /* Get the size of the page */ + buf = tracefs_instance_file_read(NULL, "events/header_page", &len); + if (!buf) + goto fail; + + ret = tep_parse_header_page(tep, buf, len, sizeof(long)); + free(buf); + if (ret < 0) + goto fail; + + subbuf_size = tep_get_sub_buffer_size(tep); + tep_free(tep); + tep = NULL; + + tcpu = tracefs_cpu_alloc_fd(fd, subbuf_size, nonblock); + if (!tcpu) + goto fail; + + return tcpu; + fail: + tep_free(tep); + close(fd); + return NULL; +} + +static void close_fd(int fd) +{ + if (fd < 0) + return; + close(fd); +} + +/** + * tracefs_cpu_free_fd - clean up the tracefs_cpu descriptor + * @tcpu: The descriptor created with tracefs_cpu_alloc_fd() + * + * Closes all the internal file descriptors that were opened by + * tracefs_cpu_alloc_fd(), and frees the descriptor. + */ +void tracefs_cpu_free_fd(struct tracefs_cpu *tcpu) +{ + close_fd(tcpu->ctrl_pipe[0]); + close_fd(tcpu->ctrl_pipe[1]); + close_fd(tcpu->splice_pipe[0]); + close_fd(tcpu->splice_pipe[1]); + + free(tcpu); +} + +/** + * tracefs_cpu_close - clean up and close a raw trace descriptor + * @tcpu: The descriptor created with tracefs_cpu_open() + * + * Closes all the file descriptors associated to the trace_pipe_raw + * opened by tracefs_cpu_open(). + */ +void tracefs_cpu_close(struct tracefs_cpu *tcpu) +{ + if (!tcpu) + return; + + close(tcpu->fd); + tracefs_cpu_free_fd(tcpu); +} + +/** + * tracefs_cpu_read_size - Return the size of the sub buffer + * @tcpu: The descriptor that holds the size of the sub buffer + * + * A lot of the functions that read the data from the trace_pipe_raw + * expect the caller to have allocated enough space to store a full + * subbuffer. Calling this function is a requirement to do so. + */ +int tracefs_cpu_read_size(struct tracefs_cpu *tcpu) +{ + if (!tcpu) + return -1; + return tcpu->subbuf_size; +} + +static void set_nonblock(struct tracefs_cpu *tcpu) +{ + long flags; + + if (tcpu->flags & TC_NONBLOCK) + return; + + flags = fcntl(tcpu->fd, F_GETFL); + fcntl(tcpu->fd, F_SETFL, flags | O_NONBLOCK); + tcpu->flags |= TC_NONBLOCK; +} + +static void unset_nonblock(struct tracefs_cpu *tcpu) +{ + long flags; + + if (!(tcpu->flags & TC_NONBLOCK)) + return; + + flags = fcntl(tcpu->fd, F_GETFL); + flags &= ~O_NONBLOCK; + fcntl(tcpu->fd, F_SETFL, flags); + tcpu->flags &= ~TC_NONBLOCK; +} + +/* + * If set to blocking mode, block until the watermark has been + * reached, or the control has said to stop. If the contol is + * set, then nonblock will be set to true on the way out. + */ +static int wait_on_input(struct tracefs_cpu *tcpu, bool nonblock) +{ + fd_set rfds; + int ret; + + if (tcpu->flags & TC_PERM_NONBLOCK) + return 1; + + if (nonblock) { + set_nonblock(tcpu); + return 1; + } else { + unset_nonblock(tcpu); + } + + FD_ZERO(&rfds); + FD_SET(tcpu->fd, &rfds); + FD_SET(tcpu->ctrl_pipe[0], &rfds); + + ret = select(tcpu->nfds, &rfds, NULL, NULL, NULL); + + /* Let the application decide what to do with signals and such */ + if (ret < 0) + return ret; + + if (FD_ISSET(tcpu->ctrl_pipe[0], &rfds)) { + /* Flush the ctrl pipe */ + read(tcpu->ctrl_pipe[0], &ret, 1); + + /* Make nonblock as it is now stopped */ + set_nonblock(tcpu); + /* Permanently set unblock */ + tcpu->flags |= TC_PERM_NONBLOCK; + } + + return FD_ISSET(tcpu->fd, &rfds); +} + +/** + * tracefs_cpu_read - read from the raw trace file + * @tcpu: The descriptor representing the raw trace file + * @buffer: Where to read into (must be at least the size of the subbuffer) + * @nonblock: Hint to not block on the read if there's no data. + * + * Reads the trace_pipe_raw files associated to @tcpu into @buffer. + * @buffer must be at least the size of the sub buffer of the ring buffer, + * which is returned by tracefs_cpu_read_size(). + * + * If @nonblock is set, and there's no data available, it will return + * immediately. Otherwise depending on how @tcpu was opened, it will + * block. If @tcpu was opened with nonblock set, then this @nonblock + * will make no difference. + * + * Returns the amount read or -1 on error. + */ +int tracefs_cpu_read(struct tracefs_cpu *tcpu, void *buffer, bool nonblock) +{ + int ret; + + /* + * If nonblock is set, then the wait_on_input() will return + * immediately, if there's nothing in the buffer, with + * ret == 0. + */ + ret = wait_on_input(tcpu, nonblock); + if (ret <= 0) + return ret; + + ret = read(tcpu->fd, buffer, tcpu->subbuf_size); + + /* It's OK if there's no data to read */ + if (ret < 0 && errno == EAGAIN) { + /* Reset errno */ + errno = 0; + ret = 0; + } + + return ret; +} + +static int init_splice(struct tracefs_cpu *tcpu) +{ + int ret; + + if (tcpu->splice_pipe[0] >= 0) + return 0; + + ret = pipe(tcpu->splice_pipe); + if (ret < 0) + return ret; + + ret = fcntl(tcpu->splice_pipe[0], F_GETPIPE_SZ, &tcpu->pipe_size); + /* + * F_GETPIPE_SZ was introduced in 2.6.35, ftrace was introduced + * in 2.6.31. If we are running on an older kernel, just fall + * back to using subbuf_size for splice(). It could also return + * the size of the pipe and not set pipe_size. + */ + if (ret > 0 && !tcpu->pipe_size) + tcpu->pipe_size = ret; + else if (ret < 0) + tcpu->pipe_size = tcpu->subbuf_size; + + tcpu->splice_read_flags = SPLICE_F_MOVE; + if (tcpu->flags & TC_NONBLOCK) + tcpu->splice_read_flags |= SPLICE_F_NONBLOCK; + + return 0; +} + +/** + * tracefs_cpu_buffered_read - Read the raw trace data buffering through a pipe + * @tcpu: The descriptor representing the raw trace file + * @buffer: Where to read into (must be at least the size of the subbuffer) + * @nonblock: Hint to not block on the read if there's no data. + * + * This is basically the same as tracefs_cpu_read() except that it uses + * a pipe through splice to buffer reads. This will batch reads keeping + * the reading from the ring buffer less intrusive to the system, as + * just reading all the time can cause quite a disturbance. + * + * Note, one difference between this and tracefs_cpu_read() is that it + * will read only in sub buffer pages. If the ring buffer has not filled + * a page, then it will not return anything, even with @nonblock set. + * Calls to tracefs_cpu_flush() should be done to read the rest of + * the file at the end of the trace. + * + * Returns the amount read or -1 on error. + */ +int tracefs_cpu_buffered_read(struct tracefs_cpu *tcpu, void *buffer, bool nonblock) +{ + int mode = SPLICE_F_MOVE; + int ret; + + if (tcpu->buffered < 0) + tcpu->buffered = 0; + + if (tcpu->buffered) + goto do_read; + + ret = wait_on_input(tcpu, nonblock); + if (ret <= 0) + return ret; + + if (tcpu->flags & TC_NONBLOCK) + mode |= SPLICE_F_NONBLOCK; + + ret = init_splice(tcpu); + if (ret < 0) + return ret; + + ret = splice(tcpu->fd, NULL, tcpu->splice_pipe[1], NULL, + tcpu->pipe_size, mode); + if (ret <= 0) + return ret; + + tcpu->buffered = ret; + + do_read: + ret = read(tcpu->splice_pipe[0], buffer, tcpu->subbuf_size); + if (ret > 0) + tcpu->buffered -= ret; + return ret; +} + +/** + * tracefs_cpu_stop - Stop a blocked read of the raw tracing file + * @tcpu: The descriptor representing the raw trace file + * + * This will attempt to unblock a task blocked on @tcpu reading it. + * On older kernels, it may not do anything for the pipe reads, as + * older kernels do not wake up tasks waiting on the ring buffer. + * + * Returns 0 if the tasks reading the raw tracing file does not + * need a nudge. + * + * Returns 1 if that tasks may need a nudge (send a signal). + * + * Returns negative on error. + */ +int tracefs_cpu_stop(struct tracefs_cpu *tcpu) +{ + int ret = 1; + + if (tcpu->flags & TC_PERM_NONBLOCK) + return 0; + + ret = write(tcpu->ctrl_pipe[1], &ret, 1); + if (ret < 0) + return ret; + + /* Calling ioctl() on recent kernels will wake up the waiters */ + ret = ioctl(tcpu->fd, 0); + if (ret < 0) + ret = 1; + else + ret = 0; + + set_nonblock(tcpu); + + return ret; +} + +/** + * tracefs_cpu_flush - Finish out and read the rest of the raw tracing file + * @tcpu: The descriptor representing the raw trace file + * @buffer: Where to read into (must be at least the size of the subbuffer) + * + * Reads the trace_pipe_raw file associated by the @tcpu and puts it + * into @buffer, which must be the size of the sub buffer which is retrieved. + * by tracefs_cpu_read_size(). This should be called at the end of tracing + * to get the rest of the data. + * + * This will set the file descriptor for reading to non-blocking mode. + * + * Returns the number of bytes read, or negative on error. + */ +int tracefs_cpu_flush(struct tracefs_cpu *tcpu, void *buffer) +{ + int ret; + + /* Make sure that reading is now non blocking */ + set_nonblock(tcpu); + + if (tcpu->buffered < 0) + tcpu->buffered = 0; + + if (tcpu->buffered) { + ret = read(tcpu->splice_pipe[0], buffer, tcpu->subbuf_size); + if (ret > 0) + tcpu->buffered -= ret; + return ret; + } + + ret = read(tcpu->fd, buffer, tcpu->subbuf_size); + if (ret > 0 && tcpu->buffered) + tcpu->buffered -= ret; + + /* It's OK if there's no data to read */ + if (ret < 0 && errno == EAGAIN) { + /* Reset errno */ + errno = 0; + ret = 0; + } + + return ret; +} + +/** + * tracefs_cpu_flush_write - Finish out and read the rest of the raw tracing file + * @tcpu: The descriptor representing the raw trace file + * @wfd: The write file descriptor to write the data to + * + * Reads the trace_pipe_raw file associated by the @tcpu and writes it to + * @wfd. This should be called at the end of tracing to get the rest of the data. + * + * Returns the number of bytes written, or negative on error. + */ +int tracefs_cpu_flush_write(struct tracefs_cpu *tcpu, int wfd) +{ + char buffer[tcpu->subbuf_size]; + int ret; + + ret = tracefs_cpu_flush(tcpu, buffer); + if (ret > 0) + ret = write(wfd, buffer, ret); + + /* It's OK if there's no data to read */ + if (ret < 0 && errno == EAGAIN) + ret = 0; + + return ret; +} + +/** + * tracefs_cpu_write - Write the raw trace file into a file descriptor + * @tcpu: The descriptor representing the raw trace file + * @wfd: The write file descriptor to write the data to + * @nonblock: Hint to not block on the read if there's no data. + * + * This will pipe the data from the trace_pipe_raw file associated with @tcpu + * into the @wfd file descriptor. If @nonblock is set, then it will not + * block on if there's nothing to write. Note, it will only write sub buffer + * size data to @wfd. Calls to tracefs_cpu_flush_write() are needed to + * write out the rest. + * + * Returns the number of bytes read or negative on error. + */ +int tracefs_cpu_write(struct tracefs_cpu *tcpu, int wfd, bool nonblock) +{ + char buffer[tcpu->subbuf_size]; + int mode = SPLICE_F_MOVE; + int tot_write = 0; + int tot; + int ret; + + ret = wait_on_input(tcpu, nonblock); + if (ret <= 0) + return ret; + + if (tcpu->flags & TC_NONBLOCK) + mode |= SPLICE_F_NONBLOCK; + + ret = init_splice(tcpu); + if (ret < 0) + return ret; + + tot = splice(tcpu->fd, NULL, tcpu->splice_pipe[1], NULL, + tcpu->pipe_size, mode); + if (tot < 0) + return tot; + + if (tot == 0) + return 0; + + ret = splice(tcpu->splice_pipe[0], NULL, wfd, NULL, + tot, SPLICE_F_MOVE | SPLICE_F_NONBLOCK); + + if (ret >= 0) + return ret; + + /* Some file systems do not allow splicing, try writing instead */ + do { + int r = tcpu->subbuf_size; + + if (r > tot) + r = tot; + + ret = read(tcpu->splice_pipe[0], buffer, r); + if (ret > 0) { + tot -= ret; + ret = write(wfd, buffer, ret); + } + if (ret > 0) + tot_write += ret; + } while (ret > 0); + + if (ret < 0) + return ret; + + return tot_write; +} + +/** + * tracefs_cpu_pipe - Write the raw trace file into a pipe descriptor + * @tcpu: The descriptor representing the raw trace file + * @wfd: The write file descriptor to write the data to (must be a pipe) + * @nonblock: Hint to not block on the read if there's no data. + * + * This will splice directly the file descriptor of the trace_pipe_raw + * file to the given @wfd, which must be a pipe. This can also be used + * if @tcpu was created with tracefs_cpu_create_fd() where the passed + * in @fd there was a pipe, then @wfd does not need to be a pipe. + * + * Returns the number of bytes read or negative on error. + */ +int tracefs_cpu_pipe(struct tracefs_cpu *tcpu, int wfd, bool nonblock) +{ + int mode = SPLICE_F_MOVE; + int ret; + + ret = wait_on_input(tcpu, nonblock); + if (ret <= 0) + return ret; + + if (tcpu->flags & TC_NONBLOCK) + mode |= SPLICE_F_NONBLOCK; + + ret = splice(tcpu->fd, NULL, wfd, NULL, + tcpu->pipe_size, mode); + return ret; +} diff --git a/src/tracefs-sqlhist.c b/src/tracefs-sqlhist.c new file mode 100644 index 0000000..3f571b7 --- /dev/null +++ b/src/tracefs-sqlhist.c @@ -0,0 +1,1653 @@ +// SPDX-License-Identifier: LGPL-2.1 +/* + * Copyright (C) 2021 VMware Inc, Steven Rostedt + * + * Updates: + * Copyright (C) 2021, VMware, Tzvetomir Stoyanov + * + */ +#include +#include +#include +#include +#include + +#include "tracefs.h" +#include "tracefs-local.h" +#include "sqlhist-parse.h" + +extern int yylex_init(void* ptr_yy_globals); +extern int yylex_init_extra(struct sqlhist_bison *sb, void* ptr_yy_globals); +extern int yylex_destroy (void * yyscanner ); + +struct str_hash { + struct str_hash *next; + char *str; +}; + +enum alias_type { + ALIAS_EVENT, + ALIAS_FIELD, +}; + +enum field_type { + FIELD_NONE, + FIELD_FROM, + FIELD_TO, +}; + +#define for_each_field(expr, field, table) \ + for (expr = (table)->fields; expr; expr = (field)->next) + +struct field { + struct expr *next; /* private link list */ + const char *system; + const char *event_name; + struct tep_event *event; + const char *raw; + const char *label; + const char *field; + const char *type; + enum field_type ftype; +}; + +struct filter { + enum filter_type type; + struct expr *lval; + struct expr *rval; +}; + +struct match { + struct match *next; + struct expr *lval; + struct expr *rval; +}; + +struct compare { + enum compare_type type; + struct expr *lval; + struct expr *rval; + const char *name; +}; + +enum expr_type +{ + EXPR_NUMBER, + EXPR_STRING, + EXPR_FIELD, + EXPR_FILTER, + EXPR_COMPARE, +}; + +struct expr { + struct expr *free_list; + struct expr *next; + enum expr_type type; + int line; + int idx; + union { + struct field field; + struct filter filter; + struct compare compare; + const char *string; + long number; + }; +}; + +struct sql_table { + struct sqlhist_bison *sb; + const char *name; + struct expr *exprs; + struct expr *fields; + struct expr *from; + struct expr *to; + struct expr *where; + struct expr **next_where; + struct match *matches; + struct match **next_match; + struct expr *selections; + struct expr **next_selection; +}; + +__hidden int my_yyinput(void *extra, char *buf, int max) +{ + struct sqlhist_bison *sb = extra; + + if (!sb || !sb->buffer) + return -1; + + if (sb->buffer_idx + max > sb->buffer_size) + max = sb->buffer_size - sb->buffer_idx; + + if (max) + memcpy(buf, sb->buffer + sb->buffer_idx, max); + + sb->buffer_idx += max; + + return max; +} + +__hidden void sql_parse_error(struct sqlhist_bison *sb, const char *text, + const char *fmt, va_list ap) +{ + const char *buffer = sb->buffer; + struct trace_seq s; + int line = sb->line_no; + int idx = sb->line_idx - strlen(text); + int i; + + if (!buffer) + return; + + trace_seq_init(&s); + if (!s.buffer) { + tracefs_warning("Error allocating internal buffer\n"); + return; + } + + for (i = 0; line && buffer[i]; i++) { + if (buffer[i] == '\n') + line--; + } + for (; buffer[i] && buffer[i] != '\n'; i++) + trace_seq_putc(&s, buffer[i]); + trace_seq_putc(&s, '\n'); + for (i = idx; i > 0; i--) + trace_seq_putc(&s, ' '); + trace_seq_puts(&s, "^\n"); + trace_seq_printf(&s, "ERROR: '%s'\n", text); + trace_seq_vprintf(&s, fmt, ap); + + trace_seq_terminate(&s); + + sb->parse_error_str = strdup(s.buffer); + trace_seq_destroy(&s); +} + +static void parse_error(struct sqlhist_bison *sb, const char *text, + const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + sql_parse_error(sb, text, fmt, ap); + va_end(ap); +} + +__hidden unsigned int quick_hash(const char *str) +{ + unsigned int val = 0; + int len = strlen(str); + + for (; len >= 4; str += 4, len -= 4) { + val += str[0]; + val += str[1] << 8; + val += str[2] << 16; + val += str[3] << 24; + } + for (; len > 0; str++, len--) + val += str[0] << (len * 8); + + val *= 2654435761; + + return val & ((1 << HASH_BITS) - 1); +} + + +static struct str_hash *find_string(struct sqlhist_bison *sb, const char *str) +{ + unsigned int key = quick_hash(str); + struct str_hash *hash = sb->str_hash[key]; + + for (; hash; hash = hash->next) { + if (!strcmp(hash->str, str)) + return hash; + } + return NULL; +} + +/* + * If @str is found, then return the hash string. + * This lets store_str() know to free str. + */ +static char **add_hash(struct sqlhist_bison *sb, const char *str) +{ + struct str_hash *hash; + unsigned int key; + + if ((hash = find_string(sb, str))) { + return &hash->str; + } + + hash = malloc(sizeof(*hash)); + if (!hash) + return NULL; + key = quick_hash(str); + hash->next = sb->str_hash[key]; + sb->str_hash[key] = hash; + hash->str = NULL; + return &hash->str; +} + +__hidden char *store_str(struct sqlhist_bison *sb, const char *str) +{ + char **pstr = add_hash(sb, str); + + if (!pstr) + return NULL; + + if (!(*pstr)) + *pstr = strdup(str); + + return *pstr; +} + +__hidden void *add_cast(struct sqlhist_bison *sb, + void *data, const char *type) +{ + struct expr *expr = data; + struct field *field = &expr->field; + + field->type = type; + return expr; +} + +__hidden int add_selection(struct sqlhist_bison *sb, void *select, + const char *name) +{ + struct sql_table *table = sb->table; + struct expr *expr = select; + + switch (expr->type) { + case EXPR_FIELD: + expr->field.label = name; + break; + case EXPR_COMPARE: + expr->compare.name = name; + break; + case EXPR_NUMBER: + case EXPR_STRING: + case EXPR_FILTER: + default: + return -1; + } + + if (expr->next) + return -1; + + *table->next_selection = expr; + table->next_selection = &expr->next; + + return 0; +} + +static struct expr *find_field(struct sqlhist_bison *sb, + const char *raw, const char *label) +{ + struct field *field; + struct expr *expr; + + for_each_field(expr, field, sb->table) { + field = &expr->field; + + if (!strcmp(field->raw, raw)) { + if (label && !field->label) + field->label = label; + if (label && strcmp(label, field->label) != 0) + continue; + return expr; + } + + if (label && !strcmp(field->raw, label)) { + if (!field->label) { + field->label = label; + field->raw = raw; + } + return expr; + } + + if (!field->label) + continue; + + if (!strcmp(field->label, raw)) + return expr; + + if (label && !strcmp(field->label, label)) + return expr; + } + return NULL; +} + +static void *create_expr(struct sqlhist_bison *sb, + enum expr_type type, struct expr **expr_p) +{ + struct expr *expr; + + expr = calloc(1, sizeof(*expr)); + if (!expr) + return NULL; + + if (expr_p) + *expr_p = expr; + + expr->free_list = sb->table->exprs; + sb->table->exprs = expr; + + expr->type = type; + expr->line = sb->line_no; + expr->idx = sb->line_idx; + + switch (type) { + case EXPR_FIELD: return &expr->field; + case EXPR_COMPARE: return &expr->compare; + case EXPR_NUMBER: return &expr->number; + case EXPR_STRING: return &expr->string; + case EXPR_FILTER: return &expr->filter; + } + + return NULL; +} + +#define __create_expr(var, type, ENUM, expr) \ + do { \ + var = (type *)create_expr(sb, EXPR_##ENUM, expr); \ + } while(0) + +#define create_field(var, expr) \ + __create_expr(var, struct field, FIELD, expr) + +#define create_filter(var, expr) \ + __create_expr(var, struct filter, FILTER, expr) + +#define create_compare(var, expr) \ + __create_expr(var, struct compare, COMPARE, expr) + +#define create_string(var, expr) \ + __create_expr(var, const char *, STRING, expr) + +#define create_number(var, expr) \ + __create_expr(var, long, NUMBER, expr) + +__hidden void *add_field(struct sqlhist_bison *sb, + const char *field_name, const char *label) +{ + struct sql_table *table = sb->table; + struct expr *expr; + struct field *field; + + expr = find_field(sb, field_name, label); + if (expr) + return expr; + + create_field(field, &expr); + + field->next = table->fields; + table->fields = expr; + + field->raw = field_name; + field->label = label; + + return expr; +} + +__hidden void *add_filter(struct sqlhist_bison *sb, + void *A, void *B, enum filter_type op) +{ + struct filter *filter; + struct expr *expr; + + create_filter(filter, &expr); + + filter->lval = A; + filter->rval = B; + + filter->type = op; + + return expr; +} + +__hidden int add_match(struct sqlhist_bison *sb, void *A, void *B) +{ + struct sql_table *table = sb->table; + struct match *match; + + match = calloc(1, sizeof(*match)); + if (!match) + return -1; + + match->lval = A; + match->rval = B; + + *table->next_match = match; + table->next_match = &match->next; + + return 0; +} +__hidden void *add_compare(struct sqlhist_bison *sb, + void *A, void *B, enum compare_type type) +{ + struct compare *compare; + struct expr *expr; + + create_compare(compare, &expr); + + compare = &expr->compare; + compare->lval = A; + compare->rval = B; + compare->type = type; + + return expr; +} + +__hidden int add_where(struct sqlhist_bison *sb, void *item) +{ + struct expr *expr = item; + struct sql_table *table = sb->table; + + if (expr->type != EXPR_FILTER) + return -1; + + *table->next_where = expr; + table->next_where = &expr->next; + + if (expr->next) + return -1; + + return 0; +} + +__hidden int add_from(struct sqlhist_bison *sb, void *item) +{ + struct expr *expr = item; + + if (expr->type != EXPR_FIELD) + return -1; + + sb->table->from = expr; + + return 0; +} + +__hidden int add_to(struct sqlhist_bison *sb, void *item) +{ + struct expr *expr = item; + + if (expr->type != EXPR_FIELD) + return -1; + + sb->table->to = expr; + + return 0; +} + +__hidden void *add_string(struct sqlhist_bison *sb, const char *str) +{ + struct expr *expr; + const char **str_p; + + create_string(str_p, &expr); + *str_p = str; + return expr; +} + +__hidden void *add_number(struct sqlhist_bison *sb, long val) +{ + struct expr *expr; + long *num; + + create_number(num, &expr); + *num = val; + return expr; +} + +__hidden int table_start(struct sqlhist_bison *sb) +{ + struct sql_table *table; + + table = calloc(1, sizeof(*table)); + if (!table) + return -ENOMEM; + + table->sb = sb; + sb->table = table; + + table->next_where = &table->where; + table->next_match = &table->matches; + table->next_selection = &table->selections; + + return 0; +} + +static int test_event_exists(struct tep_handle *tep, + struct sqlhist_bison *sb, + struct expr *expr, struct tep_event **pevent) +{ + struct field *field = &expr->field; + const char *system = field->system; + const char *event = field->event_name; + + if (!field->event) + field->event = tep_find_event_by_name(tep, system, event); + if (pevent) + *pevent = field->event; + + if (field->event) + return 0; + + sb->line_no = expr->line; + sb->line_idx = expr->idx; + + parse_error(sb, field->raw, "event not found\n"); + return -1; +} + +static int test_field_exists(struct tep_handle *tep, + struct sqlhist_bison *sb, + struct expr *expr) +{ + struct field *field = &expr->field; + struct tep_format_field *tfield; + char *field_name; + const char *p; + + if (!field->event) { + if (test_event_exists(tep, sb, expr, NULL)) + return -1; + } + + /* The field could have a conversion */ + p = strchr(field->field, '.'); + if (p) + field_name = strndup(field->field, p - field->field); + else + field_name = strdup(field->field); + + if (!field_name) + return -1; + + if (!strcmp(field_name, TRACEFS_TIMESTAMP) || + !strcmp(field->field, TRACEFS_TIMESTAMP_USECS)) + tfield = (void *)1L; + else + tfield = tep_find_any_field(field->event, field_name); + free(field_name); + + if (!tfield && (!strcmp(field->field, "COMM") || !strcmp(field->field, "comm"))) + tfield = (void *)1L; + + if (tfield) + return 0; + + sb->line_no = expr->line; + sb->line_idx = expr->idx; + + parse_error(sb, field->raw, + "Field '%s' not part of event %s\n", + field->field, field->event_name); + return -1; +} + +static int update_vars(struct tep_handle *tep, + struct sql_table *table, + struct expr *expr) +{ + struct sqlhist_bison *sb = table->sb; + struct field *event_field = &expr->field; + enum field_type ftype = FIELD_NONE; + struct tep_event *event; + struct field *field; + const char *label; + const char *raw = event_field->raw; + const char *event_name; + const char *system; + const char *p; + int label_len = 0, event_len, system_len; + + if (expr == table->to) + ftype = FIELD_TO; + else if (expr == table->from) + ftype = FIELD_FROM; + + p = strchr(raw, '.'); + if (p) { + char *str; + + str = strndup(raw, p - raw); + if (!str) + return -1; + event_field->system = store_str(sb, str); + free(str); + if (!event_field->system) + return -1; + p++; + } else { + p = raw; + } + + event_field->event_name = store_str(sb, p); + if (!event_field->event_name) + return -1; + + if (test_event_exists(tep, sb, expr, &event)) + return -1; + + if (!event_field->system) + event_field->system = store_str(sb, event->system); + + if (!event_field->system) + return -1; + + label = event_field->label; + if (label) + label_len = strlen(label); + + system = event_field->system; + system_len = strlen(system); + + event_name = event_field->event_name; + event_len = strlen(event_name); + + for_each_field(expr, field, table) { + int len; + + field = &expr->field; + + if (field->event) + continue; + + raw = field->raw; + + /* + * The field could be: + * system.event.field... + * event.field... + * label.field... + * We check label first. + */ + + len = label_len; + if (label && !strncmp(raw, label, len) && + raw[len] == '.') { + /* Label matches and takes precedence */ + goto found; + } + + if (!strncmp(raw, system, system_len) && + raw[system_len] == '.') { + raw += system_len + 1; + /* Check the event portion next */ + } + + len = event_len; + if (strncmp(raw, event_name, len) || + raw[len] != '.') { + /* Does not match */ + continue; + } + found: + field->system = system; + field->event_name = event_name; + field->event = event; + field->field = raw + len + 1; + field->ftype = ftype; + + if (!strcmp(field->field, "TIMESTAMP")) + field->field = store_str(sb, TRACEFS_TIMESTAMP); + if (!strcmp(field->field, "TIMESTAMP_USECS")) + field->field = store_str(sb, TRACEFS_TIMESTAMP_USECS); + if (test_field_exists(tep, sb, expr)) + return -1; + } + + return 0; +} + +/* + * Called when there's a FROM but no JOIN(to), which means that the + * selections can be fields and not mention the event itself. + */ +static int update_fields(struct tep_handle *tep, + struct sql_table *table, + struct expr *expr) +{ + struct field *event_field = &expr->field; + struct sqlhist_bison *sb = table->sb; + struct tep_format_field *tfield; + struct tep_event *event; + struct field *field; + const char *p; + int len; + + /* First update fields with aliases an such and add event */ + update_vars(tep, table, expr); + + /* + * If event is not found, the creation of the synth will + * add a proper error, so return "success". + */ + if (!event_field->event) + return 0; + + event = event_field->event; + + for_each_field(expr, field, table) { + const char *field_name; + + field = &expr->field; + + if (field->event) + continue; + + field_name = field->raw; + + p = strchr(field_name, '.'); + if (p) { + len = p - field_name; + p = strndup(field_name, len); + if (!p) + return -1; + field_name = store_str(sb, p); + if (!field_name) + return -1; + free((char *)p); + } + + tfield = tep_find_any_field(event, field_name); + /* Let it error properly later */ + if (!tfield) + continue; + + field->system = event_field->system; + field->event_name = event_field->event_name; + field->event = event; + field->field = field_name; + } + + return 0; +} + +static int match_error(struct sqlhist_bison *sb, struct match *match, + struct field *lmatch, struct field *rmatch) +{ + struct field *lval = &match->lval->field; + struct field *rval = &match->rval->field; + struct field *field; + struct expr *expr; + + if (lval->system != lmatch->system || + lval->event != lmatch->event) { + expr = match->lval; + field = lval; + } else { + expr = match->rval; + field = rval; + } + + sb->line_no = expr->line; + sb->line_idx = expr->idx; + + parse_error(sb, field->raw, + "'%s' and '%s' must be a field for each event: '%s' and '%s'\n", + lval->raw, rval->raw, sb->table->to->field.raw, + sb->table->from->field.raw); + + return -1; +} + +static int test_match(struct sql_table *table, struct match *match) +{ + struct field *lval, *rval; + struct field *to, *from; + + if (!match->lval || !match->rval) + return -1; + + if (match->lval->type != EXPR_FIELD || match->rval->type != EXPR_FIELD) + return -1; + + to = &table->to->field; + from = &table->from->field; + + lval = &match->lval->field; + rval = &match->rval->field; + + /* + * Note, strings are stored in the string store, so all + * duplicate strings are the same value, and we can use + * normal "==" and "!=" instead of strcmp(). + * + * Either lval == to and rval == from + * or lval == from and rval == to. + */ + if ((lval->system != to->system) || + (lval->event != to->event)) { + if ((rval->system != to->system) || + (rval->event != to->event) || + (lval->system != from->system) || + (lval->event != from->event)) + return match_error(table->sb, match, from, to); + } else { + if ((rval->system != from->system) || + (rval->event != from->event) || + (lval->system != to->system) || + (lval->event != to->event)) + return match_error(table->sb, match, to, from); + } + return 0; +} + +static void assign_match(const char *system, const char *event, + struct match *match, + const char **start_match, const char **end_match) +{ + struct field *lval, *rval; + + lval = &match->lval->field; + rval = &match->rval->field; + + if (lval->system == system && + lval->event_name == event) { + *start_match = lval->field; + *end_match = rval->field; + } else { + *start_match = rval->field; + *end_match = lval->field; + } +} + +static int build_compare(struct tracefs_synth *synth, + const char *system, const char *event, + struct compare *compare) +{ + const char *start_field; + const char *end_field; + struct field *lval, *rval; + enum tracefs_synth_calc calc; + int ret; + + if (!compare->name) + return -1; + + lval = &compare->lval->field; + rval = &compare->rval->field; + + if (lval->system == system && + lval->event_name == event) { + start_field = lval->field; + end_field = rval->field; + calc = TRACEFS_SYNTH_DELTA_START; + } else { + start_field = rval->field; + end_field = lval->field; + calc = TRACEFS_SYNTH_DELTA_END; + } + + if (compare->type == COMPARE_ADD) + calc = TRACEFS_SYNTH_ADD; + + ret = tracefs_synth_add_compare_field(synth, start_field, + end_field, calc, + compare->name); + return ret; +} + +static int verify_filter_error(struct sqlhist_bison *sb, struct expr *expr, + const char *event) +{ + struct field *field = &expr->field; + + sb->line_no = expr->line; + sb->line_idx = expr->idx; + + parse_error(sb, field->raw, + "event '%s' can not be grouped or '||' together with '%s'\n" + "All filters between '&&' must be for the same event\n", + field->event, event); + return -1; +} + +static int do_verify_filter(struct sqlhist_bison *sb, struct filter *filter, + const char **system, const char **event, + enum field_type *ftype) +{ + int ret; + + if (filter->type == FILTER_OR || + filter->type == FILTER_AND) { + ret = do_verify_filter(sb, &filter->lval->filter, system, event, ftype); + if (ret) + return ret; + return do_verify_filter(sb, &filter->rval->filter, system, event, ftype); + } + if (filter->type == FILTER_GROUP || + filter->type == FILTER_NOT_GROUP) { + return do_verify_filter(sb, &filter->lval->filter, system, event, ftype); + } + + /* + * system and event will be NULL until we find the left most + * node. Then assign it, and compare on the way back up. + */ + if (!*system && !*event) { + *system = filter->lval->field.system; + *event = filter->lval->field.event_name; + *ftype = filter->lval->field.ftype; + return 0; + } + + if (filter->lval->field.system != *system || + filter->lval->field.event_name != *event) + return verify_filter_error(sb, filter->lval, *event); + + return 0; +} + +static int verify_filter(struct sqlhist_bison *sb, struct filter *filter, + const char **system, const char **event, + enum field_type *ftype) +{ + int ret; + + switch (filter->type) { + case FILTER_OR: + case FILTER_AND: + case FILTER_GROUP: + case FILTER_NOT_GROUP: + break; + default: + return do_verify_filter(sb, filter, system, event, ftype); + } + + ret = do_verify_filter(sb, &filter->lval->filter, system, event, ftype); + if (ret) + return ret; + + switch (filter->type) { + case FILTER_OR: + case FILTER_AND: + return do_verify_filter(sb, &filter->rval->filter, system, event, ftype); + default: + return 0; + } +} + +static int test_field_exists(struct tep_handle *tep, struct sqlhist_bison *sb, + struct expr *expr); + +static void filter_compare_error(struct tep_handle *tep, + struct sqlhist_bison *sb, + struct expr *expr) +{ + struct field *field = &expr->field; + + switch (errno) { + case ENODEV: + case EBADE: + break; + case EINVAL: + parse_error(sb, field->raw, "Invalid compare\n"); + break; + default: + parse_error(sb, field->raw, "System error?\n"); + return; + } + + /* ENODEV means that an event or field does not exist */ + if (errno == ENODEV) { + if (test_field_exists(tep, sb, expr)) + return; + if (test_field_exists(tep, sb, expr)) + return; + return; + } + + /* fields exist, but values are not compatible */ + sb->line_no = expr->line; + sb->line_idx = expr->idx; + + parse_error(sb, field->raw, + "Field '%s' is not compatible to be compared with the given value\n", + field->field); +} + +static void filter_error(struct tep_handle *tep, + struct sqlhist_bison *sb, struct expr *expr) +{ + struct filter *filter = &expr->filter; + + sb->line_no = expr->line; + sb->line_idx = expr->idx; + + switch (filter->type) { + case FILTER_NOT_GROUP: + case FILTER_GROUP: + case FILTER_OR: + case FILTER_AND: + break; + default: + filter_compare_error(tep, sb, filter->lval); + return; + } + + sb->line_no = expr->line; + sb->line_idx = expr->idx; + + parse_error(sb, "", "Problem with filter entry?\n"); +} + +static int build_filter(struct tep_handle *tep, struct sqlhist_bison *sb, + struct tracefs_synth *synth, + bool start, struct expr *expr, bool *started) +{ + int (*append_filter)(struct tracefs_synth *synth, + enum tracefs_filter type, + const char *field, + enum tracefs_compare compare, + const char *val); + struct filter *filter = &expr->filter; + enum tracefs_compare cmp; + const char *val; + int and_or = TRACEFS_FILTER_AND; + char num[64]; + int ret; + + if (start) + append_filter = tracefs_synth_append_start_filter; + else + append_filter = tracefs_synth_append_end_filter; + + if (started && *started) { + ret = append_filter(synth, and_or, NULL, 0, NULL); + ret = append_filter(synth, TRACEFS_FILTER_OPEN_PAREN, + NULL, 0, NULL); + } + + switch (filter->type) { + case FILTER_NOT_GROUP: + ret = append_filter(synth, TRACEFS_FILTER_NOT, + NULL, 0, NULL); + if (ret < 0) + goto out; + /* Fall through */ + case FILTER_GROUP: + ret = append_filter(synth, TRACEFS_FILTER_OPEN_PAREN, + NULL, 0, NULL); + if (ret < 0) + goto out; + ret = build_filter(tep, sb, synth, start, filter->lval, NULL); + if (ret < 0) + goto out; + ret = append_filter(synth, TRACEFS_FILTER_CLOSE_PAREN, + NULL, 0, NULL); + goto out; + + case FILTER_OR: + and_or = TRACEFS_FILTER_OR; + /* Fall through */ + case FILTER_AND: + ret = build_filter(tep, sb, synth, start, filter->lval, NULL); + if (ret < 0) + goto out; + ret = append_filter(synth, and_or, NULL, 0, NULL); + + if (ret) + goto out; + ret = build_filter(tep, sb, synth, start, filter->rval, NULL); + goto out; + default: + break; + } + + switch (filter->rval->type) { + case EXPR_NUMBER: + sprintf(num, "%ld", filter->rval->number); + val = num; + break; + case EXPR_STRING: + val = filter->rval->string; + break; + default: + break; + } + + switch (filter->type) { + case FILTER_EQ: cmp = TRACEFS_COMPARE_EQ; break; + case FILTER_NE: cmp = TRACEFS_COMPARE_NE; break; + case FILTER_LE: cmp = TRACEFS_COMPARE_LE; break; + case FILTER_LT: cmp = TRACEFS_COMPARE_LT; break; + case FILTER_GE: cmp = TRACEFS_COMPARE_GE; break; + case FILTER_GT: cmp = TRACEFS_COMPARE_GT; break; + case FILTER_BIN_AND: cmp = TRACEFS_COMPARE_AND; break; + case FILTER_STR_CMP: cmp = TRACEFS_COMPARE_RE; break; + default: + tracefs_warning("Error invalid filter type '%d'", filter->type); + return ERANGE; + } + + ret = append_filter(synth, TRACEFS_FILTER_COMPARE, + filter->lval->field.field, cmp, val); + + if (ret) + filter_error(tep, sb, expr); + out: + if (!ret && started) { + if (*started) + ret = append_filter(synth, TRACEFS_FILTER_CLOSE_PAREN, + NULL, 0, NULL); + *started = true; + } + return ret; +} + +static void *field_match_error(struct tep_handle *tep, struct sqlhist_bison *sb, + struct match *match) +{ + switch (errno) { + case ENODEV: + case EBADE: + break; + default: + /* System error */ + return NULL; + } + + /* ENODEV means that an event or field does not exist */ + if (errno == ENODEV) { + if (test_field_exists(tep, sb, match->lval)) + return NULL; + if (test_field_exists(tep, sb, match->rval)) + return NULL; + return NULL; + } + + /* fields exist, but values are not compatible */ + sb->line_no = match->lval->line; + sb->line_idx = match->lval->idx; + + parse_error(sb, match->lval->field.raw, + "Field '%s' is not compatible to match field '%s'\n", + match->lval->field.raw, match->rval->field.raw); + return NULL; +} + +static void *synth_init_error(struct tep_handle *tep, struct sql_table *table) +{ + struct sqlhist_bison *sb = table->sb; + struct match *match = table->matches; + + switch (errno) { + case ENODEV: + case EBADE: + break; + default: + /* System error */ + return NULL; + } + + /* ENODEV could mean that start or end events do not exist */ + if (errno == ENODEV) { + if (test_event_exists(tep, sb, table->from, NULL)) + return NULL; + if (test_event_exists(tep, sb, table->to, NULL)) + return NULL; + } + + return field_match_error(tep, sb, match); +} + +static void selection_error(struct tep_handle *tep, + struct sqlhist_bison *sb, struct expr *expr) +{ + /* We just care about event not existing */ + if (errno != ENODEV) + return; + + test_field_exists(tep, sb, expr); +} + +static void compare_error(struct tep_handle *tep, + struct sqlhist_bison *sb, struct expr *expr) +{ + struct compare *compare = &expr->compare; + + if (!compare->name) { + sb->line_no = expr->line; + sb->line_idx = expr->idx + strlen("no name"); + + parse_error(sb, "no name", + "Field calculations must be labeled 'AS name'\n"); + } + + switch (errno) { + case ENODEV: + case EBADE: + break; + default: + /* System error */ + return; + } + + /* ENODEV means that an event or field does not exist */ + if (errno == ENODEV) { + if (test_field_exists(tep, sb, compare->lval)) + return; + if (test_field_exists(tep, sb, compare->rval)) + return; + return; + } + + /* fields exist, but values are not compatible */ + sb->line_no = compare->lval->line; + sb->line_idx = compare->lval->idx; + + parse_error(sb, compare->lval->field.raw, + "'%s' is not compatible to compare with '%s'\n", + compare->lval->field.raw, compare->rval->field.raw); +} + +static void compare_no_to_error(struct sqlhist_bison *sb, struct expr *expr) +{ + struct compare *compare = &expr->compare; + + sb->line_no = compare->lval->line; + sb->line_idx = compare->lval->idx; + + parse_error(sb, compare->lval->field.raw, + "Simple SQL (without JOIN/ON) do not allow comparisons\n", + compare->lval->field.raw, compare->rval->field.raw); +} + +static void where_no_to_error(struct sqlhist_bison *sb, struct expr *expr, + const char *from_event, const char *event) +{ + while (expr) { + switch (expr->filter.type) { + case FILTER_OR: + case FILTER_AND: + case FILTER_GROUP: + case FILTER_NOT_GROUP: + expr = expr->filter.lval; + continue; + default: + break; + } + break; + } + sb->line_no = expr->filter.lval->line; + sb->line_idx = expr->filter.lval->idx; + + parse_error(sb, expr->filter.lval->field.raw, + "Event '%s' does not match FROM event '%s'\n", + event, from_event); +} + +static int verify_field_type(struct tep_handle *tep, + struct sqlhist_bison *sb, + struct expr *expr, int *cnt) +{ + struct field *field = &expr->field; + struct tep_event *event; + struct tep_format_field *tfield; + char *type; + int ret; + int i; + + if (!field->type) + return 0; + + sb->line_no = expr->line; + sb->line_idx = expr->idx; + + event = tep_find_event_by_name(tep, field->system, field->event_name); + if (!event) { + parse_error(sb, field->raw, + "Event '%s' not found\n", + field->event_name ? : "(null)"); + return -1; + } + + tfield = tep_find_any_field(event, field->field); + if (!tfield) { + parse_error(sb, field->raw, + "Field '%s' not part of event '%s'\n", + field->field ? : "(null)", field->event); + return -1; + } + + type = strdup(field->type); + if (!type) + return -1; + + if (!strcmp(type, TRACEFS_HIST_COUNTER) || + !strcmp(type, "_COUNTER_")) { + ret = HIST_COUNTER_TYPE; + if (tfield->flags & (TEP_FIELD_IS_STRING | TEP_FIELD_IS_ARRAY)) { + parse_error(sb, field->raw, + "'%s' is a string, and counters may only be used with numbers\n"); + ret = -1; + } + goto out; + } + + for (i = 0; type[i]; i++) + type[i] = tolower(type[i]); + + if (!strcmp(type, "hex")) { + if (tfield->flags & (TEP_FIELD_IS_STRING | TEP_FIELD_IS_ARRAY)) + goto fail_type; + ret = TRACEFS_HIST_KEY_HEX; + } else if (!strcmp(type, "sym")) { + if (tfield->flags & (TEP_FIELD_IS_STRING | TEP_FIELD_IS_ARRAY)) + goto fail_type; + ret = TRACEFS_HIST_KEY_SYM; + } else if (!strcmp(type, "sym-offset")) { + if (tfield->flags & (TEP_FIELD_IS_STRING | TEP_FIELD_IS_ARRAY)) + goto fail_type; + ret = TRACEFS_HIST_KEY_SYM_OFFSET; + } else if (!strcmp(type, "syscall")) { + if (tfield->flags & (TEP_FIELD_IS_STRING | TEP_FIELD_IS_ARRAY)) + goto fail_type; + ret = TRACEFS_HIST_KEY_SYSCALL; + } else if (!strcmp(type, "execname") || + !strcmp(type, "comm")) { + ret = TRACEFS_HIST_KEY_EXECNAME; + if (strcmp(field->field, "common_pid")) { + parse_error(sb, field->raw, + "'%s' is only allowed for common_pid\n", + type); + ret = -1; + } + } else if (!strcmp(type, "log") || + !strcmp(type, "log2")) { + if (tfield->flags & (TEP_FIELD_IS_STRING | TEP_FIELD_IS_ARRAY)) + goto fail_type; + ret = TRACEFS_HIST_KEY_LOG; + } else if (!strncmp(type, "buckets", 7)) { + if (type[7] != '=' || !isdigit(type[8])) { + parse_error(sb, field->raw, + "buckets type must have '=[number]' after it\n"); + ret = -1; + goto out; + } + *cnt = atoi(&type[8]); + if (tfield->flags & (TEP_FIELD_IS_STRING | TEP_FIELD_IS_ARRAY)) + goto fail_type; + ret = TRACEFS_HIST_KEY_BUCKETS; + } else { + parse_error(sb, field->raw, + "Cast of '%s' to unknown type '%s'\n", + field->raw, type); + ret = -1; + } + out: + free(type); + return ret; + fail_type: + parse_error(sb, field->raw, + "Field '%s' cast to '%s' but is of type %s\n", + field->field, type, tfield->flags & TEP_FIELD_IS_STRING ? + "string" : "array"); + free(type); + return -1; +} + +static struct tracefs_synth *build_synth(struct tep_handle *tep, + const char *name, + struct sql_table *table) +{ + struct tracefs_synth *synth; + struct field *field; + struct match *match; + struct expr *expr; + const char *start_system; + const char *start_event; + const char *end_system; + const char *end_event; + const char *start_match; + const char *end_match; + bool started_start = false; + bool started_end = false; + bool non_val = false; + int ret; + + if (!table->from) + return NULL; + + /* This could be a simple SQL statement to only build a histogram */ + if (!table->to) { + ret = update_fields(tep, table, table->from); + if (ret < 0) + return NULL; + + start_system = table->from->field.system; + start_event = table->from->field.event_name; + + synth = synth_init_from(tep, start_system, start_event); + if (!synth) + return synth_init_error(tep, table); + goto hist_only; + } + + ret = update_vars(tep, table, table->from); + if (ret < 0) + return NULL; + + ret = update_vars(tep, table, table->to); + if (ret < 0) + return NULL; + + start_system = table->from->field.system; + start_event = table->from->field.event_name; + + match = table->matches; + if (!match) + return NULL; + + ret = test_match(table, match); + if (ret < 0) + return NULL; + + end_system = table->to->field.system; + end_event = table->to->field.event_name; + + assign_match(start_system, start_event, match, + &start_match, &end_match); + + synth = tracefs_synth_alloc(tep, name, start_system, + start_event, end_system, end_event, + start_match, end_match, NULL); + if (!synth) + return synth_init_error(tep, table); + + for (match = match->next; match; match = match->next) { + ret = test_match(table, match); + if (ret < 0) + goto free; + + assign_match(start_system, start_event, match, + &start_match, &end_match); + + ret = tracefs_synth_add_match_field(synth, + start_match, + end_match, NULL); + if (ret < 0) { + field_match_error(tep, table->sb, match); + goto free; + } + } + + hist_only: + /* table->to may be NULL here */ + + for (expr = table->selections; expr; expr = expr->next) { + if (expr->type == EXPR_FIELD) { + ret = -1; + field = &expr->field; + if (field->ftype != FIELD_TO && + field->system == start_system && + field->event_name == start_event) { + int type; + int cnt = 0; + type = verify_field_type(tep, table->sb, expr, &cnt); + if (type < 0) + goto free; + if (type != HIST_COUNTER_TYPE) + non_val = true; + ret = synth_add_start_field(synth, + field->field, field->label, + type, cnt); + } else if (table->to) { + ret = tracefs_synth_add_end_field(synth, + field->field, field->label); + } + if (ret < 0) { + selection_error(tep, table->sb, expr); + goto free; + } + continue; + } + + if (!table->to) { + compare_no_to_error(table->sb, expr); + goto free; + } + + if (expr->type != EXPR_COMPARE) + goto free; + + ret = build_compare(synth, start_system, end_system, + &expr->compare); + if (ret < 0) { + compare_error(tep, table->sb, expr); + goto free; + } + } + + if (!non_val && !table->to) { + table->sb->line_no = 0; + table->sb->line_idx = 10; + parse_error(table->sb, "CAST", + "Not all SELECT items can be of type _COUNTER_\n"); + goto free; + } + + for (expr = table->where; expr; expr = expr->next) { + const char *filter_system = NULL; + const char *filter_event = NULL; + enum field_type ftype = FIELD_NONE; + bool *started; + bool start; + + ret = verify_filter(table->sb, &expr->filter, &filter_system, + &filter_event, &ftype); + if (ret < 0) + goto free; + + start = filter_system == start_system && + filter_event == start_event && + ftype != FIELD_TO; + + if (start) + started = &started_start; + else if (!table->to) { + where_no_to_error(table->sb, expr, start_event, + filter_event); + goto free; + } else + started = &started_end; + + ret = build_filter(tep, table->sb, synth, start, expr, started); + if (ret < 0) + goto free; + } + + return synth; + free: + tracefs_synth_free(synth); + return NULL; +} + +static void free_sql_table(struct sql_table *table) +{ + struct match *match; + struct expr *expr; + + if (!table) + return; + + while ((expr = table->exprs)) { + table->exprs = expr->free_list; + free(expr); + } + + while ((match = table->matches)) { + table->matches = match->next; + free(match); + } + + free(table); +} + +static void free_str_hash(struct str_hash **hash) +{ + struct str_hash *item; + int i; + + for (i = 0; i < 1 << HASH_BITS; i++) { + while ((item = hash[i])) { + hash[i] = item->next; + free(item->str); + free(item); + } + } +} + +static void free_sb(struct sqlhist_bison *sb) +{ + free_sql_table(sb->table); + free_str_hash(sb->str_hash); + free(sb->parse_error_str); +} + +struct tracefs_synth *tracefs_sql(struct tep_handle *tep, const char *name, + const char *sql_buffer, char **err) +{ + struct tracefs_synth *synth = NULL; + struct sqlhist_bison sb; + int ret; + + if (!tep || !sql_buffer) { + errno = EINVAL; + return NULL; + } + + memset(&sb, 0, sizeof(sb)); + + sb.buffer = sql_buffer; + sb.buffer_size = strlen(sql_buffer); + sb.buffer_idx = 0; + + ret = yylex_init_extra(&sb, &sb.scanner); + if (ret < 0) { + yylex_destroy(sb.scanner); + return NULL; + } + + ret = tracefs_parse(&sb); + yylex_destroy(sb.scanner); + + if (ret) + goto free; + + synth = build_synth(tep, name, sb.table); + + free: + if (!synth) { + if (sb.parse_error_str && err) { + *err = sb.parse_error_str; + sb.parse_error_str = NULL; + } + } + free_sb(&sb); + return synth; +} diff --git a/src/tracefs-tools.c b/src/tracefs-tools.c new file mode 100644 index 0000000..8e7b46d --- /dev/null +++ b/src/tracefs-tools.c @@ -0,0 +1,1273 @@ +// SPDX-License-Identifier: LGPL-2.1 +/* + * Copyright (C) 2008, 2009, 2010 Red Hat Inc, Steven Rostedt + * + * Updates: + * Copyright (C) 2021, VMware, Tzvetomir Stoyanov + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tracefs.h" +#include "tracefs-local.h" + +__hidden pthread_mutex_t toplevel_lock = PTHREAD_MUTEX_INITIALIZER; + +#define TRACE_CTRL "tracing_on" +#define TRACE_FILTER "set_ftrace_filter" +#define TRACE_NOTRACE "set_ftrace_notrace" +#define TRACE_FILTER_LIST "available_filter_functions" +#define CUR_TRACER "current_tracer" + +#define TRACERS \ + C(NOP, "nop"), \ + C(CUSTOM, "CUSTOM"), \ + C(FUNCTION, "function"), \ + C(FUNCTION_GRAPH, "function_graph"), \ + C(IRQSOFF, "irqsoff"), \ + C(PREEMPTOFF, "preemptoff"), \ + C(PREEMPTIRQSOFF, "preemptirqsoff"), \ + C(WAKEUP, "wakeup"), \ + C(WAKEUP_RT, "wakeup_rt"), \ + C(WAKEUP_DL, "wakeup_dl"), \ + C(MMIOTRACE, "mmiotrace"), \ + C(HWLAT, "hwlat"), \ + C(BRANCH, "branch"), \ + C(BLOCK, "block") + +#undef C +#define C(a, b) b +const char *tracers[] = { TRACERS }; + +#undef C +#define C(a, b) TRACEFS_TRACER_##a +const int tracer_enums[] = { TRACERS }; + +/* File descriptor for Top level set_ftrace_filter */ +static int ftrace_filter_fd = -1; +static int ftrace_notrace_fd = -1; + +static const char * const options_map[] = { + "unknown", + "annotate", + "bin", + "blk_cgname", + "blk_cgroup", + "blk_classic", + "block", + "context-info", + "disable_on_free", + "display-graph", + "event-fork", + "funcgraph-abstime", + "funcgraph-cpu", + "funcgraph-duration", + "funcgraph-irqs", + "funcgraph-overhead", + "funcgraph-overrun", + "funcgraph-proc", + "funcgraph-tail", + "func_stack_trace", + "function-fork", + "function-trace", + "graph-time", + "hex", + "irq-info", + "latency-format", + "markers", + "overwrite", + "pause-on-trace", + "printk-msg-only", + "print-parent", + "raw", + "record-cmd", + "record-tgid", + "sleep-time", + "stacktrace", + "sym-addr", + "sym-offset", + "sym-userobj", + "trace_printk", + "userstacktrace", + "verbose" }; + +static int trace_on_off(int fd, bool on) +{ + const char *val = on ? "1" : "0"; + int ret; + + ret = write(fd, val, 1); + if (ret == 1) + return 0; + + return -1; +} + +static int trace_on_off_file(struct tracefs_instance *instance, bool on) +{ + int ret; + int fd; + + fd = tracefs_instance_file_open(instance, TRACE_CTRL, O_WRONLY); + if (fd < 0) + return -1; + ret = trace_on_off(fd, on); + close(fd); + + return ret; +} + +/** + * tracefs_trace_is_on - Check if writing traces to the ring buffer is enabled + * @instance: ftrace instance, can be NULL for the top instance + * + * Returns -1 in case of an error, 0 if tracing is disable or 1 if tracing + * is enabled. + */ +int tracefs_trace_is_on(struct tracefs_instance *instance) +{ + long long res; + + if (tracefs_instance_file_read_number(instance, TRACE_CTRL, &res) == 0) + return (int)res; + + return -1; +} + +/** + * tracefs_trace_on - Enable writing traces to the ring buffer of the given instance + * @instance: ftrace instance, can be NULL for the top instance + * + * Returns -1 in case of an error or 0 otherwise + */ +int tracefs_trace_on(struct tracefs_instance *instance) +{ + return trace_on_off_file(instance, true); +} + +/** + * tracefs_trace_off - Disable writing traces to the ring buffer of the given instance + * @instance: ftrace instance, can be NULL for the top instance + * + * Returns -1 in case of an error or 0 otherwise + */ +int tracefs_trace_off(struct tracefs_instance *instance) +{ + return trace_on_off_file(instance, false); +} + +/** + * tracefs_trace_on_fd - Enable writing traces to the ring buffer + * @fd: File descriptor to ftrace tracing_on file, previously opened + * with tracefs_trace_on_get_fd() + * + * Returns -1 in case of an error or 0 otherwise + */ +int tracefs_trace_on_fd(int fd) +{ + if (fd < 0) + return -1; + return trace_on_off(fd, true); +} + +/** + * tracefs_trace_off_fd - Disable writing traces to the ring buffer + * @fd: File descriptor to ftrace tracing_on file, previously opened + * with tracefs_trace_on_get_fd() + * + * Returns -1 in case of an error or 0 otherwise + */ +int tracefs_trace_off_fd(int fd) +{ + if (fd < 0) + return -1; + return trace_on_off(fd, false); +} + +/** + * tracefs_option_name - Get trace option name from id + * @id: trace option id + * + * Returns string with option name, or "unknown" in case of not known option id. + * The returned string must *not* be freed. + */ +const char *tracefs_option_name(enum tracefs_option_id id) +{ + /* Make sure options map contains all the options */ + BUILD_BUG_ON(ARRAY_SIZE(options_map) != TRACEFS_OPTION_MAX); + + if (id < TRACEFS_OPTION_MAX) + return options_map[id]; + + return options_map[0]; +} + +/** + * tracefs_option_id - Get trace option ID from name + * @name: trace option name + * + * Returns trace option ID or TRACEFS_OPTION_INVALID in case of an error or + * unknown option name. + */ +enum tracefs_option_id tracefs_option_id(const char *name) +{ + int i; + + if (!name) + return TRACEFS_OPTION_INVALID; + + for (i = 0; i < TRACEFS_OPTION_MAX; i++) { + if (strlen(name) == strlen(options_map[i]) && + !strcmp(options_map[i], name)) + return i; + } + + return TRACEFS_OPTION_INVALID; +} + +const static struct tracefs_options_mask * +trace_get_options(struct tracefs_instance *instance, bool enabled) +{ + pthread_mutex_t *lock = trace_get_lock(instance); + struct tracefs_options_mask *bitmask; + enum tracefs_option_id id; + unsigned long long set; + char file[PATH_MAX]; + struct stat st; + long long val; + char *path; + int ret; + + bitmask = enabled ? enabled_opts_mask(instance) : + supported_opts_mask(instance); + + for (id = 1; id < TRACEFS_OPTION_MAX; id++) { + snprintf(file, PATH_MAX, "options/%s", options_map[id]); + path = tracefs_instance_get_file(instance, file); + if (!path) + return NULL; + + set = 1; + ret = stat(path, &st); + if (ret < 0 || !S_ISREG(st.st_mode)) { + set = 0; + } else if (enabled) { + ret = tracefs_instance_file_read_number(instance, file, &val); + if (ret != 0 || val != 1) + set = 0; + } + + pthread_mutex_lock(lock); + bitmask->mask = (bitmask->mask & ~(1ULL << (id - 1))) | (set << (id - 1)); + pthread_mutex_unlock(lock); + + tracefs_put_tracing_file(path); + } + + + return bitmask; +} + +/** + * tracefs_options_get_supported - Get all supported trace options in given instance + * @instance: ftrace instance, can be NULL for the top instance + * + * Returns bitmask structure with all trace options, supported in given instance, + * or NULL in case of an error. + */ +const struct tracefs_options_mask * +tracefs_options_get_supported(struct tracefs_instance *instance) +{ + return trace_get_options(instance, false); +} + +/** + * tracefs_options_get_enabled - Get all currently enabled trace options in given instance + * @instance: ftrace instance, can be NULL for the top instance + * + * Returns bitmask structure with all trace options, enabled in given instance, + * or NULL in case of an error. + */ +const struct tracefs_options_mask * +tracefs_options_get_enabled(struct tracefs_instance *instance) +{ + return trace_get_options(instance, true); +} + +static int trace_config_option(struct tracefs_instance *instance, + enum tracefs_option_id id, bool set) +{ + const char *set_str = set ? "1" : "0"; + char file[PATH_MAX]; + const char *name; + + name = tracefs_option_name(id); + if (!name) + return -1; + + snprintf(file, PATH_MAX, "options/%s", name); + if (strlen(set_str) != tracefs_instance_file_write(instance, file, set_str)) + return -1; + return 0; +} + +/** + * tracefs_option_enable - Enable trace option + * @instance: ftrace instance, can be NULL for the top instance + * @id: trace option id + * + * Returns -1 in case of an error or 0 otherwise + */ +int tracefs_option_enable(struct tracefs_instance *instance, enum tracefs_option_id id) +{ + return trace_config_option(instance, id, true); +} + +/** + * tracefs_option_disable - Disable trace option + * @instance: ftrace instance, can be NULL for the top instance + * @id: trace option id + * + * Returns -1 in case of an error or 0 otherwise + */ +int tracefs_option_disable(struct tracefs_instance *instance, enum tracefs_option_id id) +{ + return trace_config_option(instance, id, false); +} + +/** + * tracefs_option_is_supported - Check if an option is supported + * @instance: ftrace instance, can be NULL for the top instance + * @id: trace option id + * + * Returns true if an option with given id is supported by the system, false if + * it is not supported. + */ +bool tracefs_option_is_supported(struct tracefs_instance *instance, enum tracefs_option_id id) +{ + const char *name = tracefs_option_name(id); + char file[PATH_MAX]; + + if (!name) + return false; + snprintf(file, PATH_MAX, "options/%s", name); + return tracefs_file_exists(instance, file); +} + +/** + * tracefs_option_is_enabled - Check if an option is enabled in given instance + * @instance: ftrace instance, can be NULL for the top instance + * @id: trace option id + * + * Returns true if an option with given id is enabled in the given instance, + * false if it is not enabled. + */ +bool tracefs_option_is_enabled(struct tracefs_instance *instance, enum tracefs_option_id id) +{ + const char *name = tracefs_option_name(id); + char file[PATH_MAX]; + long long res; + + if (!name) + return false; + snprintf(file, PATH_MAX, "options/%s", name); + if (!tracefs_instance_file_read_number(instance, file, &res) && res) + return true; + + return false; +} + +/** + * tracefs_option_mask_is_set - Check if given option is set in the bitmask + * @options: Options bitmask + * @id: trace option id + * + * Returns true if an option with given id is set in the bitmask, + * false if it is not set. + */ +bool tracefs_option_mask_is_set(const struct tracefs_options_mask *options, + enum tracefs_option_id id) +{ + if (id > TRACEFS_OPTION_INVALID) + return options->mask & (1ULL << (id - 1)); + return false; +} + +struct func_list { + struct func_list *next; + char *func; + unsigned int start; + unsigned int end; +}; + +struct func_filter { + const char *filter; + regex_t re; + bool set; + bool is_regex; +}; + +static bool is_regex(const char *str) +{ + int i; + + for (i = 0; str[i]; i++) { + switch (str[i]) { + case 'a' ... 'z': + case 'A'...'Z': + case '_': + case '0'...'9': + case '*': + case '.': + /* Dots can be part of a function name */ + case '?': + continue; + default: + return true; + } + } + return false; +} + +static char *update_regex(const char *reg) +{ + int len = strlen(reg); + char *str; + + if (reg[0] == '^' && reg[len - 1] == '$') + return strdup(reg); + + str = malloc(len + 3); + if (reg[0] == '^') { + strcpy(str, reg); + } else { + str[0] = '^'; + strcpy(str + 1, reg); + len++; /* add ^ */ + } + if (str[len - 1] != '$') + str[len++]= '$'; + str[len] = '\0'; + return str; +} + +/* + * Convert a glob into a regular expression. + */ +static char *make_regex(const char *glob) +{ + char *str; + int cnt = 0; + int i, j; + + for (i = 0; glob[i]; i++) { + if (glob[i] == '*'|| glob[i] == '.') + cnt++; + } + + /* '^' + ('*'->'.*' or '.' -> '\.') + '$' + '\0' */ + str = malloc(i + cnt + 3); + if (!str) + return NULL; + + str[0] = '^'; + for (i = 0, j = 1; glob[i]; i++, j++) { + if (glob[i] == '*') + str[j++] = '.'; + /* Dots can be part of a function name */ + if (glob[i] == '.') + str[j++] = '\\'; + str[j] = glob[i]; + } + str[j++] = '$'; + str[j] = '\0'; + return str; +} + +static bool match(const char *str, struct func_filter *func_filter) +{ + return regexec(&func_filter->re, str, 0, NULL, 0) == 0; +} + +/* + * Return 0 on success, -1 error writing, 1 on other errors. + */ +static int write_filter(int fd, const char *filter, const char *module) +{ + char *each_str = NULL; + int write_size; + int size; + + if (module) + write_size = asprintf(&each_str, "%s:mod:%s ", filter, module); + else + write_size = asprintf(&each_str, "%s ", filter); + + if (write_size < 0) + return 1; + + size = write(fd, each_str, write_size); + free(each_str); + + /* compare written bytes*/ + if (size < write_size) + return -1; + + return 0; +} + +static int add_func(struct func_list ***next_func_ptr, unsigned int index) +{ + struct func_list **next_func = *next_func_ptr; + struct func_list *func_list = *next_func; + + if (!func_list) { + func_list = calloc(1, sizeof(*func_list)); + if (!func_list) + return -1; + func_list->start = index; + func_list->end = index; + *next_func = func_list; + return 0; + } + + if (index == func_list->end + 1) { + func_list->end = index; + return 0; + } + *next_func_ptr = &func_list->next; + return add_func(next_func_ptr, index); +} + +static int add_func_str(struct func_list ***next_func_ptr, const char *func) +{ + struct func_list **next_func = *next_func_ptr; + struct func_list *func_list = *next_func; + + if (!func_list) { + func_list = calloc(1, sizeof(*func_list)); + if (!func_list) + return -1; + func_list->func = strdup(func); + if (!func_list->func) + return -1; + *next_func = func_list; + return 0; + } + *next_func_ptr = &func_list->next; + return add_func_str(next_func_ptr, func); +} + +static void free_func_list(struct func_list *func_list) +{ + struct func_list *f; + + while (func_list) { + f = func_list; + func_list = f->next; + free(f->func); + free(f); + } +} + +enum match_type { + FILTER_CHECK = (1 << 0), + FILTER_WRITE = (1 << 1), + FILTER_FUTURE = (1 << 2), + SAVE_STRING = (1 << 2), +}; + +static int match_filters(int fd, struct func_filter *func_filter, + const char *module, struct func_list **func_list, + int flags) +{ + enum match_type type = flags & (FILTER_CHECK | FILTER_WRITE); + bool save_str = flags & SAVE_STRING; + bool future = flags & FILTER_FUTURE; + bool mod_match = false; + char *line = NULL; + size_t size = 0; + char *path; + FILE *fp; + int index = 0; + int ret = 1; + int mlen; + + path = tracefs_get_tracing_file(TRACE_FILTER_LIST); + if (!path) + return 1; + + fp = fopen(path, "r"); + tracefs_put_tracing_file(path); + + if (!fp) + return 1; + + if (module) + mlen = strlen(module); + + while (getline(&line, &size, fp) >= 0) { + char *saveptr = NULL; + char *tok, *mtok; + int len = strlen(line); + + if (line[len - 1] == '\n') + line[len - 1] = '\0'; + tok = strtok_r(line, " ", &saveptr); + if (!tok) + goto next; + + index++; + + if (module) { + mtok = strtok_r(NULL, " ", &saveptr); + if (!mtok) + goto next; + if ((strncmp(mtok + 1, module, mlen) != 0) || + (mtok[mlen + 1] != ']')) + goto next; + if (future) + mod_match = true; + } + switch (type) { + case FILTER_CHECK: + if (match(tok, func_filter)) { + func_filter->set = true; + if (save_str) + ret = add_func_str(&func_list, tok); + else + ret = add_func(&func_list, index); + if (ret) + goto out; + } + break; + case FILTER_WRITE: + /* Writes only have one filter */ + if (match(tok, func_filter)) { + ret = write_filter(fd, tok, module); + if (ret) + goto out; + } + break; + default: + /* Should never happen */ + ret = -1; + goto out; + + } + next: + free(line); + line = NULL; + len = 0; + } + out: + free(line); + fclose(fp); + + /* If there was no matches and future was set, this is a success */ + if (future && !mod_match) + ret = 0; + + return ret; +} + +static int check_available_filters(struct func_filter *func_filter, + const char *module, + struct func_list **func_list, + bool future) +{ + int flags = FILTER_CHECK | (future ? FILTER_FUTURE : 0); + + return match_filters(-1, func_filter, module, func_list, flags); +} + + +static int list_available_filters(struct func_filter *func_filter, + const char *module, + struct func_list **func_list) +{ + int flags = FILTER_CHECK | SAVE_STRING; + + return match_filters(-1, func_filter, module, func_list, flags); +} + +static int set_regex_filter(int fd, struct func_filter *func_filter, + const char *module) +{ + return match_filters(fd, func_filter, module, NULL, FILTER_WRITE); +} + +static int controlled_write(int fd, struct func_filter *func_filter, + const char *module) +{ + const char *filter = func_filter->filter; + int ret; + + if (func_filter->is_regex) + ret = set_regex_filter(fd, func_filter, module); + else + ret = write_filter(fd, filter, module); + + return ret; +} + +static int init_func_filter(struct func_filter *func_filter, const char *filter) +{ + char *str; + int ret; + + if (!(func_filter->is_regex = is_regex(filter))) + str = make_regex(filter); + else + str = update_regex(filter); + + if (!str) + return -1; + + ret = regcomp(&func_filter->re, str, REG_ICASE|REG_NOSUB); + free(str); + + if (ret < 0) + return -1; + + func_filter->filter = filter; + return 0; +} + +static int write_number(int fd, unsigned int start, unsigned int end) +{ + char buf[64]; + unsigned int i; + int n, ret; + + for (i = start; i <= end; i++) { + n = snprintf(buf, 64, "%d ", i); + ret = write(fd, buf, n); + if (ret < 0) + return ret; + } + + return 0; +} + +/* + * This will try to write the first number, if that fails, it + * will assume that it is not supported and return 1. + * If the first write succeeds, but a following write fails, then + * the kernel does support this, but something else went wrong, + * in this case, return -1. + */ +static int write_func_list(int fd, struct func_list *list) +{ + int ret; + + if (!list) + return 0; + + ret = write_number(fd, list->start, list->end); + if (ret) + return 1; // try a different way + list = list->next; + while (list) { + ret = write_number(fd, list->start, list->end); + if (ret) + return -1; + list = list->next; + } + return 0; +} + +static int update_filter(const char *filter_path, int *fd, + struct tracefs_instance *instance, const char *filter, + const char *module, unsigned int flags) +{ + struct func_filter func_filter; + struct func_list *func_list = NULL; + bool reset = flags & TRACEFS_FL_RESET; + bool cont = flags & TRACEFS_FL_CONTINUE; + bool future = flags & TRACEFS_FL_FUTURE; + pthread_mutex_t *lock = trace_get_lock(instance); + int open_flags; + int ret = 1; + + /* future flag is only applicable to modules */ + if (future && !module) { + errno = EINVAL; + return 1; + } + + pthread_mutex_lock(lock); + + /* RESET is only allowed if the file is not opened yet */ + if (reset && *fd >= 0) { + errno = EBUSY; + ret = -1; + goto out; + } + + /* + * Set EINVAL on no matching filter. But errno may still be modified + * on another type of failure (allocation or opening a file). + */ + errno = EINVAL; + + /* module set with NULL filter means to enable all functions in a module */ + if (module && !filter) + filter = "*"; + + if (!filter) { + /* OK to call without filters if this is closing the opened file */ + if (!cont && *fd >= 0) { + errno = 0; + ret = 0; + close(*fd); + *fd = -1; + } + /* Also OK to call if reset flag is set */ + if (reset) + goto open_file; + + goto out; + } + + if (init_func_filter(&func_filter, filter) < 0) + goto out; + + ret = check_available_filters(&func_filter, module, &func_list, future); + if (ret) + goto out_free; + + open_file: + ret = 1; + + open_flags = reset ? O_TRUNC : O_APPEND; + + if (*fd < 0) + *fd = open(filter_path, O_WRONLY | O_CLOEXEC | open_flags); + if (*fd < 0) + goto out_free; + + errno = 0; + ret = 0; + + if (filter) { + /* + * If future is set, and no functions were found, then + * set it directly. + */ + if (func_list) + ret = write_func_list(*fd, func_list); + else + ret = 1; + if (ret > 0) + ret = controlled_write(*fd, &func_filter, module); + } + + if (!cont) { + close(*fd); + *fd = -1; + } + + out_free: + if (filter) + regfree(&func_filter.re); + free_func_list(func_list); + out: + pthread_mutex_unlock(lock); + + return ret; +} + +/** + * tracefs_function_filter - filter the functions that are traced + * @instance: ftrace instance, can be NULL for top tracing instance. + * @filter: The filter to filter what functions are to be traced + * @module: Module to be traced or NULL if all functions are to be examined. + * @flags: flags on modifying the filter file + * + * @filter may be a full function name, a glob, or a regex. It will be + * considered a regex, if there's any characters that are not normally in + * function names or "*" or "?" for a glob. + * + * @flags: + * TRACEFS_FL_RESET - will clear the functions in the filter file + * before applying the @filter. This will error with -1 + * and errno of EBUSY if this flag is set and a previous + * call had the same instance and TRACEFS_FL_CONTINUE set. + * TRACEFS_FL_CONTINUE - will keep the filter file open on return. + * The filter is updated on closing of the filter file. + * With this flag set, the file is not closed, and more filters + * may be added before they take effect. The last call of this + * function must be called without this flag for the filter + * to take effect. + * TRACEFS_FL_FUTURE - only applicable if "module" is set. If no match + * is made, and the module is not yet loaded, it will still attempt + * to write the filter plus the module; ":mod:" + * to the filter file. Starting with Linux kernels 4.13, it is possible + * to load the filter file with module functions for a module that + * is not yet loaded, and when the module is loaded, it will then + * activate the module. + * + * Returns 0 on success, 1 if there was an error but the filtering has not + * yet started, -1 if there was an error but the filtering has started. + * If -1 is returned and TRACEFS_FL_CONTINUE was set, then this function + * needs to be called again without the TRACEFS_FL_CONTINUE flag to commit + * the changes and close the filter file. + */ +int tracefs_function_filter(struct tracefs_instance *instance, const char *filter, + const char *module, unsigned int flags) +{ + char *filter_path; + int *fd; + int ret; + + filter_path = tracefs_instance_get_file(instance, TRACE_FILTER); + if (!filter_path) + return -1; + + if (instance) + fd = &instance->ftrace_filter_fd; + else + fd = &ftrace_filter_fd; + + ret = update_filter(filter_path, fd, instance, filter, module, flags); + tracefs_put_tracing_file(filter_path); + return ret; +} + +/** + * tracefs_function_notrace - filter the functions that are not to be traced + * @instance: ftrace instance, can be NULL for top tracing instance. + * @filter: The filter to filter what functions are not to be traced + * @module: Module to be traced or NULL if all functions are to be examined. + * @flags: flags on modifying the filter file + * + * See tracefs_function_filter, as this has the same functionality but + * for adding to the "notrace" filter. + */ +int tracefs_function_notrace(struct tracefs_instance *instance, const char *filter, + const char *module, unsigned int flags) +{ + char *filter_path; + int *fd; + int ret; + + filter_path = tracefs_instance_get_file(instance, TRACE_NOTRACE); + if (!filter_path) + return -1; + + if (instance) + fd = &instance->ftrace_notrace_fd; + else + fd = &ftrace_notrace_fd; + + ret = update_filter(filter_path, fd, instance, filter, module, flags); + tracefs_put_tracing_file(filter_path); + return ret; +} + +int write_tracer(int fd, const char *tracer) +{ + int ret; + + ret = write(fd, tracer, strlen(tracer)); + if (ret < strlen(tracer)) + return -1; + return ret; +} + +/** + * tracefs_set_tracer - function to set the tracer + * @instance: ftrace instance, can be NULL for top tracing instance + * @tracer: The tracer enum that defines the tracer to be set + * @t: A tracer name if TRACEFS_TRACER_CUSTOM is passed in for @tracer + * + * Set the tracer for the instance based on the tracefs_tracer enums. + * If the user wishes to enable a tracer that is not defined by + * the enum (new or custom kernel), the tracer can be set to + * TRACEFS_TRACER_CUSTOM, and pass in a const char * name for + * the tracer to set. + * + * Returns 0 on succes, negative on error. + */ + +int tracefs_tracer_set(struct tracefs_instance *instance, + enum tracefs_tracers tracer, ...) +{ + char *tracer_path = NULL; + const char *t = NULL; + int ret = -1; + int fd = -1; + int i; + + if (tracer < 0 || tracer > ARRAY_SIZE(tracers)) { + errno = EINVAL; + return -1; + } + + tracer_path = tracefs_instance_get_file(instance, CUR_TRACER); + if (!tracer_path) + return -1; + + fd = open(tracer_path, O_WRONLY); + if (fd < 0) { + errno = ENOENT; + goto out; + } + + if (tracer == TRACEFS_TRACER_CUSTOM) { + va_list ap; + + va_start(ap, tracer); + t = va_arg(ap, const char *); + va_end(ap); + } else if (tracer == tracer_enums[tracer]) { + t = tracers[tracer]; + } else { + for (i = 0; i < ARRAY_SIZE(tracer_enums); i++) { + if (tracer == tracer_enums[i]) { + t = tracers[i]; + break; + } + } + } + if (!t) { + errno = EINVAL; + goto out; + } + ret = write_tracer(fd, t); + /* + * If the tracer does not exist, EINVAL is returned, + * but let the user know this as ENODEV. + */ + if (ret < 0 && errno == EINVAL) + errno = ENODEV; + out: + tracefs_put_tracing_file(tracer_path); + close(fd); + return ret > 0 ? 0 : ret; +} + +int tracefs_tracer_clear(struct tracefs_instance *instance) +{ + return tracefs_tracer_set(instance, TRACEFS_TRACER_NOP); +} + +static bool splice_safe(int fd, int pfd) +{ + int ret; + + errno = 0; + ret = splice(pfd, NULL, fd, NULL, + 10, SPLICE_F_NONBLOCK | SPLICE_F_MOVE); + + return !ret || (ret < 0 && errno == EAGAIN); +} + +static ssize_t read_trace_pipe(bool *keep_going, int in_fd, int out_fd) +{ + char buf[BUFSIZ]; + ssize_t bread = 0; + int ret; + + while (*(volatile bool *)keep_going) { + int r; + ret = read(in_fd, buf, BUFSIZ); + if (ret <= 0) + break; + r = ret; + ret = write(out_fd, buf, r); + if (ret < 0) + break; + bread += ret; + /* + * If the write does a partial write, then + * the iteration should stop. This can happen if + * the destination file system ran out of disk space. + * Sure, it probably lost a little from the read + * but there's not much more that can be + * done. Just return what was transferred. + */ + if (ret < r) + break; + } + + if (ret < 0 && (errno == EAGAIN || errno == EINTR)) + ret = 0; + + return ret < 0 ? ret : bread; +} + +static bool top_pipe_keep_going; + +/** + * tracefs_trace_pipe_stream - redirect the stream of trace data to an output + * file. The "splice" system call is used to moves the data without copying + * between kernel address space and user address space. The user can interrupt + * the streaming of the data by pressing Ctrl-c. + * @fd: The file descriptor of the output file. + * @instance: ftrace instance, can be NULL for top tracing instance. + * @flags: flags for opening the trace_pipe file. + * + * Returns -1 in case of an error or number of bytes transferred otherwise. + */ +ssize_t tracefs_trace_pipe_stream(int fd, struct tracefs_instance *instance, + int flags) +{ + bool *keep_going = instance ? &instance->pipe_keep_going : + &top_pipe_keep_going; + const char *file = "trace_pipe"; + int brass[2], in_fd, ret = -1; + int sflags = flags & O_NONBLOCK ? SPLICE_F_NONBLOCK : 0; + off_t data_size; + ssize_t bread = 0; + + (*(volatile bool *)keep_going) = true; + + in_fd = tracefs_instance_file_open(instance, file, O_RDONLY | flags); + if (in_fd < 0) { + tracefs_warning("Failed to open 'trace_pipe'."); + return ret; + } + + if(pipe(brass) < 0) { + tracefs_warning("Failed to open pipe."); + goto close_file; + } + + data_size = fcntl(brass[0], F_GETPIPE_SZ); + if (data_size <= 0) { + tracefs_warning("Failed to open pipe (size=0)."); + goto close_all; + } + + /* Test if the output is splice safe */ + if (!splice_safe(fd, brass[0])) { + bread = read_trace_pipe(keep_going, in_fd, fd); + ret = 0; /* Force return of bread */ + goto close_all; + } + + errno = 0; + + while (*(volatile bool *)keep_going) { + ret = splice(in_fd, NULL, + brass[1], NULL, + data_size, sflags); + if (ret < 0) + break; + + ret = splice(brass[0], NULL, + fd, NULL, + data_size, sflags); + if (ret < 0) + break; + bread += ret; + } + + /* + * Do not return error in the case when the "splice" system call + * was interrupted by the user (pressing Ctrl-c). + * Or if NONBLOCK was specified. + */ + if (!keep_going || errno == EAGAIN || errno == EINTR) + ret = 0; + + close_all: + close(brass[0]); + close(brass[1]); + close_file: + close(in_fd); + + return ret ? ret : bread; +} + +/** + * tracefs_trace_pipe_print - redirect the stream of trace data to "stdout". + * The "splice" system call is used to moves the data without copying + * between kernel address space and user address space. + * @instance: ftrace instance, can be NULL for top tracing instance. + * @flags: flags for opening the trace_pipe file. + * + * Returns -1 in case of an error or number of bytes transferred otherwise. + */ + +ssize_t tracefs_trace_pipe_print(struct tracefs_instance *instance, int flags) +{ + return tracefs_trace_pipe_stream(STDOUT_FILENO, instance, flags); +} + +/** + * tracefs_trace_pipe_stop - stop the streaming of trace data. + * @instance: ftrace instance, can be NULL for top tracing instance. + */ +void tracefs_trace_pipe_stop(struct tracefs_instance *instance) +{ + if (instance) + instance->pipe_keep_going = false; + else + top_pipe_keep_going = false; +} + +/** + * tracefs_filter_functions - return a list of available functons that can be filtered + * @filter: The filter to filter what functions to list (can be NULL for all) + * @module: Module to be traced or NULL if all functions are to be examined. + * @list: The list to return the list from (freed by tracefs_list_free() on success) + * + * Returns a list of function names that match @filter and @module. If both + * @filter and @module is NULL, then all available functions that can be filtered + * will be returned. (Note, there can be duplicates, if there are more than + * one function with the same name. + * + * On success, zero is returned, and @list contains a list of functions that were + * found, and must be freed with tracefs_list_free(). + * On failure, a negative number is returned, and @list is ignored. + */ +int tracefs_filter_functions(const char *filter, const char *module, char ***list) +{ + struct func_filter func_filter; + struct func_list *func_list = NULL, *f; + char **funcs = NULL; + int ret; + + if (!filter) + filter = ".*"; + + ret = init_func_filter(&func_filter, filter); + if (ret < 0) + return ret; + + ret = list_available_filters(&func_filter, module, &func_list); + if (ret < 0) + goto out; + + ret = -1; + for (f = func_list; f; f = f->next) { + char **tmp; + + tmp = tracefs_list_add(funcs, f->func); + if (!tmp) { + tracefs_list_free(funcs); + goto out; + } + funcs = tmp; + } + + *list = funcs; + ret = 0; +out: + regfree(&func_filter.re); + free_func_list(func_list); + return ret; +} diff --git a/src/tracefs-uprobes.c b/src/tracefs-uprobes.c new file mode 100644 index 0000000..aa39b75 --- /dev/null +++ b/src/tracefs-uprobes.c @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: LGPL-2.1 +/* + * Copyright (C) 2022, VMware, Tzvetomir Stoyanov + * + */ +#include +#include + +#include "tracefs.h" +#include "tracefs-local.h" + +#define UPROBE_DEFAULT_GROUP "uprobes" + +static struct tracefs_dynevent * +uprobe_alloc(enum tracefs_dynevent_type type, const char *system, const char *event, + const char *file, unsigned long long offset, const char *fetchargs) +{ + struct tracefs_dynevent *kp; + char *target; + + if (!event || !file) { + errno = EINVAL; + return NULL; + } + + if (!system) + system = UPROBE_DEFAULT_GROUP; + + if (asprintf(&target, "%s:0x%0*llx", file, (int)(sizeof(void *) * 2), offset) < 0) + return NULL; + + kp = dynevent_alloc(type, system, event, target, fetchargs); + free(target); + + return kp; +} + +/** + * tracefs_uprobe_alloc - Allocate new user probe (uprobe) + * @system: The system name (NULL for the default uprobes) + * @event: The name of the event to create + * @file: The full path to the binary file, where the uprobe will be set + * @offset: Offset within the @file + * @fetchargs: String with arguments, that will be fetched with the uprobe + * + * Allocate new uprobe context that will be in the @system group + * (or uprobes if @system is NULL) and with @event name. The new uprobe will be + * attached to @offset within the @file. The arguments described in @fetchargs + * will fetched with the uprobe. See linux/Documentation/trace/uprobetracer.rst + * for more details. + * + * The uprobe is not created in the system. + * + * Return a pointer to a uprobe context on success, or NULL on error. + * The returned pointer must be freed with tracefs_dynevent_free() + * + */ +struct tracefs_dynevent * +tracefs_uprobe_alloc(const char *system, const char *event, + const char *file, unsigned long long offset, const char *fetchargs) +{ + return uprobe_alloc(TRACEFS_DYNEVENT_UPROBE, system, event, file, offset, fetchargs); +} + +/** + * tracefs_uretprobe_alloc - Allocate new user return probe (uretprobe) + * @system: The system name (NULL for the default uprobes) + * @event: The name of the event to create + * @file: The full path to the binary file, where the uretprobe will be set + * @offset: Offset within the @file + * @fetchargs: String with arguments, that will be fetched with the uretprobe + * + * Allocate mew uretprobe context that will be in the @system group + * (or uprobes if @system is NULL) and with @event name. The new uretprobe will + * be attached to @offset within the @file. The arguments described in @fetchargs + * will fetched with the uprobe. See linux/Documentation/trace/uprobetracer.rst + * for more details. + * + * The uretprobe is not created in the system. + * + * Return a pointer to a uretprobe context on success, or NULL on error. + * The returned pointer must be freed with tracefs_dynevent_free() + * + */ +struct tracefs_dynevent * +tracefs_uretprobe_alloc(const char *system, const char *event, + const char *file, unsigned long long offset, const char *fetchargs) +{ + return uprobe_alloc(TRACEFS_DYNEVENT_URETPROBE, system, event, file, offset, fetchargs); +} diff --git a/src/tracefs-utils.c b/src/tracefs-utils.c new file mode 100644 index 0000000..9acf2ad --- /dev/null +++ b/src/tracefs-utils.c @@ -0,0 +1,624 @@ +// SPDX-License-Identifier: LGPL-2.1 +/* + * Copyright (C) 2008, 2009, 2010 Red Hat Inc, Steven Rostedt + * + * Updates: + * Copyright (C) 2019, VMware, Tzvetomir Stoyanov + * + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "tracefs.h" +#include "tracefs-local.h" + +#define TRACEFS_PATH "/sys/kernel/tracing" +#define DEBUGFS_PATH "/sys/kernel/debug" + +#define ERROR_LOG "error_log" + +#define _STR(x) #x +#define STR(x) _STR(x) + +static int log_level = TEP_LOG_CRITICAL; +static char *custom_tracing_dir; + +/** + * tracefs_set_loglevel - set log level of the library + * @level: desired level of the library messages + */ +void tracefs_set_loglevel(enum tep_loglevel level) +{ + log_level = level; + tep_set_loglevel(level); +} + +void __weak tracefs_warning(const char *fmt, ...) +{ + va_list ap; + + if (log_level < TEP_LOG_WARNING) + return; + + va_start(ap, fmt); + tep_vprint("libtracefs", TEP_LOG_WARNING, true, fmt, ap); + va_end(ap); +} + +static int mount_tracefs(void) +{ + struct stat st; + int ret; + + /* make sure debugfs exists */ + ret = stat(TRACEFS_PATH, &st); + if (ret < 0) + return -1; + + ret = mount("nodev", TRACEFS_PATH, + "tracefs", 0, NULL); + + return ret; +} + +static int mount_debugfs(void) +{ + struct stat st; + int ret; + + /* make sure debugfs exists */ + ret = stat(DEBUGFS_PATH, &st); + if (ret < 0) + return -1; + + ret = mount("nodev", DEBUGFS_PATH, + "debugfs", 0, NULL); + + return ret; +} + +/* Exported for testing purpose only */ +__hidden char *find_tracing_dir(bool debugfs, bool mount) +{ + char *debug_str = NULL; + char fspath[PATH_MAX+1]; + char *tracing_dir; + char type[100]; + int use_debug = 0; + FILE *fp; + + fp = fopen("/proc/mounts", "r"); + if (!fp) { + tracefs_warning("Can't open /proc/mounts for read"); + return NULL; + } + + while (fscanf(fp, "%*s %" + STR(PATH_MAX) + "s %99s %*s %*d %*d\n", + fspath, type) == 2) { + if (!debugfs && strcmp(type, "tracefs") == 0) + break; + if (!debug_str && strcmp(type, "debugfs") == 0) { + if (debugfs) + break; + debug_str = strdup(fspath); + if (!debug_str) { + fclose(fp); + return NULL; + } + } + } + fclose(fp); + + if (debugfs) { + if (strcmp(type, "debugfs") != 0) { + if (!mount || mount_debugfs() < 0) + return NULL; + strcpy(fspath, DEBUGFS_PATH); + } + } else if (strcmp(type, "tracefs") != 0) { + if (!mount || mount_tracefs() < 0) { + if (debug_str) { + strncpy(fspath, debug_str, PATH_MAX); + fspath[PATH_MAX] = 0; + } else { + if (!mount || mount_debugfs() < 0) { + if (mount) + tracefs_warning("debugfs not mounted, please mount"); + free(debug_str); + return NULL; + } + strcpy(fspath, DEBUGFS_PATH); + } + use_debug = 1; + } else + strcpy(fspath, TRACEFS_PATH); + } + free(debug_str); + + if (use_debug) { + int ret; + + ret = asprintf(&tracing_dir, "%s/tracing", fspath); + if (ret < 0) + return NULL; + } else { + tracing_dir = strdup(fspath); + if (!tracing_dir) + return NULL; + } + + return tracing_dir; +} + +/** + * tracefs_tracing_dir_is_mounted - test if the tracing dir is already mounted + * @mount: Mount it if it is not already mounted + * @path: the path to the tracing directory if mounted or was mounted + * + * Returns 1 if the tracing directory is already mounted and 0 if it is not. + * If @mount is set and it fails to mount, it returns -1. + * + * If path is not NULL, and the tracing directory is or was mounted, it holds + * the path to the tracing directory. It must not be freed. + */ +int tracefs_tracing_dir_is_mounted(bool mount, const char **path) +{ + const char *dir; + + dir = find_tracing_dir(false, false); + if (dir) { + if (path) + *path = dir; + return 1; + } + if (!mount) + return 0; + + dir = find_tracing_dir(false, mount); + if (!dir) + return -1; + if (path) + *path = dir; + return 0; +} + +/** + * trace_find_tracing_dir - Find tracing directory + * @debugfs: Boolean to just return the debugfs directory + * + * Returns string containing the full path to the system's tracing directory. + * The string must be freed by free() + */ +__hidden char *trace_find_tracing_dir(bool debugfs) +{ + return find_tracing_dir(debugfs, false); +} + +/** + * tracefs_set_tracing_dir - Set location of the tracing directory + * @tracing_dir: full path to the system's tracing directory mount point. + * + * Set the location to the system's tracing directory. This API should be used + * to set a custom location of the tracing directory. There is no need to call + * it if the location is standard, in that case the library will auto detect it. + * + * Returns 0 on success, -1 otherwise. + */ +int tracefs_set_tracing_dir(char *tracing_dir) +{ + if (custom_tracing_dir) { + free(custom_tracing_dir); + custom_tracing_dir = NULL; + } + + if (tracing_dir) { + custom_tracing_dir = strdup(tracing_dir); + if (!custom_tracing_dir) + return -1; + } + + return 0; +} + +/* Used to check if the directory is still mounted */ +static int test_dir(const char *dir, const char *file) +{ + char path[strlen(dir) + strlen(file) + 2]; + struct stat st; + + sprintf(path, "%s/%s", dir, file); + return stat(path, &st) < 0 ? 0 : 1; +} + +/** + * tracefs_tracing_dir - Get tracing directory + * + * Returns string containing the full path to the system's tracing directory. + * The returned string must *not* be freed. + */ +const char *tracefs_tracing_dir(void) +{ + static const char *tracing_dir; + + /* Do not check custom_tracing_dir */ + if (custom_tracing_dir) + return custom_tracing_dir; + + if (tracing_dir && test_dir(tracing_dir, "trace")) + return tracing_dir; + + tracing_dir = find_tracing_dir(false, true); + return tracing_dir; +} + +/** + * tracefs_debug_dir - Get debugfs directory path + * + * Returns string containing the full path to the system's debugfs directory. + * + * The returned string must *not* be freed. + */ +const char *tracefs_debug_dir(void) +{ + static const char *debug_dir; + + if (debug_dir && test_dir(debug_dir, "tracing")) + return debug_dir; + + debug_dir = find_tracing_dir(true, true); + return debug_dir; +} + +/** + * tracefs_get_tracing_file - Get tracing file + * @name: tracing file name + * + * Returns string containing the full path to a tracing file in + * the system's tracing directory. + * + * Must use tracefs_put_tracing_file() to free the returned string. + */ +char *tracefs_get_tracing_file(const char *name) +{ + const char *tracing; + char *file; + int ret; + + if (!name) + return NULL; + + tracing = tracefs_tracing_dir(); + if (!tracing) + return NULL; + + ret = asprintf(&file, "%s/%s", tracing, name); + if (ret < 0) + return NULL; + + return file; +} + +/** + * tracefs_put_tracing_file - Free tracing file or directory name + * + * Frees tracing file or directory, returned by + * tracefs_get_tracing_file()API. + */ +void tracefs_put_tracing_file(char *name) +{ + free(name); +} + +__hidden int str_read_file(const char *file, char **buffer, bool warn) +{ + char stbuf[BUFSIZ]; + char *buf = NULL; + int size = 0; + char *nbuf; + int fd; + int r; + + fd = open(file, O_RDONLY); + if (fd < 0) { + if (warn) + tracefs_warning("File %s not found", file); + return -1; + } + + do { + r = read(fd, stbuf, BUFSIZ); + if (r <= 0) + continue; + nbuf = realloc(buf, size+r+1); + if (!nbuf) { + if (warn) + tracefs_warning("Failed to allocate file buffer"); + size = -1; + break; + } + buf = nbuf; + memcpy(buf+size, stbuf, r); + size += r; + } while (r > 0); + + close(fd); + if (r == 0 && size > 0) { + buf[size] = '\0'; + *buffer = buf; + } else + free(buf); + + return size; +} + +/** + * tracefs_error_all - return the content of the error log + * @instance: The instance to read the error log from (NULL for top level) + * + * Return NULL if the log is empty, or on error (where errno will be + * set. Otherwise the content of the entire log is returned in a string + * that must be freed with free(). + */ +char *tracefs_error_all(struct tracefs_instance *instance) +{ + char *content; + char *path; + int size; + + errno = 0; + + path = tracefs_instance_get_file(instance, ERROR_LOG); + if (!path) + return NULL; + size = str_read_file(path, &content, false); + tracefs_put_tracing_file(path); + + if (size <= 0) + return NULL; + + return content; +} + +enum line_states { + START, + CARROT, +}; + +/** + * tracefs_error_last - return the last error logged + * @instance: The instance to read the error log from (NULL for top level) + * + * Return NULL if the log is empty, or on error (where errno will be + * set. Otherwise a string containing the content of the last error shown +* in the log that must be freed with free(). + */ +char *tracefs_error_last(struct tracefs_instance *instance) +{ + enum line_states state = START; + char *content; + char *ret; + bool done = false; + int size; + int i; + + content = tracefs_error_all(instance); + if (!content) + return NULL; + + size = strlen(content); + if (!size) /* Should never happen */ + return content; + + for (i = size - 1; i > 0; i--) { + switch (state) { + case START: + if (content[i] == '\n') { + /* Remove extra new lines */ + content[i] = '\0'; + break; + } + if (content[i] == '^') + state = CARROT; + break; + case CARROT: + if (content[i] == '\n') { + /* Remember last new line */ + size = i; + break; + } + if (content[i] == '^') { + /* Go just passed the last newline */ + i = size + 1; + done = true; + } + break; + } + if (done) + break; + } + + if (i) { + ret = strdup(content + i); + free(content); + } else { + ret = content; + } + + return ret; +} + +/** + * tracefs_error_clear - clear the error log of an instance + * @instance: The instance to clear (NULL for top level) + * + * Clear the content of the error log. + * + * Returns 0 on success, -1 otherwise. + */ +int tracefs_error_clear(struct tracefs_instance *instance) +{ + return tracefs_instance_file_clear(instance, ERROR_LOG); +} + +/** + * tracefs_list_free - free list if strings, returned by APIs + * tracefs_event_systems() + * tracefs_system_events() + * + *@list pointer to a list of strings, the last one must be NULL + */ +void tracefs_list_free(char **list) +{ + int i; + + if (!list) + return; + + for (i = 0; list[i]; i++) + free(list[i]); + + /* The allocated list is before the user visible portion */ + list--; + free(list); +} + + +__hidden char ** trace_list_create_empty(void) +{ + char **list; + + list = calloc(2, sizeof(*list)); + + return list ? &list[1] : NULL; +} + +/** + * tracefs_list_add - create or extend a string list + * @list: The list to add to (NULL to create a new one) + * @string: The string to append to @list. + * + * If @list is NULL, a new list is created with the first element + * a copy of @string, and the second element is NULL. + * + * If @list is not NULL, it is then reallocated to include + * a new element and a NULL terminator, and will return the new + * allocated array on success, and the one passed in should be + * ignored. + * + * Returns an allocated string array that must be freed with + * tracefs_list_free() on success. On failure, NULL is returned + * and the @list is untouched. + */ +char **tracefs_list_add(char **list, const char *string) +{ + unsigned long size = 0; + char *str = strdup(string); + char **new_list; + + if (!str) + return NULL; + + /* + * The returned list is really the address of the + * second entry of the list (&list[1]), the first + * entry contains the number of strings in the list. + */ + if (list) { + list--; + size = *(unsigned long *)list; + } + + new_list = realloc(list, sizeof(*list) * (size + 3)); + if (!new_list) { + free(str); + return NULL; + } + + list = new_list; + list[0] = (char *)(size + 1); + list++; + list[size++] = str; + list[size] = NULL; + + return list; +} + +/* + * trace_list_pop - Removes the last string added + * @list: The list to remove the last event from + * + * Returns 0 on success, -1 on error. + * Returns 1 if the list is empty or NULL. + */ +__hidden int trace_list_pop(char **list) +{ + unsigned long size; + + if (!list || list[0]) + return 1; + + list--; + size = *(unsigned long *)list; + /* size must be greater than zero */ + if (!size) + return -1; + size--; + *list = (char *)size; + list++; + list[size] = NULL; + return 0; +} + +/** + * tracefs_list_size - Return the number of strings in the list + * @list: The list to determine the size. + * + * Returns the number of elements in the list. + * If @list is NULL, then zero is returned. + */ +int tracefs_list_size(char **list) +{ + if (!list) + return 0; + + list--; + return (int)*(unsigned long *)list; +} + +/** + * tracefs_tracer_available - test if a tracer is available + * @tracing_dir: The directory that contains the tracing directory + * @tracer: The name of the tracer + * + * Return true if the tracer is available + */ +bool tracefs_tracer_available(const char *tracing_dir, const char *tracer) +{ + bool ret = false; + char **tracers = NULL; + int i; + + tracers = tracefs_tracers(tracing_dir); + if (!tracers) + return false; + + for (i = 0; tracers[i]; i++) { + if (strcmp(tracer, tracers[i]) == 0) { + ret = true; + break; + } + } + + tracefs_list_free(tracers); + return ret; +} -- cgit v1.2.3