From b0d8f2d983cb25d1035fae1cd7de214dd67809b4 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Tue, 21 Mar 2023 13:03:42 -0400 Subject: [PATCH] Add SHELL_ERROR and SHELL_EXIT_CODE magic variables to psql. These are set after a \! command or a backtick substitution. SHELL_ERROR is just "true" for error (nonzero exit status) or "false" for success, while SHELL_EXIT_CODE records the actual exit status following standard shell/system(3) conventions. Corey Huinker, reviewed by Maxim Orlov and myself Discussion: https://postgr.es/m/CADkLM=cWao2x2f+UDw15W1JkVFr_bsxfstw=NGea7r9m4j-7rQ@mail.gmail.com --- doc/src/sgml/ref/psql-ref.sgml | 28 ++++++++++++++++++++++++++++ src/bin/psql/command.c | 15 +++++++++++++++ src/bin/psql/help.c | 4 ++++ src/bin/psql/psqlscanslash.l | 24 +++++++++++++++++++++--- src/common/wait_error.c | 19 +++++++++++++++++++ src/include/port.h | 1 + 6 files changed, 88 insertions(+), 3 deletions(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 7b8ae9fac30..29bbec21886 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -4267,6 +4267,34 @@ bar + + SHELL_ERROR + + + true if the last shell command + failed, false if it succeeded. + This applies to shell commands invoked via the \! + meta-command or backquote (`) expansion. + See also SHELL_EXIT_CODE. + + + + + + SHELL_EXIT_CODE + + + The exit status returned by the last shell command. + 0–127 represent program exit codes, 128–255 + indicate termination by a signal, and -1 indicates failure + to launch a program or to collect its exit status. + This applies to shell commands invoked via the \! + meta-command or backquote (`) expansion. + See also SHELL_ERROR. + + + + SHOW_ALL_RESULTS diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index 61ec049f054..d7731234b63 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -5041,6 +5041,21 @@ do_shell(const char *command) else result = system(command); + if (result == 0) + { + SetVariable(pset.vars, "SHELL_EXIT_CODE", "0"); + SetVariable(pset.vars, "SHELL_ERROR", "false"); + } + else + { + int exit_code = wait_result_to_exit_code(result); + char buf[32]; + + snprintf(buf, sizeof(buf), "%d", exit_code); + SetVariable(pset.vars, "SHELL_EXIT_CODE", buf); + SetVariable(pset.vars, "SHELL_ERROR", "true"); + } + if (result == 127 || result == -1) { pg_log_error("\\!: failed"); diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c index e45c4aaca5a..48fd51592aa 100644 --- a/src/bin/psql/help.c +++ b/src/bin/psql/help.c @@ -451,6 +451,10 @@ helpVariables(unsigned short int pager) HELP0(" SERVER_VERSION_NAME\n" " SERVER_VERSION_NUM\n" " server's version (in short string or numeric format)\n"); + HELP0(" SHELL_ERROR\n" + " true if the last shell command failed, false if it succeeded\n"); + HELP0(" SHELL_EXIT_CODE\n" + " exit status of the last shell command\n"); HELP0(" SHOW_ALL_RESULTS\n" " show all results of a combined query (\\;) instead of only the last\n"); HELP0(" SHOW_CONTEXT\n" diff --git a/src/bin/psql/psqlscanslash.l b/src/bin/psql/psqlscanslash.l index 8449ee1a52e..fa2d0b2a5f4 100644 --- a/src/bin/psql/psqlscanslash.l +++ b/src/bin/psql/psqlscanslash.l @@ -19,6 +19,8 @@ #include "postgres_fe.h" #include "psqlscanslash.h" +#include "settings.h" + #include "common/logging.h" #include "fe_utils/conditional.h" @@ -772,6 +774,7 @@ evaluate_backtick(PsqlScanState state) PQExpBufferData cmd_output; FILE *fd; bool error = false; + int exit_code = 0; char buf[512]; size_t result; @@ -783,6 +786,7 @@ evaluate_backtick(PsqlScanState state) { pg_log_error("%s: %m", cmd); error = true; + exit_code = -1; } if (!error) @@ -800,10 +804,19 @@ evaluate_backtick(PsqlScanState state) } while (!feof(fd)); } - if (fd && pclose(fd) == -1) + if (fd) { - pg_log_error("%s: %m", cmd); - error = true; + /* + * Although pclose's result always sets SHELL_EXIT_CODE, we + * historically have abandoned the backtick substitution only if it + * returns -1. + */ + exit_code = pclose(fd); + if (exit_code == -1) + { + pg_log_error("%s: %m", cmd); + error = true; + } } if (PQExpBufferDataBroken(cmd_output)) @@ -826,5 +839,10 @@ evaluate_backtick(PsqlScanState state) appendBinaryPQExpBuffer(output_buf, cmd_output.data, cmd_output.len); } + /* And finally, set the shell error variables */ + snprintf(buf, sizeof(buf), "%d", wait_result_to_exit_code(exit_code)); + SetVariable(pset.vars, "SHELL_EXIT_CODE", buf); + SetVariable(pset.vars, "SHELL_ERROR", (exit_code == 0) ? "false" : "true"); + termPQExpBuffer(&cmd_output); } diff --git a/src/common/wait_error.c b/src/common/wait_error.c index 4a3c3c61af1..a90b745f077 100644 --- a/src/common/wait_error.c +++ b/src/common/wait_error.c @@ -127,3 +127,22 @@ wait_result_is_any_signal(int exit_status, bool include_command_not_found) return true; return false; } + +/* + * Return the shell exit code (normally 0 to 255) that corresponds to the + * given wait status. The argument is a wait status as returned by wait(2) + * or waitpid(2), which also applies to pclose(3) and system(3). To support + * the latter two cases, we pass through "-1" unchanged. + */ +int +wait_result_to_exit_code(int exit_status) +{ + if (exit_status == -1) + return -1; /* failure of pclose() or system() */ + if (WIFEXITED(exit_status)) + return WEXITSTATUS(exit_status); + if (WIFSIGNALED(exit_status)) + return 128 + WTERMSIG(exit_status); + /* On many systems, this is unreachable */ + return -1; +} diff --git a/src/include/port.h b/src/include/port.h index e66193bed9f..a88d403483e 100644 --- a/src/include/port.h +++ b/src/include/port.h @@ -495,6 +495,7 @@ extern char *escape_single_quotes_ascii(const char *src); extern char *wait_result_to_str(int exitstatus); extern bool wait_result_is_signal(int exit_status, int signum); extern bool wait_result_is_any_signal(int exit_status, bool include_command_not_found); +extern int wait_result_to_exit_code(int exit_status); /* * Interfaces that we assume all Unix system have. We retain individual macros -- 2.39.5