From 113ad9a5365375ff28bd2c52d847470d937b76cd Mon Sep 17 00:00:00 2001
From: Sungju Kwon <sungju.kwon@gmail.com>
Date: Mon, 15 Jul 2019 14:39:03 +1000
Subject: [PATCH] Allows to change the error output direction

Currently, the error() is always printing the output to the console
through 'stdout'. This does not follow redirection which is good when
you want to know error while redirecting commands output to a file.
However, there are situations that you want to hide error messages or
redirect it into somewhere else.

Using 'set error' command, it can be changed to three different
destination - default behaviour 'default', following redirection 'redirect',
or a custom file path.

        crash> set error
        error: default
        crash> sym 0x523 > /dev/null
        sym: invalid address: 0x523
        crash> set error redirect
        error: redirect
        crash> sym 0x523 > /dev/null
        crash> set error /tmp/err.log
        error: /tmp/err.log
        crash> sym 0x523 > /dev/null
        crash> set error default
        error: default
        crash> sym 0x523 > /dev/null
        sym: invalid address: 0x523
---
 defs.h  |  3 +++
 help.c  |  8 +++++++
 main.c  |  3 +++
 tools.c | 80 +++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
 4 files changed, 82 insertions(+), 12 deletions(-)

diff --git a/defs.h b/defs.h
index ceb6eb7..3a1234a 100644
--- a/defs.h
+++ b/defs.h
@@ -553,6 +553,8 @@ struct program_context {
 	ulong scope;			/* optional text context address */
 	ulong nr_hash_queues;		/* hash queue head count */
 	char *(*read_vmcoreinfo)(const char *);
+        FILE *error_fp;                 /* error() message direction */
+        char *error_path;               /* stderr path information */
 };
 
 #define READMEM  pc->readmem
@@ -4955,6 +4957,7 @@ void exec_args_input_file(struct command_table_entry *, struct args_input_file *
 /*
  *  tools.c
  */
+FILE *set_error(char *target);
 int __error(int, char *, ...);
 #define error __error               /* avoid conflict with gdb error() */
 int console(char *, ...);
diff --git a/help.c b/help.c
index 484539e..c1b3fba 100644
--- a/help.c
+++ b/help.c
@@ -1093,6 +1093,13 @@ char *help_set[] = {
 "         redzone  on | off     if on, CONFIG_SLUB object addresses displayed by",
 "                               the kmem command will point to the SLAB_RED_ZONE",
 "                               padding inserted at the beginning of the object.", 
+"error  default | redirect | <path>  set the direction of error output. 'default' always",
+"                               print error on console as well as write into",
+"                               redirected path. 'redirect' prints into the redirected",
+"                               path only if redirected to file or pipe. Otherwise,",
+"                               it prints on console just like 'default'.",
+"                               '<path>' is a file path which error message goes",
+"                               and it does not print on console.",
 " ",
 "  Internal variables may be set in four manners:\n",
 "    1. entering the set command in $HOME/.%src.",
@@ -1144,6 +1151,7 @@ char *help_set[] = {
 "             scope: (not set)",
 "           offline: show",
 "           redzone: on",
+"             error: default",
 " ",
 "  Show the current context:\n",
 "    %s> set",
diff --git a/main.c b/main.c
index 83ccd31..c95ffcc 100644
--- a/main.c
+++ b/main.c
@@ -1085,6 +1085,7 @@ setup_environment(int argc, char **argv)
 	 *  to pipes or output files.
 	 */
 	fp = stdout;
+        set_error("default");
 
 	/*
 	 *  Start populating the program_context structure.  It's used so
@@ -1725,6 +1726,8 @@ dump_program_context(void)
 		pc->scope ? "" : "(not set)");
 	fprintf(fp, "   nr_hash_queues: %ld\n", pc->nr_hash_queues);
 	fprintf(fp, "  read_vmcoreinfo: %lx\n", (ulong)pc->read_vmcoreinfo);
+        fprintf(fp, "         error_fp: %lx\n", (ulong)pc->error_fp);
+        fprintf(fp, "       error_path: %s\n", pc->error_path);
 }
 
 char *
diff --git a/tools.c b/tools.c
index 5c0e63e..be015ab 100644
--- a/tools.c
+++ b/tools.c
@@ -38,6 +38,43 @@ static void print_value(struct req_entry *, unsigned int, ulong, unsigned int);
 static struct req_entry *fill_member_offsets(char *);
 static void dump_struct_members_fast(struct req_entry *, int, ulong);
 
+FILE *
+set_error(char *target)
+{
+        FILE *tmp_fp = NULL;
+        char *tmp_str = NULL;
+
+        if (pc->error_path != NULL && STREQ(target, pc->error_path))
+                return pc->error_fp;
+
+        tmp_str = malloc(strlen(target) + 1);
+        if (tmp_str == NULL)
+                return NULL;
+        strcpy(tmp_str, target);
+
+        if (STREQ(target, "default")) {
+                tmp_fp = stdout;
+        } else if (STREQ(target, "redirect")) {
+                tmp_fp = fp;
+        } else {
+                tmp_fp = fopen(target, "a");
+                if (tmp_fp == NULL) {
+                        error(INFO, "invalid path: %s\n", target);
+                        return NULL;
+                }
+        }
+
+        if (pc->error_fp != NULL && pc->error_fp != stdout && pc->error_fp != fp)
+                fclose(pc->error_fp);
+
+        if (pc->error_path)
+                free(pc->error_path);
+        pc->error_fp = tmp_fp;
+        pc->error_path = tmp_str;
+        return pc->error_fp;
+}
+
+
 /*
  *  General purpose error reporting routine.  Type INFO prints the message
  *  and returns.  Type FATAL aborts the command in progress, and longjmps
@@ -58,6 +95,9 @@ __error(int type, char *fmt, ...)
         void *retaddr[NUMBER_STACKFRAMES] = { 0 };
 	va_list ap;
 
+        if (STREQ(pc->error_path, "redirect"))
+                pc->error_fp = fp;
+
 	if (CRASHDEBUG(1) || (pc->flags & DROP_CORE)) {
 		SAVE_RETURN_ADDRESS(retaddr);
 		console("error() trace: %lx => %lx => %lx => %lx\n",
@@ -69,7 +109,7 @@ __error(int type, char *fmt, ...)
         va_end(ap);
 
 	if (!fmt && FATAL_ERROR(type)) {
-		fprintf(stdout, "\n");
+		fprintf(pc->error_fp, "\n");
 		clean_exit(1);
 	}
 
@@ -85,7 +125,8 @@ __error(int type, char *fmt, ...)
 	else
 		spacebuf = NULL;
 
-	if (pc->stdpipe) {
+	if (pc->stdpipe && 
+            (STREQ(pc->error_path, "default") || STREQ(pc->error_path, "redirect"))) {
 		fprintf(pc->stdpipe, "%s%s%s %s%s", 
 			new_line ? "\n" : "", 
 			type == CONT ? spacebuf : pc->curcmd, 
@@ -95,25 +136,26 @@ __error(int type, char *fmt, ...)
 			buf);
 		fflush(pc->stdpipe);
 	} else { 
-		fprintf(stdout, "%s%s%s %s%s", 
+		fprintf(pc->error_fp, "%s%s%s %s%s",
 			new_line || end_of_line ? "\n" : "",
 			type == WARNING ? "WARNING" : 
 			type == NOTE ? "NOTE" : 
 			type == CONT ? spacebuf : pc->curcmd,
 			type == CONT ? " " : ":",
 			buf, end_of_line ? "\n" : "");
-		fflush(stdout);
+		fflush(pc->error_fp);
 	}
 
-        if ((fp != stdout) && (fp != pc->stdpipe) && (fp != pc->tmpfile)) {
+        if ((STREQ(pc->error_path, "default")) &&
+            (fp != stdout) && (fp != pc->stdpipe) && (fp != pc->tmpfile)) {
                 fprintf(fp, "%s%s%s %s", new_line ? "\n" : "",
-			type == WARNING ? "WARNING" : 
-			type == NOTE ? "NOTE" : 
-			type == CONT ? spacebuf : pc->curcmd, 
-			type == CONT ? " " : ":",
-			buf);
-		fflush(fp);
-	}
+                        type == WARNING ? "WARNING" :
+                        type == NOTE ? "NOTE" :
+                        type == CONT ? spacebuf : pc->curcmd,
+                        type == CONT ? " " : ":",
+                        buf);
+                fflush(fp);
+        }
 
 	if ((pc->flags & DROP_CORE) && (type != NOTE)) {
 		dump_trace(retaddr);
@@ -2486,6 +2528,19 @@ cmd_set(void)
 			}
 			return;
 
+                } else if (STREQ(args[optind], "error")) {
+                        if (args[optind+1]) {
+                                optind++;
+                                if (set_error(args[optind]) == NULL)
+                                        return;
+                        }
+
+                        if (runtime) {
+                                fprintf(fp, "error: %s\n",
+                                        pc->error_path);
+                        }
+                        return;
+
 		} else if (XEN_HYPER_MODE()) {
 			error(FATAL, "invalid argument for the Xen hypervisor\n");
 		} else if (pc->flags & MINIMAL_MODE) {
@@ -2593,6 +2648,7 @@ show_options(void)
 		fprintf(fp, "(not set)\n");
 	fprintf(fp, "       offline: %s\n", pc->flags2 & OFFLINE_HIDE ? "hide" : "show");
 	fprintf(fp, "       redzone: %s\n", pc->flags2 & REDZONE ? "on" : "off");
+	fprintf(fp, "         error: %s\n", pc->error_path);
 }
 
 
-- 
1.8.3.1

