diff options
Diffstat (limited to 'pseudolog.c')
-rw-r--r-- | pseudolog.c | 783 |
1 files changed, 783 insertions, 0 deletions
diff --git a/pseudolog.c b/pseudolog.c new file mode 100644 index 0000000..33ee3c7 --- /dev/null +++ b/pseudolog.c @@ -0,0 +1,783 @@ +/* + * pseudolog.c, pseudo database viewer (preliminary) + * + * Copyright (c) 2008-2010 Wind River Systems, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the Lesser GNU General Public License version 2.1 as + * published by the Free Software Foundation. + * + * 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 Lesser GNU General Public License for more details. + * + * You should have received a copy of the Lesser GNU General Public License + * version 2.1 along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +/* We need _XOPEN_SOURCE for strptime(), but if we define that, + * we then don't get S_IFSOCK... _GNU_SOURCE turns on everything. */ +#define _GNU_SOURCE + +#include <ctype.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <sys/stat.h> + +#include "pseudo.h" +#include "pseudo_ipc.h" +#include "pseudo_db.h" + +static int opt_D = 0; +static int opt_l = 0; + +static void display(log_entry *, char *format); +static unsigned long format_scan(char *format); + +void +usage(int status) { + static char *options[] = { + "c client pid", + "d device number", + "f file descriptor", + "g gid", + "G tag (text)", + "i inode number", + "I id (database row)", + "m permission bits (octal)", + "M file mode (octal)", + "o operation (e.g. 'open')", + "O order by (< DESC > ASC)", + "p file path", + "r result (e.g. 'succeed')", + "s timestamp", + "S severity", + "t type (like find -type)", + "T text (text field)", + "u uid", + NULL, + }; + FILE *f = (status == EXIT_SUCCESS) ? stdout : stderr; + int i; + + fputs("pseudolog: create or report log entries. usage:\n", f); + fputs("pseudolog -l [-E timeformat] [SPECIFIERS] -- create entries\n", f); + fputs("pseudolog [-D] [-F format] [-E timeformat] [SPECIFIERS] -- report entries\n", f); + fputs(" format is a printf-like format string using the option letters\n", f); + fputs(" listed below as format specifiers for the corresponding field.\n", f); + fputs(" timeformat is a strftime-like format string, the default is '%x %X'.\n", f); + fputs("\n", f); + fputs("SPECIFIERS are options of the form -X <value>, where X is one of\n", f); + fputs("the following option letters, and value is the value to match.\n", f); + fputs("values may be prefixed with ! (not equal to), > (greater than),\n", f); + fputs("< (less than), & (bitwise and), ~ (LIKE match, anchored at both\n", f); + fputs("ends, text fields only), or % (LIKE match, text fields only).\n", f); + fputs("\n", f); + fputs("OPTION LETTERS:\n", f); + for (i = 0; options[i]; ++i) { + fprintf(f, " %-28s%s", options[i], (i % 2) ? "\n" : " "); + } + exit(status); +} + +pseudo_query_field_t opt_to_field[UCHAR_MAX + 1] = { + ['c'] = PSQF_CLIENT, + ['d'] = PSQF_DEV, + ['f'] = PSQF_FD, + ['g'] = PSQF_GID, + ['G'] = PSQF_TAG, + ['I'] = PSQF_ID, + ['i'] = PSQF_INODE, + ['m'] = PSQF_PERM, + ['M'] = PSQF_MODE, + ['o'] = PSQF_OP, + ['O'] = PSQF_ORDER, + ['p'] = PSQF_PATH, + ['r'] = PSQF_RESULT, + ['s'] = PSQF_STAMP, + ['S'] = PSQF_SEVERITY, + ['t'] = PSQF_FTYPE, + ['T'] = PSQF_TEXT, + ['u'] = PSQF_UID, +}; + +pseudo_query_type_t +plog_query_type(char **string) { + pseudo_query_type_t type = PSQT_EXACT; + if (!string || !*string) + return PSQT_UNKNOWN; + switch (**string) { + case '\0': + pseudo_diag("Error: Value may not be an empty string."); + return PSQT_UNKNOWN; + break; + case '>': + type = PSQT_GREATER; + ++*string; + break; + case '<': + type = PSQT_LESS; + ++*string; + break; + case '!': + type = PSQT_NOTEQUAL; + ++*string; + break; + case '=': + ++*string; + break; + case '&': + type = PSQT_BITAND; + ++*string; + break; + case '%': + type = PSQT_LIKE; + ++*string; + break; + case '^': + type = PSQT_NOTLIKE; + ++*string; + break; + case '~': + type = PSQT_SQLPAT; + ++*string; + break; + case '\\': + /* no special type, but allows one of the others to be the + * first character of the effective string + */ + ++*string; + break; + } + if (opt_l && type != PSQT_EXACT) { + pseudo_diag("Error: Non-exact match requested while trying to create a log entry.\n"); + type = PSQT_UNKNOWN; + } + return type; +} + +static char *time_formats[] = { + "%s", + "%F %r", + "%F %T", + "%m-%d %r", + "%m-%d %T", + "%r", + "%T", + NULL, +}; +static char *timeformat = "%x %X"; + +mode_t +parse_file_type(char *string) { + switch (*string) { + case 'b': + return S_IFBLK; + break; + case 'c': + return S_IFCHR; + break; + case 'd': + return S_IFDIR; + break; + case '-': /* FALLTHROUGH */ + case 'f': + return S_IFREG; + break; + case 'l': + return S_IFLNK; + break; + case 'p': + return S_IFIFO; + break; + case 's': + return S_IFSOCK; + break; + default: + pseudo_diag("unknown file type %c; should be one of [-bcdflps]\n", + isprint(*string) ? *string : '?'); + return -1; + break; + } +} + +mode_t +parse_partial_mode(char *string) { + mode_t mode = 0; + switch (string[0]) { + case 'r': + mode |= 04; + break; + case '-': + break; + default: + pseudo_diag("unknown mode character: %c\n", string[0]); + return -1; + break; + } + switch (string[1]) { + case 'w': + mode |= 02; + break; + case '-': + break; + default: + pseudo_diag("unknown mode character: %c\n", string[1]); + return -1; + break; + } + switch (string[2]) { + case 'x': + mode |= 01; + break; + case 't': /* FALLTHROUGH */ + case 's': + mode |= 011; + break; + case 'T': /* FALLTHROUGH */ + case 'S': + mode |= 010; + break; + case '-': + break; + default: + pseudo_diag("unknown mode character: %c\n", string[2]); + return -1; + break; + } + return mode; +} + +mode_t +parse_mode_string(char *string) { + size_t len = strlen(string); + mode_t mode = 0; + mode_t bits = 0; + + if (len != 9 && len != 10) { + pseudo_diag("mode strings must be of the form [-]rwxr-xr-x\n"); + return -1; + } + if (len == 10) { + mode |= parse_file_type(string); + ++string; + if (mode == -1) { + pseudo_diag("mode strings with a file type must use a valid type [-bcdflps]\n"); + return -1; + } + } + bits = parse_partial_mode(string); + if (bits == -1) + return -1; + if (bits & 010) { + mode |= S_ISUID; + bits &= ~010; + } + mode |= bits << 6; + string += 3; + bits = parse_partial_mode(string); + if (bits == -1) + return -1; + if (bits & 010) { + mode |= S_ISGID; + bits &= ~010; + } + mode |= bits << 3; + string += 3; + bits = parse_partial_mode(string); + if (bits == -1) + return -1; + if (bits & 010) { + mode |= S_ISVTX; + bits &= ~010; + } + mode |= bits; + return mode; +} + +static time_t +parse_timestamp(char *string) { + time_t stamp_sec; + struct tm stamp_tm; + int i; + char *s; + char timebuf[4096]; + + stamp_sec = time(0); + + /* try the user's provided time format first, if there is one: */ + localtime_r(&stamp_sec, &stamp_tm); + s = strptime(string, timeformat, &stamp_tm); + if (s && !*s) { + return mktime(&stamp_tm); + } + + for (i = 0; time_formats[i]; ++i) { + char *s; + localtime_r(&stamp_sec, &stamp_tm); + s = strptime(string, time_formats[i], &stamp_tm); + if (s && !*s) { + break; + } + } + if (!time_formats[i]) { + pseudo_diag("Couldn't parse <%s> as a time. Current time in known formats is:\n", + string); + localtime_r(&stamp_sec, &stamp_tm); + for (i = 0; time_formats[i]; ++i) { + strftime(timebuf, sizeof(timebuf), time_formats[i], &stamp_tm); + pseudo_diag("\t%s\n", timebuf); + } + pseudo_diag("Or, specify your own with -E; see strptime(3).\n"); + return -1; + } + return mktime(&stamp_tm); +} + +pseudo_query_t * +plog_trait(int opt, char *string) { + pseudo_query_t *new_trait; + char *endptr; + + if (opt < 0 || opt > UCHAR_MAX) { + pseudo_diag("Unknown/invalid option value: %d\n", opt); + return 0; + } + if (!opt_to_field[opt]) { + if (isprint(opt)) { + pseudo_diag("Unknown option: -%c\n", opt); + } else { + pseudo_diag("Unknown option: 0x%02x\n", opt); + } + return 0; + } + if (!*string) { + pseudo_diag("invalid empty string for -%c\n", opt); + return 0; + } + new_trait = calloc(sizeof(*new_trait), 1); + if (!new_trait) { + pseudo_diag("Couldn't allocate requested trait (for -%c %s)\n", + opt, string ? string : "<nil>"); + return 0; + } + new_trait->field = opt_to_field[opt]; + new_trait->type = plog_query_type(&string); + if (new_trait->type == PSQT_UNKNOWN) { + pseudo_diag("Couldn't comprehend trait type for '%s'\n", + string ? string : "<nil>"); + free(new_trait); + return 0; + } + switch (new_trait->field) { + case PSQF_FTYPE: + /* special magic: allow file types ala find */ + /* This is implemented by additional magic over in the database code */ + /* must not be more than one character. The test against + * the first character is because in theory, if the + * first character is the terminating NUL, we may not + * access the second. */ + if (string[0] && string[1]) { + pseudo_diag("file type must be a single character [-bcdflps].\n"); + free(new_trait); + return 0; + } + new_trait->data.ivalue = parse_file_type(string); + if (new_trait->data.ivalue == -1) { + free(new_trait); + return 0; + } + break; + case PSQF_OP: + new_trait->data.ivalue = pseudo_op_id(string); + break; + case PSQF_ORDER: + if (string[0] && string[1]) { + pseudo_diag("order type must be a single specifier character.\n"); + free(new_trait); + return 0; + } + new_trait->data.ivalue = opt_to_field[(unsigned char) string[0]]; + if (!new_trait->data.ivalue) { + pseudo_diag("Unknown field type: %c\n", string[0]); + } + break; + case PSQF_RESULT: + new_trait->data.ivalue = pseudo_res_id(string); + break; + case PSQF_SEVERITY: + new_trait->data.ivalue = pseudo_sev_id(string); + break; + case PSQF_STAMP: + new_trait->data.ivalue = parse_timestamp(string); + if (new_trait->data.ivalue == (time_t) -1) { + free(new_trait); + return 0; + } + break; + case PSQF_CLIENT: + case PSQF_DEV: + case PSQF_FD: + case PSQF_GID: + case PSQF_INODE: + case PSQF_UID: + new_trait->data.ivalue = strtoll(string, &endptr, 0); + if (*endptr) { + pseudo_diag("Unexpected garbage after number (%llu): '%s'\n", + new_trait->data.ivalue, endptr); + free(new_trait); + return 0; + } + break; + case PSQF_MODE: + case PSQF_PERM: + new_trait->data.ivalue = strtoll(string, &endptr, 8); + if (!*endptr) { + break; + } + /* maybe it's a mode string? */ + new_trait->data.ivalue = parse_mode_string(string); + if (new_trait->data.ivalue == -1) { + free(new_trait); + return 0; + } + if (new_trait->field == PSQF_PERM) { + /* mask out file type */ + new_trait->data.ivalue &= ~S_IFMT; + } + break; + case PSQF_PATH: /* FALLTHROUGH */ + case PSQF_TEXT: /* FALLTHROUGH */ + case PSQF_TAG: + /* Plain strings */ + new_trait->data.svalue = strdup(string); + break; + default: + pseudo_diag("I don't know how I got here. Unknown field type %d.\n", + new_trait->field); + free(new_trait); + return 0; + break; + } + return new_trait; +} + +/* You can either create a query or create a log entry. They use very + * similar syntax, but: + * - if you're making a query, you can use >, <, etc. + * - if you're logging, you can't. + * This is tracked by recording whether any non-exact relations + * have been requested ("query_only"), and refusing to set the -l + * flag if they have, and refusing to accept any such relation + * if the -l flag is already set. + */ +int +main(int argc, char **argv) { + pseudo_query_t *traits = 0, *current = 0, *new_trait = 0; + log_history history; + int query_only = 0; + int o; + int bad_args = 0; + char *format = "%s %-5o %7r: [mode %04m] %p %T"; + + while ((o = getopt(argc, argv, "vlc:d:DE:f:F:g:G:hi:I:m:M:o:O:p:r:s:S:t:T:u:")) != -1) { + switch (o) { + case 'P': + setenv("PSEUDO_PREFIX", optarg, 1); + break; + case 'v': + pseudo_debug_verbose(); + break; + case 'l': + opt_l = 1; + break; + case 'D': + opt_D = 1; + query_only = 1; + break; + case 'E': + timeformat = strdup(optarg); + break; + case 'F': + /* disallow specifying -F with -l */ + format = strdup(optarg); + query_only = 1; + break; + case 'I': /* PSQF_ID */ + query_only = 1; + /* FALLTHROUGH */ + case 'c': /* PSQF_CLIENT */ + case 'd': /* PSQF_DEV */ + case 'f': /* PSQF_FD */ + case 'g': /* PSQF_GID */ + case 'G': /* PSQF_TAG */ + case 'i': /* PSQF_INODE */ + case 'm': /* PSQF_PERM */ + case 'M': /* PSQF_MODE */ + case 'o': /* PSQF_OP */ + case 'O': /* PSQF_ORDER */ + case 'p': /* PSQF_PATH */ + case 'r': /* PSQF_RESULT */ + case 's': /* PSQF_STAMP */ + case 'S': /* PSQF_SEVERITY */ + case 't': /* PSQF_FTYPE */ + case 'T': /* PSQF_TEXT */ + case 'u': /* PSQF_UID */ + new_trait = plog_trait(o, optarg); + if (!new_trait) { + bad_args = 1; + } + break; + case 'h': + usage(EXIT_SUCCESS); + break; + case '?': /* FALLTHROUGH */ + default: + fprintf(stderr, "unknown option '%c'\n", optopt); + usage(EXIT_FAILURE); + break; + } + if (new_trait) { + if (current) { + current->next = new_trait; + current = current->next; + } else { + traits = new_trait; + current = new_trait; + } + new_trait = 0; + } + } + + if (optind < argc) { + pseudo_diag("Error: Extra arguments not associated with any option.\n"); + usage(EXIT_FAILURE); + } + + if (query_only && opt_l) { + pseudo_diag("Error: -l cannot be used with query-only options or flags.\n"); + bad_args = 1; + } + + /* should be set only if we have already diagnosed the bad arguments. */ + if (bad_args) + exit(EXIT_FAILURE); + + if (!pseudo_get_prefix(argv[0])) { + pseudo_diag("Can't figure out prefix. Set PSEUDO_PREFIX or invoke with full path.\n"); + exit(EXIT_FAILURE); + } + + if (opt_l) { + pdb_log_traits(traits); + } else { + unsigned long fields; + fields = format_scan(format); + if (fields == -1) { + pseudo_diag("couldn't parse format string (%s).\n", format); + return EXIT_FAILURE; + } + history = pdb_history(traits, fields, opt_D); + if (history) { + log_entry *e; + while ((e = pdb_history_entry(history)) != NULL) { + display(e, format); + log_entry_free(e); + } + pdb_history_free(history); + } else { + pseudo_diag("could not retrieve history.\n"); + return EXIT_FAILURE; + } + } + return 0; +} + +/* print a single member of log, based on a single format specifier; + * returns the address of the last character of the format specifier. + */ +static char * +format_one(log_entry *e, char *format) { + char fmtbuf[256]; + size_t len = strcspn(format, "cdfgGimMoprsStTu"), real_len; + char scratch[4096]; + time_t stamp_sec; + struct tm stamp_tm; + char *s; + + if (!e || !format) { + pseudo_diag("invalid log entry or format specifier.\n"); + return 0; + } + real_len = snprintf(fmtbuf, sizeof(fmtbuf), "%.*s", len + 1, format); + if (real_len >= sizeof(fmtbuf) - 1) { + pseudo_diag("Format string way too long starting at %.10s", + format - 1); + return 0; + } + /* point to the last character */ + s = fmtbuf + real_len - 1; + + /* The * modifier for width or precision requires additional + * parameters -- this doesn't make sense here. + */ + if (strchr(fmtbuf, '*') || strchr(fmtbuf, 'l') || strchr(fmtbuf, 'h')) { + pseudo_diag("Sorry, you can't use *, h, or l format modifiers.\n"); + return 0; + } + + switch (*s) { + case 'c': /* PSQF_CLIENT */ + strcpy(s, "d"); + printf(fmtbuf, (int) e->client); + break; + case 'd': /* PSQF_DEV */ + strcpy(s, "d"); + printf(fmtbuf, (int) e->dev); + break; + case 'f': /* PSQF_FD */ + strcpy(s, "d"); + printf(fmtbuf, (int) e->fd); + break; + case 'g': /* PSQF_GID */ + strcpy(s, "d"); + printf(fmtbuf, (int) e->gid); + break; + case 'G': /* PSQF_TAG */ + strcpy(s, "s"); + printf(fmtbuf, e->tag ? e->tag : ""); + break; + case 'i': /* PSQF_INODE */ + strcpy(s, "llu"); + printf(fmtbuf, (unsigned long long) e->ino); + break; + case 'm': /* PSQF_PERM */ + strcpy(s, "o"); + printf(fmtbuf, (unsigned int) e->mode & ALLPERMS); + break; + case 'M': /* PSQF_MODE */ + strcpy(s, "o"); + printf(fmtbuf, (unsigned int) e->mode); + break; + case 'o': /* PSQF_OP */ + strcpy(s, "s"); + printf(fmtbuf, pseudo_op_name(e->op)); + break; + case 'p': /* PSQF_PATH */ + strcpy(s, "s"); + printf(fmtbuf, e->path ? e->path : ""); + break; + case 'r': /* PSQF_RESULT */ + strcpy(s, "s"); + printf(fmtbuf, pseudo_res_name(e->result)); + break; + case 's': /* PSQF_STAMP */ + strcpy(s, "s"); + stamp_sec = e->stamp; + localtime_r(&stamp_sec, &stamp_tm); + strftime(scratch, sizeof(scratch), timeformat, &stamp_tm); + printf(fmtbuf, scratch); + break; + case 'S': /* PSQF_SEVERITY */ + strcpy(s, "s"); + printf(fmtbuf, pseudo_sev_name(e->severity)); + break; + case 't': /* PSQF_FTYPE */ + strcpy(s, "s"); + if (S_ISREG(e->mode)) { + strcpy(scratch, "file"); + } if (S_ISLNK(e->mode)) { + strcpy(scratch, "link"); + } else if (S_ISDIR(e->mode)) { + strcpy(scratch, "dir"); + } else if (S_ISFIFO(e->mode)) { + strcpy(scratch, "fifo"); + } else if (S_ISBLK(e->mode)) { + strcpy(scratch, "block"); + } else if (S_ISCHR(e->mode)) { + strcpy(scratch, "char"); + } else { + snprintf(scratch, sizeof(scratch), "?%o", (unsigned int) e->mode & S_IFMT); + } + printf(fmtbuf, scratch); + break; + case 'T': /* PSQF_TEXT */ + strcpy(s, "s"); + printf(fmtbuf, e->text ? e->text : ""); + break; + case 'u': /* PSQF_UID */ + strcpy(s, "d"); + printf(fmtbuf, (int) e->uid); + break; + } + return format + len; +} + +static unsigned long +format_scan(char *format) { + char *s; + size_t len; + unsigned long fields = 0; + pseudo_query_field_t field; + + for (s = format; (s = strchr(s, '%')) != NULL; ++s) { + len = strcspn(s, "cdfgGimMoprsStTu"); + s += len; + field = opt_to_field[(unsigned char) *s]; + switch (field) { + case PSQF_PERM: + case PSQF_FTYPE: + fields |= (1 << PSQF_MODE); + break; + case PSQF_CLIENT: + case PSQF_DEV: + case PSQF_FD: + case PSQF_GID: + case PSQF_TAG: + case PSQF_INODE: + case PSQF_MODE: + case PSQF_OP: + case PSQF_PATH: + case PSQF_RESULT: + case PSQF_STAMP: + case PSQF_SEVERITY: + case PSQF_TEXT: + case PSQF_UID: + fields |= (1 << field); + break; + case '\0': + /* if there are no more formats, that may be wrong, but + * we can ignore it here + */ + break; + default: + pseudo_diag("error: invalid format specifier %c (at %s)\n", *s, s); + return -1; + break; + } + } + return fields; +} + +static void +display(log_entry *e, char *format) { + for (; *format; ++format) { + switch (*format) { + case '%': + format = format_one(e, format); + if (!format) + return; + break; + default: + putchar(*format); + break; + } + } + putchar('\n'); +} |