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