[Libguestfs] [PATCH v6 2/2] fish: add journal-view command (RHBZ#988100)

Maros Zatko mzatko at redhat.com
Fri Oct 16 17:05:49 UTC 2015


Let the user view the journald log from a guest, with a format similar
to what journalctl uses.

Fixes RFE: journal reader in guestfish
---
 .gnulib                    |   2 +-
 cat/log.c                  |   2 +-
 fish/journal.c             | 133 ++++++++++++++++++++++-----------------------
 fish/journal.h             |   2 +-
 generator/Makefile.am      |   6 +-
 generator/actions.ml       |  36 ++++++++++++
 generator/journalfields.ml |  87 +++++++++++++++++++++++++++++
 generator/main.ml          |   3 +
 po/POTFILES                |   1 +
 src/guestfs-internal.h     |   5 ++
 src/proto.c                |  14 +++++
 11 files changed, 219 insertions(+), 72 deletions(-)
 create mode 100644 generator/journalfields.ml

diff --git a/.gnulib b/.gnulib
index eda101a..3ca9a53 160000
--- a/.gnulib
+++ b/.gnulib
@@ -1 +1 @@
-Subproject commit eda101a012571c3d043380c959d0aa04de40e721
+Subproject commit 3ca9a533c245fb472b686b30dd9645855f2be3ba
diff --git a/cat/log.c b/cat/log.c
index b092667..9d61412 100644
--- a/cat/log.c
+++ b/cat/log.c
@@ -280,7 +280,7 @@ do_log_journal (void)
   if (guestfs_journal_open (g, JOURNAL_DIR) == -1)
     return -1;
 
-  if (journal_view () == -1)
+  if (journal_view ("~3axv") == -1)
     return -1;
 
   if (guestfs_journal_close (g) == -1)
diff --git a/fish/journal.c b/fish/journal.c
index 15d058a..d1ed96c 100644
--- a/fish/journal.c
+++ b/fish/journal.c
@@ -30,6 +30,7 @@
 
 #include "fish.h"
 #include "journal.h"
+#include "journal-fields.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
@@ -66,87 +67,85 @@ static const char *const log_level_table[] = {
   [LOG_DEBUG] = "debug"
 };
 
+static const char *
+lookup_field (char field)
+{
+  size_t i = 0;
+  for (i = 0; i < sizeof journal_fields / sizeof *journal_fields; ++i) {
+    if (field == journal_fields[i].field)
+      return journal_fields[i].name;
+  }
+  return NULL;
+}
+
+/* Fetch and print journal fields in specified order
+ * default is '~3axv'
+ */
 int
-journal_view (void)
+journal_view (const char *fields)
 {
-  int r;
-  unsigned errors = 0;
+  int errors = 0;
+  guestfs_clear_user_cancelled (g);
 
-  while ((r = guestfs_journal_next (g)) > 0) {
+  while (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)
-     */
+    int priority = LOG_INFO;
 
     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, _("could not format journal entry timestamp\n"));
-        errors++;
-        continue;
+    size_t f_id = 0;
+    for (f_id = 0; f_id < strlen (fields); ++f_id) {
+      if (guestfs_is_user_cancelled (g))
+        return errors > 0 ? -1 : 0;
+
+      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);
+        }
       }
-      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);
 
+      const char *field_name, *field_val;
+      size_t field_len;
+      field_name = lookup_field (fields[f_id]);
+      if (field_name != NULL) {
+        field_val = get_journal_field (xattrs, field_name, &field_len);
+        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);
+        }
+      } else {
+        fprintf (stderr, _("unknown journal field '%c'\n"), fields[f_id]);
+        return -1;
+      }
+    }
     printf ("\n");
   }
-  if (r == -1)                  /* error from guestfs_journal_next */
-    return -1;
-
-  if (guestfs_journal_close (g) == -1)
-    return -1;
 
   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/fish/journal.h b/fish/journal.h
index 556324e..c76b0f0 100644
--- a/fish/journal.h
+++ b/fish/journal.h
@@ -22,5 +22,5 @@
 #define JOURNAL_H
 
 /* in journal.c */
-extern int journal_view (void);
+extern int journal_view (const char *fields);
 #endif /* JOURNAL_H */
diff --git a/generator/Makefile.am b/generator/Makefile.am
index a3fe50d..b7e4582 100644
--- a/generator/Makefile.am
+++ b/generator/Makefile.am
@@ -37,6 +37,7 @@ sources = \
 	haskell.ml \
 	java.ml \
 	lua.ml \
+	journalfields.ml \
 	main.ml \
 	ocaml.ml \
 	optgroups.ml \
@@ -60,13 +61,14 @@ sources = \
 objects = \
 	types.cmo \
 	utils.cmo \
+	pr.cmo \
+	docstrings.cmo \
+	journalfields.cmo \
 	actions.cmo \
 	structs.cmo \
 	optgroups.cmo \
 	prepopts.cmo \
 	events.cmo \
-	pr.cmo \
-	docstrings.cmo \
 	checks.cmo \
 	c.cmo \
 	xdr.cmo \
diff --git a/generator/actions.ml b/generator/actions.ml
index 274ef3f..165edd6 100644
--- a/generator/actions.ml
+++ b/generator/actions.ml
@@ -3059,6 +3059,22 @@ the default.  Else F</var/tmp> is the default." };
 Get the directory used by the handle to store the appliance cache." };
 
   { defaults with
+    name = "clear_user_cancelled"; added = (1, 31, 3);
+    style = RErr, [], [];
+    blocking = false; wrapper = false;
+    shortdesc = "clears user cancellation flag";
+    longdesc = "\
+Clears the cancellation flag." };
+
+  { defaults with
+    name = "is_user_cancelled"; added = (1, 31, 3);
+    style = RErr, [], [];
+    blocking = false; wrapper = false;
+    shortdesc = "check if current upload or download operation is cancelled";
+    longdesc = "\
+Read the cancellation flag." };
+
+  { defaults with
     name = "user_cancel"; added = (1, 11, 18);
     style = RErr, [], [];
     blocking = false; wrapper = false;
@@ -12957,6 +12973,26 @@ environment variable.
 See also L</hexdump>." };
 
   { defaults with
+    name = "journal_view";
+    shortdesc = "view journald log";
+    longdesc = "  journal-view [FORMAT]
+
+View journald log in format similar to L<journalctl(1)>.
+
+=over
+
+"
+^ (Journalfields.ops_to_pod_string ()) ^
+"
+=back
+
+The default format is C<~3axv>.
+
+For fields description see L<systemd.journal-fields>(7).
+
+Use C<guestfs_journal_open> first." };
+
+  { defaults with
     name = "lcd";
     shortdesc = "change working directory";
     longdesc = " lcd directory
diff --git a/generator/journalfields.ml b/generator/journalfields.ml
new file mode 100644
index 0000000..4746457
--- /dev/null
+++ b/generator/journalfields.ml
@@ -0,0 +1,87 @@
+(* libguestfs
+ * Copyright (C) 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
+ *)
+
+(* Please read generator/README first. *)
+
+open Printf
+
+open Docstrings
+open Pr
+
+open Char
+open List
+
+(* Arguments used by journal-view command *)
+
+type op_type = (char * string) list (* option, option name*)
+
+let ops = [
+  (* 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");
+  ('~', "timestamp")
+]
+
+let ops_to_pod_string () =
+  String.concat ""
+    (map (fun (a,b) -> "=item " ^ escaped a ^ " " ^ b ^ "\n\n") ops)
+
+let generate_journal_fields_h () =
+  generate_header CStyle LGPLv2plus;
+  pr "#include <config.h>\n";
+  pr "\n";
+  pr "#ifndef JOURNAL_FIELDS_H\n";
+  pr "#define JOURNAL_FIELDS_H\n";
+  pr "\n";
+  pr "static const struct JournalField {\n";
+  pr "  char field;\n";
+  pr "  const char *name;\n";
+  pr "} journal_fields[] = {\n";
+  iter (fun (a,b) -> pr "  {'%c', \"%s\"},\n" a b) ops;
+  pr "};\n\n";
+  pr "#endif /* JOURNAL_FIELDS_H */\n"
diff --git a/generator/main.ml b/generator/main.ml
index 1e0e7d6..b78de0a 100644
--- a/generator/main.ml
+++ b/generator/main.ml
@@ -46,6 +46,7 @@ open Golang
 open Bindtests
 open Errnostring
 open Customize
+open Journalfields
 
 let perror msg = function
   | Unix_error (err, _, _) ->
@@ -212,6 +213,8 @@ Run it from the top source directory using the command
   output_to "customize/customize-synopsis.pod" generate_customize_synopsis_pod;
   output_to "customize/customize-options.pod" generate_customize_options_pod;
 
+  output_to "fish/journal-fields.h" generate_journal_fields_h;
+
   (* Generate the list of files generated -- last. *)
   printf "generated %d lines of code\n" (get_lines_generated ());
   let files = List.sort compare (get_files_generated ()) in
diff --git a/po/POTFILES b/po/POTFILES
index cd2c437..6d14181 100644
--- a/po/POTFILES
+++ b/po/POTFILES
@@ -143,6 +143,7 @@ fish/glob.c
 fish/help.c
 fish/hexedit.c
 fish/inspect.c
+fish/journal.c
 fish/keys.c
 fish/lcd.c
 fish/man.c
diff --git a/src/guestfs-internal.h b/src/guestfs-internal.h
index 49da6fe..7b6ccff 100644
--- a/src/guestfs-internal.h
+++ b/src/guestfs-internal.h
@@ -461,6 +461,11 @@ struct guestfs_h
    */
   int user_cancel;
 
+  /* User cancelled transfer. Similar to user_cancel,
+   * it is cleared after calling guestfs_clear_user_cancelled.
+   */
+  int was_user_cancel;
+
   struct timeval launch_t;      /* The time that we called guestfs_launch. */
 
   /* Used by bindtests. */
diff --git a/src/proto.c b/src/proto.c
index efe9dfb..f730b15 100644
--- a/src/proto.c
+++ b/src/proto.c
@@ -872,5 +872,19 @@ int
 guestfs_user_cancel (guestfs_h *g)
 {
   g->user_cancel = 1;
+  g->was_user_cancel = 1;
   return 0;
 }
+
+int
+guestfs_clear_user_cancelled (guestfs_h *g)
+{
+  g->was_user_cancel = 0;
+  return 0;
+}
+
+int
+guestfs_is_user_cancelled (guestfs_h *g)
+{
+  return g->was_user_cancel;
+}
-- 
1.9.3




More information about the Libguestfs mailing list