summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 07:26:15 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 07:26:15 +0000
commitc72e01b9ea891fa5cbbbe9876bdfb49079b55be7 (patch)
tree44c55a81a281a6c211745c0abc68352aeaf0180f /src
parentInitial commit. (diff)
downloadlibtracefs-c72e01b9ea891fa5cbbbe9876bdfb49079b55be7.tar.xz
libtracefs-c72e01b9ea891fa5cbbbe9876bdfb49079b55be7.zip
Adding upstream version 1.6.4.upstream/1.6.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src')
-rw-r--r--src/Makefile62
-rw-r--r--src/sqlhist-lex.c2204
-rw-r--r--src/sqlhist-parse.h77
-rw-r--r--src/sqlhist.l104
-rw-r--r--src/sqlhist.tab.c1755
-rw-r--r--src/sqlhist.tab.h118
-rw-r--r--src/sqlhist.y248
-rw-r--r--src/tracefs-dynevents.c779
-rw-r--r--src/tracefs-eprobes.c56
-rw-r--r--src/tracefs-events.c1515
-rw-r--r--src/tracefs-filter.c807
-rw-r--r--src/tracefs-hist.c2401
-rw-r--r--src/tracefs-instance.c1241
-rw-r--r--src/tracefs-kprobes.c198
-rw-r--r--src/tracefs-marker.c199
-rw-r--r--src/tracefs-record.c611
-rw-r--r--src/tracefs-sqlhist.c1653
-rw-r--r--src/tracefs-tools.c1273
-rw-r--r--src/tracefs-uprobes.c90
-rw-r--r--src/tracefs-utils.c624
20 files changed, 16015 insertions, 0 deletions
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 $(<F) $@
+
+$(LIBTRACEFS_SHARED_SO): $(LIBTRACEFS_SHARED_VERSION)
+ @ln -sf $(<F) $@
+
+libtracefs.so: $(LIBTRACEFS_SHARED_SO)
+
+# bison will create both sqlhist.tab.c and sqlhist.tab.h
+sqlhist.tab.h:
+sqlhist.tab.c: sqlhist.y sqlhist.tab.h
+ bison --debug -v --report-file=bison.report -d -o $@ $<
+
+sqlhist-lex.c: sqlhist.l sqlhist.tab.c
+ flex -o $@ $<
+
+$(bdir)/%.o: %.c
+ $(Q)$(call do_fpic_compile)
+
+tracefs-sqlhist.o: sqlhist.tab.h
+
+$(OBJS): | $(bdir)
+
+clean:
+ $(Q)$(call do_clean,$(OBJS) .*.d)
+
+-include .*.d
+
+$(bdir)/tracefs-sqlhist.o tracefs-sqlhist.o: sqlhist.tab.h
+
+.PHONY: $(LIBTRACEFS_SHARED_SO) $(LIBTRACEFS_STATIC)
diff --git a/src/sqlhist-lex.c b/src/sqlhist-lex.c
new file mode 100644
index 0000000..5c75280
--- /dev/null
+++ b/src/sqlhist-lex.c
@@ -0,0 +1,2204 @@
+#line 1 "sqlhist-lex.c"
+
+#line 3 "sqlhist-lex.c"
+
+#define YY_INT_ALIGNED short int
+
+/* A lexical scanner generated by flex */
+
+#define FLEX_SCANNER
+#define YY_FLEX_MAJOR_VERSION 2
+#define YY_FLEX_MINOR_VERSION 6
+#define YY_FLEX_SUBMINOR_VERSION 4
+#if YY_FLEX_SUBMINOR_VERSION > 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 <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+
+/* end standard C headers. */
+
+/* flex integer type definitions */
+
+#ifndef FLEXINT_H
+#define FLEXINT_H
+
+/* C99 systems have <inttypes.h>. 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 <inttypes.h>
+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 <stdarg.h>
+#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 <unistd.h>
+#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 <stdarg.h>
+#include <tracefs.h>
+
+#include <tracefs-local.h>
+
+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 <stdarg.h>
+#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 <http://www.gnu.org/licenses/>. */
+
+/* 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#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<Type> (Val)
+# define YY_REINTERPRET_CAST(Type, Val) reinterpret_cast<Type> (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
+ <limits.h> and (if available) <stdint.h> are included
+ so that the code can choose integer types of a good width. */
+
+#ifndef __PTRDIFF_MAX__
+# include <limits.h> /* INFRINGES ON USER NAME SPACE */
+# if defined __STDC_VERSION__ && 199901 <= __STDC_VERSION__
+# include <stdint.h> /* 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 <stddef.h> /* 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 <stddef.h> /* 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 <libintl.h> /* 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 <alloca.h> /* INFRINGES ON USER NAME SPACE */
+# elif defined _AIX
+# define YYSTACK_ALLOC __alloca
+# elif defined _MSC_VER
+# include <malloc.h> /* INFRINGES ON USER NAME SPACE */
+# define alloca _alloca
+# else
+# define YYSTACK_ALLOC alloca
+# if ! defined _ALLOCA_H && ! defined EXIT_SUCCESS
+# include <stdlib.h> /* 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 <stdlib.h> /* 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 <stdio.h> /* 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 <http://www.gnu.org/licenses/>. */
+
+/* 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#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> NUMBER field_type
+%token <string> STRING
+%token <string> FIELD
+%token <string> LE GE EQ NEQ AND OR
+
+%left '+' '-'
+%left '*' '/'
+%left '<' '>'
+%left AND OR
+
+%type <string> name label
+
+%type <expr> selection_expr field item named_field
+%type <expr> selection_addition
+%type <expr> compare compare_list compare_cmds compare_items
+%type <expr> compare_and_or
+%type <expr> 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 <rostedt@goodmis.org>
+ *
+ * Updates:
+ * Copyright (C) 2021, VMware, Tzvetomir Stoyanov <tz.stoyanov@gmail.com>
+ *
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+
+#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 <tz.stoyanov@gmail.com>
+ *
+ */
+#include <stdlib.h>
+#include <errno.h>
+
+#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 <srostedt@redhat.com>
+ *
+ * Updates:
+ * Copyright (C) 2019, VMware, Tzvetomir Stoyanov <tz.stoyanov@gmail.com>
+ *
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <limits.h>
+
+#include <kbuffer.h>
+
+#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 <rostedt@goodmis.org>
+ *
+ * Updates:
+ * Copyright (C) 2021, VMware, Tzvetomir Stoyanov <tz.stoyanov@gmail.com>
+ *
+ */
+#include <trace-seq.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <errno.h>
+
+#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 <rostedt@goodmis.org>
+ *
+ * Updates:
+ * Copyright (C) 2021, VMware, Tzvetomir Stoyanov <tz.stoyanov@gmail.com>
+ *
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <sys/time.h>
+#include <sys/types.h>
+
+#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(<synthetic_event>,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 <srostedt@redhat.com>
+ *
+ * Updates:
+ * Copyright (C) 2019, VMware, Tzvetomir Stoyanov <tz.stoyanov@gmail.com>
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <regex.h>
+#include <limits.h>
+#include <pthread.h>
+#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 <rostedt@goodmis.org>
+ *
+ * Updates:
+ * Copyright (C) 2021, VMware, Tzvetomir Stoyanov <tz.stoyanov@gmail.com>
+ *
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+
+#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 <tz.stoyanov@gmail.com>
+ *
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <pthread.h>
+
+#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 <rostedt@goodmis.org>
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <sys/select.h>
+
+#include <kbuffer.h>
+
+#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 <rostedt@goodmis.org>
+ *
+ * Updates:
+ * Copyright (C) 2021, VMware, Tzvetomir Stoyanov <tz.stoyanov@gmail.com>
+ *
+ */
+#include <trace-seq.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include <errno.h>
+
+#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 <srostedt@redhat.com>
+ *
+ * Updates:
+ * Copyright (C) 2021, VMware, Tzvetomir Stoyanov <tz.stoyanov@gmail.com>
+ *
+ */
+#include <stdlib.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <regex.h>
+#include <dirent.h>
+#include <limits.h>
+#include <pthread.h>
+#include <errno.h>
+
+#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; "<filter>:mod:<module>"
+ * 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 <tz.stoyanov@gmail.com>
+ *
+ */
+#include <stdlib.h>
+#include <errno.h>
+
+#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 <srostedt@redhat.com>
+ *
+ * Updates:
+ * Copyright (C) 2019, VMware, Tzvetomir Stoyanov <tz.stoyanov@gmail.com>
+ *
+ */
+#include <stdlib.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <linux/limits.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include <event-parse.h>
+#include <event-utils.h>
+#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;
+}