Merge pull request #7881 from keszybz/pcre

Add new --grep option to journalctl
This commit is contained in:
Lennart Poettering 2018-01-28 15:29:10 +01:00 committed by GitHub
commit 7755083256
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 370 additions and 87 deletions

View File

@ -578,6 +578,29 @@
priorities.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>-g</option></term>
<term><option>--grep=</option></term>
<listitem><para>Filter output to entries where the <varname>MESSAGE=</varname>
field matches the specified regular expression. PERL-compatible regular expressions
are used, see
<citerefentry><refentrytitle>pcre2pattern</refentrytitle><manvolnum>3</manvolnum></citerefentry>
for a detailed description of the syntax.</para>
<para>If the pattern is all lowercase, matching is case insensitive.
Otherwise, matching is case sensitive. This can be overridden with the
<option>--case-sensitive</option> option, see below.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>--case-sensitive<optional>=BOOLEAN</optional></option></term>
<listitem><para>Make pattern matching case sensitive or case insenstive.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>-c</option></term>
<term><option>--cursor=</option></term>

View File

@ -1104,6 +1104,17 @@ else
endif
conf.set10('HAVE_XKBCOMMON', have)
want_pcre2 = get_option('pcre2')
if want_pcre2 != 'false'
libpcre2 = dependency('libpcre2-8',
required : want_pcre2 == 'true')
have = libpcre2.found()
else
have = false
libpcre2 = []
endif
conf.set10('HAVE_PCRE2', have)
want_glib = get_option('glib')
if want_glib != 'false' and not fuzzer_build
libglib = dependency('glib-2.0',
@ -1465,7 +1476,8 @@ exe = executable('journalctl',
dependencies : [threads,
libqrencode,
libxz,
liblz4],
liblz4,
libpcre2],
install_rpath : rootlibexecdir,
install : true,
install_dir : rootbindir)
@ -2817,6 +2829,7 @@ foreach tuple : [
['gnu-efi', have_gnu_efi],
['kmod'],
['xkbcommon'],
['pcre2'],
['blkid'],
['dbus'],
['glib'],

View File

@ -260,6 +260,8 @@ option('lz4', type : 'combo', choices : ['auto', 'true', 'false'],
description : 'lz4 compression support')
option('xkbcommon', type : 'combo', choices : ['auto', 'true', 'false'],
description : 'xkbcommon keymap support')
option('pcre2', type : 'combo', choices : ['auto', 'true', 'false'],
description : 'regexp matching support using pcre2')
option('glib', type : 'combo', choices : ['auto', 'true', 'false'],
description : 'libglib support (for tests only)')
option('dbus', type : 'combo', choices : ['auto', 'true', 'false'],

View File

@ -140,6 +140,12 @@
#define _IDN_FEATURE_ "-IDN"
#endif
#if HAVE_PCRE2
#define _PCRE2_FEATURE_ "+PCRE2"
#else
#define _PCRE2_FEATURE_ "-PCRE2"
#endif
#define _CGROUP_HIEARCHY_ "default-hierarchy=" DEFAULT_HIERARCHY_NAME
#define SYSTEMD_FEATURES \
@ -163,4 +169,5 @@
_KMOD_FEATURE_ " " \
_IDN2_FEATURE_ " " \
_IDN_FEATURE_ " " \
_PCRE2_FEATURE_ " " \
_CGROUP_HIEARCHY_

View File

@ -30,6 +30,7 @@
#include "gunicode.h"
#include "macro.h"
#include "string-util.h"
#include "terminal-util.h"
#include "utf8.h"
#include "util.h"
@ -648,7 +649,17 @@ char *strreplace(const char *text, const char *old_string, const char *new_strin
return ret;
}
char *strip_tab_ansi(char **ibuf, size_t *_isz) {
static void advance_offsets(ssize_t diff, size_t offsets[2], size_t shift[2], size_t size) {
if (!offsets)
return;
if ((size_t) diff < offsets[0])
shift[0] += size;
if ((size_t) diff < offsets[1])
shift[1] += size;
}
char *strip_tab_ansi(char **ibuf, size_t *_isz, size_t highlight[2]) {
const char *i, *begin = NULL;
enum {
STATE_OTHER,
@ -656,7 +667,7 @@ char *strip_tab_ansi(char **ibuf, size_t *_isz) {
STATE_BRACKET
} state = STATE_OTHER;
char *obuf = NULL;
size_t osz = 0, isz;
size_t osz = 0, isz, shift[2] = {};
FILE *f;
assert(ibuf);
@ -684,15 +695,18 @@ char *strip_tab_ansi(char **ibuf, size_t *_isz) {
break;
else if (*i == '\x1B')
state = STATE_ESCAPE;
else if (*i == '\t')
else if (*i == '\t') {
fputs(" ", f);
else
advance_offsets(i - *ibuf, highlight, shift, 7);
} else
fputc(*i, f);
break;
case STATE_ESCAPE:
if (i >= *ibuf + isz) { /* EOT */
fputc('\x1B', f);
advance_offsets(i - *ibuf, highlight, shift, 1);
break;
} else if (*i == '[') {
state = STATE_BRACKET;
@ -700,6 +714,7 @@ char *strip_tab_ansi(char **ibuf, size_t *_isz) {
} else {
fputc('\x1B', f);
fputc(*i, f);
advance_offsets(i - *ibuf, highlight, shift, 1);
state = STATE_OTHER;
}
@ -711,6 +726,7 @@ char *strip_tab_ansi(char **ibuf, size_t *_isz) {
(!(*i >= '0' && *i <= '9') && !IN_SET(*i, ';', 'm'))) {
fputc('\x1B', f);
fputc('[', f);
advance_offsets(i - *ibuf, highlight, shift, 2);
state = STATE_OTHER;
i = begin-1;
} else if (*i == 'm')
@ -732,6 +748,11 @@ char *strip_tab_ansi(char **ibuf, size_t *_isz) {
if (_isz)
*_isz = osz;
if (highlight) {
highlight[0] += shift[0];
highlight[1] += shift[1];
}
return obuf;
}

View File

@ -177,7 +177,7 @@ char* strshorten(char *s, size_t l);
char *strreplace(const char *text, const char *old_string, const char *new_string);
char *strip_tab_ansi(char **p, size_t *l);
char *strip_tab_ansi(char **ibuf, size_t *_isz, size_t highlight[2]);
char *strextend_with_separator(char **x, const char *separator, ...) _sentinel_;

View File

@ -226,7 +226,8 @@ static ssize_t request_reader_entries(
return MHD_CONTENT_READER_END_WITH_ERROR;
}
r = output_journal(m->tmp, m->journal, m->mode, 0, OUTPUT_FULL_WIDTH, NULL, NULL);
r = output_journal(m->tmp, m->journal, m->mode, 0, OUTPUT_FULL_WIDTH,
NULL, NULL, NULL);
if (r < 0) {
log_error_errno(r, "Failed to serialize item: %m");
return MHD_CONTENT_READER_END_WITH_ERROR;

View File

@ -34,6 +34,11 @@
#include <sys/stat.h>
#include <unistd.h>
#if HAVE_PCRE2
# define PCRE2_CODE_UNIT_WIDTH 8
# include <pcre2.h>
#endif
#include "sd-bus.h"
#include "sd-journal.h"
@ -76,6 +81,34 @@
#define DEFAULT_FSS_INTERVAL_USEC (15*USEC_PER_MINUTE)
#if HAVE_PCRE2
DEFINE_TRIVIAL_CLEANUP_FUNC(pcre2_match_data*, pcre2_match_data_free);
DEFINE_TRIVIAL_CLEANUP_FUNC(pcre2_code*, pcre2_code_free);
static int pattern_compile(const char *pattern, unsigned flags, pcre2_code **out) {
int errorcode, r;
PCRE2_SIZE erroroffset;
pcre2_code *p;
p = pcre2_compile((PCRE2_SPTR8) pattern,
PCRE2_ZERO_TERMINATED, flags, &errorcode, &erroroffset, NULL);
if (!p) {
unsigned char buf[LINE_MAX];
r = pcre2_get_error_message(errorcode, buf, sizeof buf);
log_error("Bad pattern \"%s\": %s",
pattern,
r < 0 ? "unknown error" : (char*) buf);
return -EINVAL;
}
*out = p;
return 0;
}
#endif
enum {
/* Special values for arg_lines */
ARG_LINES_DEFAULT = -2,
@ -126,6 +159,12 @@ static uint64_t arg_vacuum_n_files = 0;
static usec_t arg_vacuum_time = 0;
static char **arg_output_fields = NULL;
#if HAVE_PCRE2
static const char *arg_pattern = NULL;
static pcre2_code *arg_compiled_pattern = NULL;
static int arg_case_sensitive = -1; /* -1 means be smart */
#endif
static enum {
ACTION_SHOW,
ACTION_NEW_ID128,
@ -280,67 +319,69 @@ static void help(void) {
printf("%s [OPTIONS...] [MATCHES...]\n\n"
"Query the journal.\n\n"
"Options:\n"
" --system Show the system journal\n"
" --user Show the user journal for the current user\n"
" -M --machine=CONTAINER Operate on local container\n"
" -S --since=DATE Show entries not older than the specified date\n"
" -U --until=DATE Show entries not newer than the specified date\n"
" -c --cursor=CURSOR Show entries starting at the specified cursor\n"
" --after-cursor=CURSOR Show entries after the specified cursor\n"
" --show-cursor Print the cursor after all the entries\n"
" -b --boot[=ID] Show current boot or the specified boot\n"
" --list-boots Show terse information about recorded boots\n"
" -k --dmesg Show kernel message log from the current boot\n"
" -u --unit=UNIT Show logs from the specified unit\n"
" --user-unit=UNIT Show logs from the specified user unit\n"
" -t --identifier=STRING Show entries with the specified syslog identifier\n"
" -p --priority=RANGE Show entries with the specified priority\n"
" -e --pager-end Immediately jump to the end in the pager\n"
" -f --follow Follow the journal\n"
" -n --lines[=INTEGER] Number of journal entries to show\n"
" --no-tail Show all lines, even in follow mode\n"
" -r --reverse Show the newest entries first\n"
" -o --output=STRING Change journal output mode (short, short-precise,\n"
" short-iso, short-iso-precise, short-full,\n"
" short-monotonic, short-unix, verbose, export,\n"
" json, json-pretty, json-sse, cat)\n"
" --output-fields=LIST Select fields to print in verbose/export/json modes\n"
" --utc Express time in Coordinated Universal Time (UTC)\n"
" -x --catalog Add message explanations where available\n"
" --no-full Ellipsize fields\n"
" -a --all Show all fields, including long and unprintable\n"
" -q --quiet Do not show info messages and privilege warning\n"
" --no-pager Do not pipe output into a pager\n"
" --no-hostname Suppress output of hostname field\n"
" -m --merge Show entries from all available journals\n"
" -D --directory=PATH Show journal files from directory\n"
" --file=PATH Show journal file\n"
" --root=ROOT Operate on files below a root directory\n"
" --system Show the system journal\n"
" --user Show the user journal for the current user\n"
" -M --machine=CONTAINER Operate on local container\n"
" -S --since=DATE Show entries not older than the specified date\n"
" -U --until=DATE Show entries not newer than the specified date\n"
" -c --cursor=CURSOR Show entries starting at the specified cursor\n"
" --after-cursor=CURSOR Show entries after the specified cursor\n"
" --show-cursor Print the cursor after all the entries\n"
" -b --boot[=ID] Show current boot or the specified boot\n"
" --list-boots Show terse information about recorded boots\n"
" -k --dmesg Show kernel message log from the current boot\n"
" -u --unit=UNIT Show logs from the specified unit\n"
" --user-unit=UNIT Show logs from the specified user unit\n"
" -t --identifier=STRING Show entries with the specified syslog identifier\n"
" -p --priority=RANGE Show entries with the specified priority\n"
" -g --grep=PATTERN Show entries with MESSSAGE matching PATTERN\n"
" --case-sensitive[=BOOL] Force case sensitive or insenstive matching\n"
" -e --pager-end Immediately jump to the end in the pager\n"
" -f --follow Follow the journal\n"
" -n --lines[=INTEGER] Number of journal entries to show\n"
" --no-tail Show all lines, even in follow mode\n"
" -r --reverse Show the newest entries first\n"
" -o --output=STRING Change journal output mode (short, short-precise,\n"
" short-iso, short-iso-precise, short-full,\n"
" short-monotonic, short-unix, verbose, export,\n"
" json, json-pretty, json-sse, cat)\n"
" --output-fields=LIST Select fields to print in verbose/export/json modes\n"
" --utc Express time in Coordinated Universal Time (UTC)\n"
" -x --catalog Add message explanations where available\n"
" --no-full Ellipsize fields\n"
" -a --all Show all fields, including long and unprintable\n"
" -q --quiet Do not show info messages and privilege warning\n"
" --no-pager Do not pipe output into a pager\n"
" --no-hostname Suppress output of hostname field\n"
" -m --merge Show entries from all available journals\n"
" -D --directory=PATH Show journal files from directory\n"
" --file=PATH Show journal file\n"
" --root=ROOT Operate on files below a root directory\n"
#if HAVE_GCRYPT
" --interval=TIME Time interval for changing the FSS sealing key\n"
" --verify-key=KEY Specify FSS verification key\n"
" --force Override of the FSS key pair with --setup-keys\n"
" --interval=TIME Time interval for changing the FSS sealing key\n"
" --verify-key=KEY Specify FSS verification key\n"
" --force Override of the FSS key pair with --setup-keys\n"
#endif
"\nCommands:\n"
" -h --help Show this help text\n"
" --version Show package version\n"
" -N --fields List all field names currently used\n"
" -F --field=FIELD List all values that a specified field takes\n"
" --disk-usage Show total disk usage of all journal files\n"
" --vacuum-size=BYTES Reduce disk usage below specified size\n"
" --vacuum-files=INT Leave only the specified number of journal files\n"
" --vacuum-time=TIME Remove journal files older than specified time\n"
" --verify Verify journal file consistency\n"
" --sync Synchronize unwritten journal messages to disk\n"
" --flush Flush all journal data from /run into /var\n"
" --rotate Request immediate rotation of the journal files\n"
" --header Show journal header information\n"
" --list-catalog Show all message IDs in the catalog\n"
" --dump-catalog Show entries in the message catalog\n"
" --update-catalog Update the message catalog database\n"
" --new-id128 Generate a new 128-bit ID\n"
" -h --help Show this help text\n"
" --version Show package version\n"
" -N --fields List all field names currently used\n"
" -F --field=FIELD List all values that a specified field takes\n"
" --disk-usage Show total disk usage of all journal files\n"
" --vacuum-size=BYTES Reduce disk usage below specified size\n"
" --vacuum-files=INT Leave only the specified number of journal files\n"
" --vacuum-time=TIME Remove journal files older than specified time\n"
" --verify Verify journal file consistency\n"
" --sync Synchronize unwritten journal messages to disk\n"
" --flush Flush all journal data from /run into /var\n"
" --rotate Request immediate rotation of the journal files\n"
" --header Show journal header information\n"
" --list-catalog Show all message IDs in the catalog\n"
" --dump-catalog Show entries in the message catalog\n"
" --update-catalog Update the message catalog database\n"
" --new-id128 Generate a new 128-bit ID\n"
#if HAVE_GCRYPT
" --setup-keys Generate a new FSS key pair\n"
" --setup-keys Generate a new FSS key pair\n"
#endif
, program_invocation_short_name);
}
@ -372,6 +413,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_DUMP_CATALOG,
ARG_UPDATE_CATALOG,
ARG_FORCE,
ARG_CASE_SENSITIVE,
ARG_UTC,
ARG_SYNC,
ARG_FLUSH,
@ -411,6 +453,8 @@ static int parse_argv(int argc, char *argv[]) {
{ "header", no_argument, NULL, ARG_HEADER },
{ "identifier", required_argument, NULL, 't' },
{ "priority", required_argument, NULL, 'p' },
{ "grep", required_argument, NULL, 'g' },
{ "case-sensitive", optional_argument, NULL, ARG_CASE_SENSITIVE },
{ "setup-keys", no_argument, NULL, ARG_SETUP_KEYS },
{ "interval", required_argument, NULL, ARG_INTERVAL },
{ "verify", no_argument, NULL, ARG_VERIFY },
@ -762,6 +806,27 @@ static int parse_argv(int argc, char *argv[]) {
break;
}
#if HAVE_PCRE2
case 'g':
arg_pattern = optarg;
break;
case ARG_CASE_SENSITIVE:
if (optarg) {
r = parse_boolean(optarg);
if (r < 0)
return log_error_errno(r, "Bad --case-sensitive= argument \"%s\": %m", optarg);
arg_case_sensitive = r;
} else
arg_case_sensitive = true;
break;
#else
case 'g':
case ARG_CASE_SENSITIVE:
return log_error("Compiled without pattern matching support");
#endif
case 'S':
r = parse_timestamp(optarg, &arg_since);
if (r < 0) {
@ -917,6 +982,42 @@ static int parse_argv(int argc, char *argv[]) {
arg_system_units = strv_free(arg_system_units);
}
#if HAVE_PCRE2
if (arg_pattern) {
unsigned flags;
if (arg_case_sensitive >= 0)
flags = !arg_case_sensitive * PCRE2_CASELESS;
else {
_cleanup_(pcre2_match_data_freep) pcre2_match_data *md = NULL;
bool has_case;
_cleanup_(pcre2_code_freep) pcre2_code *cs = NULL;
md = pcre2_match_data_create(1, NULL);
if (!md)
return log_oom();
r = pattern_compile("[[:upper:]]", 0, &cs);
if (r < 0)
return r;
r = pcre2_match(cs, (PCRE2_SPTR8) arg_pattern, PCRE2_ZERO_TERMINATED, 0, 0, md, NULL);
has_case = r >= 0;
flags = !has_case * PCRE2_CASELESS;
}
log_debug("Doing case %s matching based on %s",
flags & PCRE2_CASELESS ? "insensitive" : "sensitive",
arg_case_sensitive >= 0 ? "request" : "pattern casing");
r = pattern_compile(arg_pattern, flags, &arg_compiled_pattern);
if (r < 0)
return r;
}
#endif
return 1;
}
@ -2415,6 +2516,7 @@ int main(int argc, char *argv[]) {
for (;;) {
while (arg_lines < 0 || n_shown < arg_lines || (arg_follow && !first_line)) {
int flags;
size_t highlight[2] = {};
if (need_seek) {
if (!arg_reverse)
@ -2468,6 +2570,58 @@ int main(int argc, char *argv[]) {
}
}
#if HAVE_PCRE2
if (arg_compiled_pattern) {
_cleanup_(pcre2_match_data_freep) pcre2_match_data *md = NULL;
const void *message;
size_t len;
PCRE2_SIZE *ovec;
md = pcre2_match_data_create(1, NULL);
if (!md)
return log_oom();
r = sd_journal_get_data(j, "MESSAGE", &message, &len);
if (r < 0) {
if (r == -ENOENT) {
need_seek = true;
continue;
}
log_error_errno(r, "Failed to get MESSAGE field: %m");
goto finish;
}
assert_se(message = startswith(message, "MESSAGE="));
r = pcre2_match(arg_compiled_pattern,
message,
len - strlen("MESSAGE="),
0, /* start at offset 0 in the subject */
0, /* default options */
md,
NULL);
if (r == PCRE2_ERROR_NOMATCH) {
need_seek = true;
continue;
}
if (r < 0) {
unsigned char buf[LINE_MAX];
int r2;
r2 = pcre2_get_error_message(r, buf, sizeof buf);
log_error("Pattern matching failed: %s",
r2 < 0 ? "unknown error" : (char*) buf);
r = -EINVAL;
goto finish;
}
ovec = pcre2_get_ovector_pointer(md);
highlight[0] = ovec[0];
highlight[1] = ovec[1];
}
#endif
flags =
arg_all * OUTPUT_SHOW_ALL |
arg_full * OUTPUT_FULL_WIDTH |
@ -2476,7 +2630,8 @@ int main(int argc, char *argv[]) {
arg_utc * OUTPUT_UTC |
arg_no_hostname * OUTPUT_NO_HOSTNAME;
r = output_journal(stdout, j, arg_output, 0, flags, arg_output_fields, &ellipsized);
r = output_journal(stdout, j, arg_output, 0, flags,
arg_output_fields, highlight, &ellipsized);
need_seek = true;
if (r == -EADDRNOTAVAIL)
break;
@ -2527,5 +2682,10 @@ finish:
free(arg_root);
free(arg_verify_key);
#if HAVE_PCRE2
if (arg_compiled_pattern)
pcre2_code_free(arg_compiled_pattern);
#endif
return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}

View File

@ -166,8 +166,17 @@ static bool shall_print(const char *p, size_t l, OutputFlags flags) {
return true;
}
static bool print_multiline(FILE *f, unsigned prefix, unsigned n_columns, OutputFlags flags, int priority, const char* message, size_t message_len) {
const char *color_on = "", *color_off = "";
static bool print_multiline(
FILE *f,
unsigned prefix,
unsigned n_columns,
OutputFlags flags,
int priority,
const char* message,
size_t message_len,
size_t highlight[2]) {
const char *color_on = "", *color_off = "", *highlight_on = "";
const char *pos, *end;
bool ellipsized = false;
int line = 0;
@ -176,9 +185,11 @@ static bool print_multiline(FILE *f, unsigned prefix, unsigned n_columns, Output
if (priority <= LOG_ERR) {
color_on = ANSI_HIGHLIGHT_RED;
color_off = ANSI_NORMAL;
highlight_on = ANSI_HIGHLIGHT;
} else if (priority <= LOG_NOTICE) {
color_on = ANSI_HIGHLIGHT;
color_off = ANSI_NORMAL;
highlight_on = ANSI_HIGHLIGHT_RED;
}
}
@ -209,9 +220,28 @@ static bool print_multiline(FILE *f, unsigned prefix, unsigned n_columns, Output
if (flags & (OUTPUT_FULL_WIDTH | OUTPUT_SHOW_ALL) ||
(prefix + len + 1 < n_columns && !tail_line)) {
fprintf(f, "%*s%s%.*s%s\n",
continuation * prefix, "",
color_on, len, pos, color_off);
if (highlight &&
(size_t) (pos - message) <= highlight[0] &&
highlight[0] < (size_t) len) {
fprintf(f, "%*s%s%.*s",
continuation * prefix, "",
color_on, (int) highlight[0], pos);
fprintf(f, "%s%.*s",
highlight_on,
(int) (MIN((size_t) len, highlight[1]) - highlight[0]),
pos + highlight[0]);
if ((size_t) len > highlight[1])
fprintf(f, "%s%.*s",
color_on,
(int) (len - highlight[1]),
pos + highlight[1]);
fprintf(f, "%s\n", color_off);
} else
fprintf(f, "%*s%s%.*s%s\n",
continuation * prefix, "",
color_on, len, pos, color_off);
continue;
}
@ -369,7 +399,8 @@ static int output_short(
OutputMode mode,
unsigned n_columns,
OutputFlags flags,
Set *output_fields) {
Set *output_fields,
size_t highlight[2]) {
int r;
const void *data;
@ -390,6 +421,7 @@ static int output_short(
PARSE_FIELD_VEC_ENTRY("_SOURCE_REALTIME_TIMESTAMP=", &realtime, &realtime_len),
PARSE_FIELD_VEC_ENTRY("_SOURCE_MONOTONIC_TIMESTAMP=", &monotonic, &monotonic_len),
};
size_t highlight_shifted[] = {highlight ? highlight[0] : 0, highlight ? highlight[1] : 0};
assert(f);
assert(j);
@ -421,7 +453,7 @@ static int output_short(
}
if (!(flags & OUTPUT_SHOW_ALL))
strip_tab_ansi(&message, &message_len);
strip_tab_ansi(&message, &message_len, highlight_shifted);
if (priority_len == 1 && *priority >= '0' && *priority <= '7')
p = *priority - '0';
@ -468,7 +500,9 @@ static int output_short(
} else {
fputs(": ", f);
ellipsized |=
print_multiline(f, n + 2, n_columns, flags, p, message, message_len);
print_multiline(f, n + 2, n_columns, flags, p,
message, message_len,
highlight_shifted);
}
if (flags & OUTPUT_CATALOG)
@ -483,7 +517,8 @@ static int output_verbose(
OutputMode mode,
unsigned n_columns,
OutputFlags flags,
Set *output_fields) {
Set *output_fields,
size_t highlight[2]) {
const void *data;
size_t length;
@ -561,7 +596,7 @@ static int output_verbose(
(((length < PRINT_CHAR_THRESHOLD) || flags & OUTPUT_FULL_WIDTH)
&& utf8_is_printable(data, length))) {
fprintf(f, " %s%.*s=", on, fieldlen, (const char*)data);
print_multiline(f, 4 + fieldlen + 1, 0, OUTPUT_FULL_WIDTH, 0, c + 1, length - fieldlen - 1);
print_multiline(f, 4 + fieldlen + 1, 0, OUTPUT_FULL_WIDTH, 0, c + 1, length - fieldlen - 1, NULL);
fputs(off, f);
} else {
char bytes[FORMAT_BYTES_MAX];
@ -590,7 +625,8 @@ static int output_export(
OutputMode mode,
unsigned n_columns,
OutputFlags flags,
Set *output_fields) {
Set *output_fields,
size_t highlight[2]) {
sd_id128_t boot_id;
char sid[33];
@ -732,7 +768,8 @@ static int output_json(
OutputMode mode,
unsigned n_columns,
OutputFlags flags,
Set *output_fields) {
Set *output_fields,
size_t highlight[2]) {
uint64_t realtime, monotonic;
_cleanup_free_ char *cursor = NULL;
@ -961,15 +998,22 @@ static int output_cat(
OutputMode mode,
unsigned n_columns,
OutputFlags flags,
Set *output_fields) {
Set *output_fields,
size_t highlight[2]) {
const void *data;
size_t l;
int r;
const char *highlight_on = "", *highlight_off = "";
assert(j);
assert(f);
if (flags & OUTPUT_COLOR) {
highlight_on = ANSI_HIGHLIGHT_RED;
highlight_off = ANSI_NORMAL;
}
sd_journal_set_data_threshold(j, 0);
r = sd_journal_get_data(j, "MESSAGE", &data, &l);
@ -987,7 +1031,17 @@ static int output_cat(
assert(l >= 8);
fwrite((const char*) data + 8, 1, l - 8, f);
if (highlight && (flags & OUTPUT_COLOR)) {
assert(highlight[0] <= highlight[1]);
assert(highlight[1] <= l - 8);
fwrite((const char*) data + 8, 1, highlight[0], f);
fwrite(highlight_on, 1, strlen(highlight_on), f);
fwrite((const char*) data + 8 + highlight[0], 1, highlight[1] - highlight[0], f);
fwrite(highlight_off, 1, strlen(highlight_off), f);
fwrite((const char*) data + 8 + highlight[1], 1, l - 8 - highlight[1], f);
} else
fwrite((const char*) data + 8, 1, l - 8, f);
fputc('\n', f);
return 0;
@ -999,7 +1053,8 @@ static int (*output_funcs[_OUTPUT_MODE_MAX])(
OutputMode mode,
unsigned n_columns,
OutputFlags flags,
Set *output_fields) = {
Set *output_fields,
size_t highlight[2]) = {
[OUTPUT_SHORT] = output_short,
[OUTPUT_SHORT_ISO] = output_short,
@ -1023,6 +1078,7 @@ int output_journal(
unsigned n_columns,
OutputFlags flags,
char **output_fields,
size_t highlight[2],
bool *ellipsized) {
int ret;
@ -1043,7 +1099,7 @@ int output_journal(
return ret;
}
ret = output_funcs[mode](f, j, mode, n_columns, flags, fields);
ret = output_funcs[mode](f, j, mode, n_columns, flags, fields, highlight);
if (ellipsized && ret > 0)
*ellipsized = true;
@ -1125,7 +1181,7 @@ static int show_journal(FILE *f,
line++;
maybe_print_begin_newline(f, &flags);
r = output_journal(f, j, mode, n_columns, flags, NULL, ellipsized);
r = output_journal(f, j, mode, n_columns, flags, NULL, NULL, ellipsized);
if (r < 0)
return r;
}

View File

@ -39,6 +39,7 @@ int output_journal(
unsigned n_columns,
OutputFlags flags,
char **output_fields,
size_t highlight[2],
bool *ellipsized);
int add_match_this_boot(sd_journal *j, const char *machine);

View File

@ -515,7 +515,6 @@ tests += [
[],
'', 'manual'],
[['src/test/test-cgroup-mask.c',
'src/test/test-helper.c'],
[libcore,

View File

@ -28,24 +28,24 @@ int main(int argc, char *argv[]) {
char *p;
assert_se(p = strdup("\tFoobar\tbar\twaldo\t"));
assert_se(strip_tab_ansi(&p, NULL));
assert_se(strip_tab_ansi(&p, NULL, NULL));
fprintf(stdout, "<%s>\n", p);
assert_se(streq(p, " Foobar bar waldo "));
free(p);
assert_se(p = strdup(ANSI_HIGHLIGHT "Hello" ANSI_NORMAL ANSI_HIGHLIGHT_RED " world!" ANSI_NORMAL));
assert_se(strip_tab_ansi(&p, NULL));
assert_se(strip_tab_ansi(&p, NULL, NULL));
fprintf(stdout, "<%s>\n", p);
assert_se(streq(p, "Hello world!"));
free(p);
assert_se(p = strdup("\x1B[\x1B[\t\x1B[" ANSI_HIGHLIGHT "\x1B[" "Hello" ANSI_NORMAL ANSI_HIGHLIGHT_RED " world!" ANSI_NORMAL));
assert_se(strip_tab_ansi(&p, NULL));
assert_se(strip_tab_ansi(&p, NULL, NULL));
assert_se(streq(p, "\x1B[\x1B[ \x1B[\x1B[Hello world!"));
free(p);
assert_se(p = strdup("\x1B[waldo"));
assert_se(strip_tab_ansi(&p, NULL));
assert_se(strip_tab_ansi(&p, NULL, NULL));
assert_se(streq(p, "\x1B[waldo"));
free(p);