[Libguestfs] [PATCH v3] fish: add journal-view command

Richard W.M. Jones rjones at redhat.com
Mon Mar 23 12:46:01 UTC 2015


On Thu, Mar 05, 2015 at 04:51:00PM +0100, Maros Zatko wrote:
> Lets user view journald log from VM in a similar format as journalctl uses.
> 
> Makes virt-log use the same code as guestfish for journal-view.
> 
> Fixes RFE: journal reader in guestfish (RHBZ#988100)

I think this patch could be in two parts, one where you move the
journal_view() code from cat/log.c into fish/journal.c, and a second
part where you add the extra command.  More comments below.

>  cat/Makefile.am      |   1 +
>  cat/log.c            | 113 +-------------------------------
>  fish/Makefile.am     |   1 +
>  fish/fish.h          |   3 +
>  fish/journal.c       | 178 +++++++++++++++++++++++++++++++++++++++++++++++++++
>  generator/actions.ml |   9 +++
>  6 files changed, 195 insertions(+), 110 deletions(-)
>  create mode 100644 fish/journal.c
> 
> diff --git a/cat/Makefile.am b/cat/Makefile.am
> index d0db6fa..d472100 100644
> --- a/cat/Makefile.am
> +++ b/cat/Makefile.am
> @@ -38,6 +38,7 @@ bin_PROGRAMS = virt-cat virt-filesystems virt-log virt-ls
>  SHARED_SOURCE_FILES = \
>  	../fish/domain.c \
>  	../fish/inspect.c \
> +	../fish/journal.c \
>  	../fish/keys.c \
>  	../fish/options.h \
>  	../fish/options.c \
> diff --git a/cat/log.c b/cat/log.c
> index 616baed..c72946b 100644
> --- a/cat/log.c
> +++ b/cat/log.c
> @@ -36,6 +36,7 @@
>  
>  #include "guestfs.h"
>  #include "options.h"
> +#include "fish.h"
>  
>  /* Currently open libguestfs handle. */
>  guestfs_h *g;
> @@ -273,128 +274,20 @@ do_log (void)
>    return 0;
>  }
>  
> -/* Find the value of the named field from the list of attributes.  If
> - * not found, returns NULL (not an error).  If found, returns a
> - * pointer to the field, and the length of the field.  NOTE: The field
> - * is NOT \0-terminated, so you have to print it using "%.*s".
> - *
> - * There may be multiple fields with the same name.  In this case, the
> - * function returns the first entry.
> - */
> -static const char *
> -get_journal_field (const struct guestfs_xattr_list *xattrs, const char *name,
> -                   size_t *len_rtn)
> -{
> -  uint32_t i;
> -
> -  for (i = 0; i < xattrs->len; ++i) {
> -    if (STREQ (name, xattrs->val[i].attrname)) {
> -      *len_rtn = xattrs->val[i].attrval_len;
> -      return xattrs->val[i].attrval;
> -    }
> -  }
> -
> -  return NULL;                  /* not found */
> -}
> -
> -static const char *const log_level_table[] = {
> -  [LOG_EMERG] = "emerg",
> -  [LOG_ALERT] = "alert",
> -  [LOG_CRIT] = "crit",
> -  [LOG_ERR] = "err",
> -  [LOG_WARNING] = "warning",
> -  [LOG_NOTICE] = "notice",
> -  [LOG_INFO] = "info",
> -  [LOG_DEBUG] = "debug"
> -};
> -
>  static int
>  do_log_journal (void)
>  {
>    int r;
> -  unsigned errors = 0;
> -
>    if (guestfs_journal_open (g, JOURNAL_DIR) == -1)
>      return -1;
>  
> -  while ((r = guestfs_journal_next (g)) > 0) {
> -    CLEANUP_FREE_XATTR_LIST struct guestfs_xattr_list *xattrs = NULL;
> -    const char *priority_str, *identifier, *comm, *pid, *message;
> -    size_t priority_len, identifier_len, comm_len, pid_len, message_len;
> -    int priority = LOG_INFO;
> -    int64_t ts;
> -
> -    /* The question is what fields to display.  We should probably
> -     * make this configurable, but for now use the "short" format from
> -     * journalctl.  (XXX)
> -     */
> -
> -    xattrs = guestfs_journal_get (g);
> -    if (xattrs == NULL)
> -      return -1;
> -
> -    ts = guestfs_journal_get_realtime_usec (g); /* error checked below */
> -
> -    priority_str = get_journal_field (xattrs, "PRIORITY", &priority_len);
> -    //hostname = get_journal_field (xattrs, "_HOSTNAME", &hostname_len);
> -    identifier = get_journal_field (xattrs, "SYSLOG_IDENTIFIER",
> -                                    &identifier_len);
> -    comm = get_journal_field (xattrs, "_COMM", &comm_len);
> -    pid = get_journal_field (xattrs, "_PID", &pid_len);
> -    message = get_journal_field (xattrs, "MESSAGE", &message_len);
> -
> -    /* Timestamp. */
> -    if (ts >= 0) {
> -      char buf[64];
> -      time_t t = ts / 1000000;
> -      struct tm tm;
> -
> -      if (strftime (buf, sizeof buf, "%b %d %H:%M:%S",
> -                    localtime_r (&t, &tm)) <= 0) {
> -        fprintf (stderr, _("%s: could not format journal entry timestamp\n"),
> -                 guestfs_int_program_name);
> -        errors++;
> -        continue;
> -      }
> -      fputs (buf, stdout);
> -    }
> -
> -    /* Hostname. */
> -    /* We don't print this because it is assumed each line from the
> -     * guest will have the same hostname.  (XXX)
> -     */
> -    //if (hostname)
> -    //  printf (" %.*s", (int) hostname_len, hostname);
> -
> -    /* Identifier. */
> -    if (identifier)
> -      printf (" %.*s", (int) identifier_len, identifier);
> -    else if (comm)
> -      printf (" %.*s", (int) comm_len, comm);
> -
> -    /* PID */
> -    if (pid)
> -      printf ("[%.*s]", (int) pid_len, pid);
> -
> -    /* Log level. */
> -    if (priority_str && *priority_str >= '0' && *priority_str <= '7')
> -      priority = *priority_str - '0';
> -
> -    printf (" %s:", log_level_table[priority]);
> -
> -    /* Message. */
> -    if (message)
> -      printf (" %.*s", (int) message_len, message);
> -
> -    printf ("\n");
> -  }
> -  if (r == -1)                  /* error from guestfs_journal_next */
> +  if ((r = journal_view ("~3axv")) == -1)
>      return -1;
>  
>    if (guestfs_journal_close (g) == -1)
>      return -1;
>  
> -  return errors > 0 ? -1 : 0;
> +  return r;
>  }
>  
>  static int
> diff --git a/fish/Makefile.am b/fish/Makefile.am
> index c4b82ae..e4b4fcf 100644
> --- a/fish/Makefile.am
> +++ b/fish/Makefile.am
> @@ -94,6 +94,7 @@ guestfish_SOURCES = \
>  	glob.c \
>  	help.c \
>  	hexedit.c \
> +	journal.c \
>  	lcd.c \
>  	man.c \
>  	more.c \
> diff --git a/fish/fish.h b/fish/fish.h
> index df22e34..8ae6454 100644
> --- a/fish/fish.h
> +++ b/fish/fish.h
> @@ -104,6 +104,9 @@ extern int rc_remote (int pid, const char *cmd, size_t argc, char *argv[],
>  /* in tilde.c */
>  extern char *try_tilde_expansion (char *path);
>  
> +/* in journal.c */
> +extern int journal_view (const char *fields);
> +
>  /* This should just list all the built-in commands so they can
>   * be added to the generated auto-completion code.
>   */
> diff --git a/fish/journal.c b/fish/journal.c
> new file mode 100644
> index 0000000..8472445
> --- /dev/null
> +++ b/fish/journal.c
> @@ -0,0 +1,178 @@
> +/* guestfish - guest filesystem shell
> + * Copyright (C) 2009-2015 Red Hat 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 2 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, write to the Free Software
> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
> + */
> +
> +#include <config.h>
> +
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <unistd.h>
> +#include <fcntl.h>
> +#include <inttypes.h>
> +#include <libintl.h>
> +#include <time.h>
> +#include <syslog.h>
> +
> +#include "fish.h"
> +
> +/* Find the value of the named field from the list of attributes.  If
> + * not found, returns NULL (not an error).  If found, returns a
> + * pointer to the field, and the length of the field.  NOTE: The field
> + * is NOT \0-terminated, so you have to print it using "%.*s".
> + *
> + * There may be multiple fields with the same name.  In this case, the
> + * function returns the first entry.
> + */
> +static const char *
> +get_journal_field (const struct guestfs_xattr_list *xattrs, const char *name,
> +                   size_t *len_rtn)
> +{
> +  uint32_t i;
> +
> +  for (i = 0; i < xattrs->len; ++i) {
> +    if (STREQ (name, xattrs->val[i].attrname)) {
> +      *len_rtn = xattrs->val[i].attrval_len;
> +      return xattrs->val[i].attrval;
> +    }
> +  }
> +
> +  return NULL;                  /* not found */
> +}
> +
> +static const char *const log_level_table[] = {
> +  [LOG_EMERG] = "emerg",
> +  [LOG_ALERT] = "alert",
> +  [LOG_CRIT] = "crit",
> +  [LOG_ERR] = "err",
> +  [LOG_WARNING] = "warning",
> +  [LOG_NOTICE] = "notice",
> +  [LOG_INFO] = "info",
> +  [LOG_DEBUG] = "debug"
> +};
> +
> +static const char *const log_fields[] = {
> +  /* Trusted fields */
> +  "a", "_PID",
> +  "b", "_UID",
> +  "c", "_GID",
> +  "d", "_COMM",
> +  "e", "_EXE",
> +  "f", "_CMDLINE",
> +  "g", "_CAP_EFFECTIVE",
> +  "h", "_AUDIT_SESSION",
> +  "i", "_AUDIT_LOGINUID",
> +  "j", "_SYSTEMD_CGROUP",
> +  "k", "_SYSTEMD_SESSION",
> +  "l", "_SYSTEMD_UNIT",
> +  "m", "_SYSTEMD_USER_UNIT",
> +  "n", "_SYSTEMD_OWNER_UID",
> +  "o", "_SYSTEMD_SLICE",
> +  "p", "_SELINUX_CONTEXT",
> +  "q", "_SOURCE_REALTIME_TIMESTAMP",
> +  "r", "_BOOT_ID",
> +  "s", "_MACHINE_ID",
> +  "t", "_HOSTNAME",
> +  "u", "_TRANSPORT",
> +  /* User fields */
> +  "v", "MESSAGE",
> +  "w", "MESSAGE_ID",
> +  "x", "PRIORITY",
> +  "y", "CODE_FILE",
> +  "z", "CODE_LINE",
> +  "0", "CODE_FUNC",
> +  "1", "ERRNO",
> +  "2", "SYSLOG_FACILITY",
> +  "3", "SYSLOG_IDENTIFIER",
> +  "4", "SYSLOG_PID"
> +/* "~" means timestamp */
> +};
> +
> +static const char *
> +lookup_field (char field)
> +{
> +  size_t i = 0;
> +  for (i = 0; i < sizeof log_fields / sizeof *log_fields; i += 2) {
> +    if (field == log_fields[i][0])
> +      return log_fields[i+1];
> +  }
> +  return NULL;

This returns NULL if the field doesn't exist, but ..

> +}
> +
> +/* Fetch and print journal fields in specified order
> + * default is '~3axv'
> + */
> +int
> +journal_view (const char *fields)
> +{
> +  int r;
> +  int errors = 0;
> +  while ((r = guestfs_journal_next(g)) > 0) {
> +    CLEANUP_FREE_XATTR_LIST struct guestfs_xattr_list *xattrs = NULL;
> +    int64_t ts;
> +    int priority = LOG_INFO;
> +
> +    xattrs = guestfs_journal_get (g);
> +    if (xattrs == NULL)
> +      return -1;
> +
> +    size_t f_id = 0;
> +    for (f_id = 0; f_id < strlen (fields); ++f_id) {
> +      if (fields[f_id] == '~') {
> +        ts = guestfs_journal_get_realtime_usec (g);
> +        /* Timestamp. */
> +        if (ts >= 0) {
> +          char buf[64];
> +          time_t t = ts / 1000000;
> +          struct tm tm;
> +
> +          if (strftime (buf, sizeof buf, "%b %d %H:%M:%S",
> +                        localtime_r (&t, &tm)) <= 0) {
> +            fprintf (stderr, _("could not format journal entry timestamp\n"));
> +            errors++;
> +            continue;
> +          }
> +          fputs (buf, stdout);
> +        }
> +      }
> +
> +      const char *field_name, *field_val;
> +      size_t field_len;
> +      if ((field_name = lookup_field (fields[f_id]))) {
> +        field_val = get_journal_field (xattrs, field_name, &field_len);

... if lookup_field returns NULL, then get_journal_field will
segfault.

So I think lookup_field should print an error and call exit (EXIT_FAILURE)
if the field is not found.

> +        if (STREQ (field_name, "PRIORITY")) {
> +          if (field_val && *field_val >= '0' && *field_val <= '7')
> +            priority = *field_val - '0';
> +          printf (" %s:", log_level_table[priority]);
> +        } else if (field_val) {
> +          printf (" %.*s", (int)field_len, field_val);
> +        }
> +      }
> +    }
> +    printf ("\n");
> +  }
> +
> +  return errors > 0 ? -1 : 0;
> +}
> +
> +int
> +run_journal_view (const char *cmd, size_t argc, char *argv[])
> +{
> +  if (argc > 0)
> +    return journal_view (argv[0]);
> +  return journal_view ("~3axv");
> +}
> diff --git a/generator/actions.ml b/generator/actions.ml
> index 9f32cb5..4565f91 100644
> --- a/generator/actions.ml
> +++ b/generator/actions.ml
> @@ -12714,6 +12714,15 @@ environment variable.
>  See also L</hexdump>." };
>  
>    { defaults with
> +    name = "journal_view";
> +    shortdesc = "view journald log";
> +    longdesc = "  journal-view

You need to document a couple of things here:

(1) That there is an optional extra parameter ('fields').

(2) At least a summary of how to specify the fields parameter and what
the fields characters mean.

> +View journald log in format similar to journalctl.
> +
> +Use C<journal-open> first." };
> +
> +  { defaults with
>      name = "lcd";
>      shortdesc = "change working directory";
>      longdesc = " lcd directory

Rich.

-- 
Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones
Read my programming and virtualization blog: http://rwmj.wordpress.com
Fedora Windows cross-compiler. Compile Windows programs, test, and
build Windows installers. Over 100 libraries supported.
http://fedoraproject.org/wiki/MinGW




More information about the Libguestfs mailing list