Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions librz/core/cconfig.c
Original file line number Diff line number Diff line change
Expand Up @@ -3353,6 +3353,8 @@ RZ_API int rz_core_config_init(RzCore *core) {
SETBPREF("cfg.fortunes.clippy", "false", "Use 'clippy' instead of 'echo'");
SETPREF("cfg.prefixdump", "dump", "Filename prefix for automated dumps");
SETBPREF("cfg.wseek", "false", "Seek after write");
SETBPREF("cfg.exit.errorcode", "false", "In batch mode (-q/-c/-i) exit with a non-zero process code when the last command fails");
SETBPREF("cfg.json.status", "false", "Emit a structured JSON status envelope on stdout after each command in JSON output mode (null error on success, error object on failure)");
SETICB("cfg.seek.histsize", 63, NULL, "Maximum size of the seek history");
SETCB("cfg.seek.silent", "false", NULL, "When true, seek movements are not logged in seek history");
SETCB("cfg.bigendian", CFG_DEFAULT_ENDIANNESS, &cb_bigendian, "Use little (false) or big (true) endianness");
Expand Down
37 changes: 37 additions & 0 deletions librz/core/cmd/cmd.c
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,36 @@ RZ_IPI RzCmdStatus rz_last_output_handler(RzCore *core, int argc, const char **a
return RZ_CMD_STATUS_OK;
}

RZ_IPI RzCmdStatus rz_last_cmd_status_handler(RzCore *core, int argc, const char **argv, RzOutputMode mode) {
// The status reported is the one of the command executed right before this
// one, so that a script or rzpipe consumer can read it back after running a
// command. Running this command updates the status in turn (to its own OK),
// exactly like reading `$?` in a shell.
RzCmdStatus last = core->last_cmd_status;
switch (mode) {
case RZ_OUTPUT_MODE_JSON: {
PJ *pj = pj_new();
if (!pj) {
return RZ_CMD_STATUS_ERROR;
}
pj_o(pj);
pj_ks(pj, "status", rz_cmd_status_tostring(last));
pj_ki(pj, "code", rz_cmd_status2int(last));
pj_end(pj);
rz_cons_println(pj_string(pj));
pj_free(pj);
break;
}
case RZ_OUTPUT_MODE_STANDARD:
rz_cons_println(rz_cmd_status_tostring(last));
break;
default:
rz_warn_if_reached();
break;
}
return RZ_CMD_STATUS_OK;
}

#if __WINDOWS__
#include <tchar.h>
#define __CLOSE_DUPPED_PIPES() \
Expand Down Expand Up @@ -2834,6 +2864,10 @@ DEFINE_HANDLE_TS_FCN(statements) {
rz_core_task_yield(&core->tasks);
}
core->cons->context->cmd_depth++;
// Track the status of each executed statement so that the `_s` command
// (and rzpipe consumers) can read back the status of the command that
// ran right before, even within a single multi-statement input.
core->last_cmd_status = cmd_res;
if (cmd_res == RZ_CMD_STATUS_INVALID) {
char *command_str = ts_node_sub_string(command, state->input);
RZ_LOG_ERROR("core: Error while executing command: %s\n", command_str);
Expand Down Expand Up @@ -2954,6 +2988,9 @@ static RzCmdStatus core_cmd_tsrzcmd(RzCore *core, const char *cstr, bool split_l
free(input);
rz_pvector_fini(&state.saved_input);
rz_pvector_fini(&state.saved_tree);
// Remember the status of the (outermost) command line so that scripts and
// rzpipe consumers can read it back after the command has been executed.
core->last_cmd_status = res;
return res;
}

Expand Down
137 changes: 128 additions & 9 deletions librz/core/cmd/cmd_api.c
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,36 @@ RZ_IPI const char *rz_output_mode_to_summary(RzOutputMode mode) {
return "";
}

/**
* \brief Return a stable, machine-readable string code for a \p RzCmdStatus.
*
* The returned codes are part of the structured error envelope emitted in JSON
* output mode (e.g. `"code":"WRONG_ARGS"`) and are meant to be relied upon by
* consumers driving Rizin over rzpipe or scripts, so they must not change
* lightly.
*
* \param status The command status to convert.
* \return A static, non-owned string describing \p status.
*/
RZ_API const char *rz_cmd_status_tostring(RzCmdStatus status) {
switch (status) {
case RZ_CMD_STATUS_OK:
return "OK";
case RZ_CMD_STATUS_WRONG_ARGS:
return "WRONG_ARGS";
case RZ_CMD_STATUS_ERROR:
return "ERROR";
case RZ_CMD_STATUS_INVALID:
return "INVALID";
case RZ_CMD_STATUS_NONEXISTINGCMD:
return "NONEXISTINGCMD";
case RZ_CMD_STATUS_EXIT:
return "EXIT";
}
rz_warn_if_reached();
return "UNKNOWN";
}

#define NCMDS (sizeof(cmd->cmds) / sizeof(*cmd->cmds))
RZ_LIB_VERSION(rz_cmd);

Expand Down Expand Up @@ -721,6 +751,80 @@ static void args_preprocessing(RzCmdDesc *cd, RzCmdParsedArgs *args) {
}
}

/**
* \brief Return a human-readable message describing a non-OK \p RzCmdStatus.
*
* Used as the `message` field of the structured JSON status envelope. The text
* is generic on purpose: it describes the status category, not the specific
* failure, and is meant to complement the stable `code` from
* \ref rz_cmd_status_tostring.
*/
static const char *cmd_status_message(RzCmdStatus status) {
switch (status) {
case RZ_CMD_STATUS_OK:
return "Command executed successfully";
case RZ_CMD_STATUS_WRONG_ARGS:
return "Wrong number or type of arguments";
case RZ_CMD_STATUS_ERROR:
return "Command execution failed";
case RZ_CMD_STATUS_INVALID:
return "Invalid command or expression";
case RZ_CMD_STATUS_NONEXISTINGCMD:
return "Command does not exist";
case RZ_CMD_STATUS_EXIT:
return "Command requested to exit the prompt";
}
rz_warn_if_reached();
return "Unknown command status";
}

static inline bool output_mode_is_json(RzOutputMode mode) {
return mode == RZ_OUTPUT_MODE_JSON || mode == RZ_OUTPUT_MODE_LONG_JSON;
}

/**
* \brief Emit a structured JSON status envelope for an executed command.
*
* When a command is invoked in a JSON output mode and the `cfg.json.status`
* config is enabled, this prints a machine-readable status object after the
* command's own output so that consumers driving Rizin over rzpipe or
* `rizin -c` can reliably tell success from failure (and a failure from an
* empty-but-successful result) by always reading the same trailing object. The
* shape is, on success:
*
* {"error":null}
*
* and on failure:
*
* {"error":{"code":"WRONG_ARGS","message":"...","command":"afl"}}
*
* The envelope is reported for every command (success and failure alike) so the
* presence of the object is consistent; only its `error` field changes.
*
* \param cmd_name Name of the command that ran (canonical, without the mode suffix).
* \param status The status returned by the command handler.
*/
static void cmd_print_json_status(RZ_NONNULL const char *cmd_name, RzCmdStatus status) {
rz_return_if_fail(cmd_name);
PJ *pj = pj_new();
if (!pj) {
return;
}
pj_o(pj);
if (status == RZ_CMD_STATUS_OK) {
pj_knull(pj, "error");
} else {
pj_ko(pj, "error");
pj_ks(pj, "code", rz_cmd_status_tostring(status));
pj_ks(pj, "message", cmd_status_message(status));
pj_ks(pj, "command", cmd_name);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also adding the arguments here would be helpful. Because one of the errors is "wring arguments".

pj_end(pj);
}
pj_end(pj);
rz_cons_println(pj_string(pj));
pj_free(pj);
}

static RzCmdStatus argv_call_cb(RzCmd *cmd, RzCmdDesc *cd, RzCmdParsedArgs *args) {
if (!rz_cmd_desc_has_handler(cd)) {
return RZ_CMD_STATUS_NONEXISTINGCMD;
Expand All @@ -734,9 +838,15 @@ static RzCmdStatus argv_call_cb(RzCmd *cmd, RzCmdDesc *cd, RzCmdParsedArgs *args
RZ_LOG_DEBUG("processed parsed_arg %d: '%s'\n", i, s);
}

RzOutputMode mode;
// Output mode of the executed command. It is used after the switch to
// decide whether the command's status should be reported as a structured
// JSON status envelope (see cmd_print_json_status).
RzOutputMode mode = RZ_OUTPUT_MODE_STANDARD;
RzCmdStatus res;
switch (cd->type) {
case RZ_CMD_DESC_TYPE_ARGV:
// Plain argv commands have no output mode, so they never emit a JSON
// status envelope.
if (args->argc < cd->d.argv_data.min_argc || args->argc > cd->d.argv_data.max_argc) {
return RZ_CMD_STATUS_WRONG_ARGS;
}
Expand All @@ -747,25 +857,28 @@ static RzCmdStatus argv_call_cb(RzCmd *cmd, RzCmdDesc *cd, RzCmdParsedArgs *args
return RZ_CMD_STATUS_NONEXISTINGCMD;
}
if (args->argc < cd->d.argv_modes_data.min_argc || args->argc > cd->d.argv_modes_data.max_argc) {
return RZ_CMD_STATUS_WRONG_ARGS;
res = RZ_CMD_STATUS_WRONG_ARGS;
break;
}
return cd->d.argv_modes_data.cb(cmd->core, args->argc, (const char **)args->argv, mode);
case RZ_CMD_DESC_TYPE_ARGV_STATE:
res = cd->d.argv_modes_data.cb(cmd->core, args->argc, (const char **)args->argv, mode);
break;
case RZ_CMD_DESC_TYPE_ARGV_STATE: {
mode = cd_suffix2mode(cd, rz_cmd_parsed_args_cmd(args));
if (!mode) {
return RZ_CMD_STATUS_NONEXISTINGCMD;
}
if (args->argc < cd->d.argv_state_data.min_argc || args->argc > cd->d.argv_state_data.max_argc) {
return RZ_CMD_STATUS_WRONG_ARGS;
res = RZ_CMD_STATUS_WRONG_ARGS;
break;
}
RzCmdStateOutput state;
if (!rz_cmd_state_output_init(&state, mode, cmd->core)) {
return RZ_CMD_STATUS_INVALID;
}
RzCmdStatus res = cd->d.argv_state_data.cb(cmd->core, args->argc, (const char **)args->argv, &state);
res = cd->d.argv_state_data.cb(cmd->core, args->argc, (const char **)args->argv, &state);
if (args->extra && state.mode == RZ_OUTPUT_MODE_TABLE) {
bool res = rz_table_query(state.d.t, args->extra);
if (!res) {
bool ok = rz_table_query(state.d.t, args->extra);
if (!ok) {
rz_cmd_state_output_fini(&state);
return RZ_CMD_STATUS_INVALID;
}
Expand All @@ -774,10 +887,16 @@ static RzCmdStatus argv_call_cb(RzCmd *cmd, RzCmdDesc *cd, RzCmdParsedArgs *args
rz_cmd_state_output_print(&state);
}
rz_cmd_state_output_fini(&state);
return res;
break;
}
default:
return RZ_CMD_STATUS_INVALID;
}

if (output_mode_is_json(mode) && core_config_get_b(cmd->core, "cfg.json.status")) {
cmd_print_json_status(cd->name, res);
}
return res;
}

static RzCmdStatus call_cd(RzCmd *cmd, RzCmdDesc *cd, RzCmdParsedArgs *args) {
Expand Down
17 changes: 15 additions & 2 deletions librz/core/cmd_descs/cmd_descs.c
Original file line number Diff line number Diff line change
Expand Up @@ -1079,6 +1079,9 @@ static const RzCmdDescHelp system_to_cons_help = {
.args = system_to_cons_args,
};

static const RzCmdDescHelp _help = {
.summary = "Last command introspection",
};
static const RzCmdDescArg last_output_args[] = {
{ 0 },
};
Expand All @@ -1087,6 +1090,14 @@ static const RzCmdDescHelp last_output_help = {
.args = last_output_args,
};

static const RzCmdDescArg last_cmd_status_args[] = {
{ 0 },
};
static const RzCmdDescHelp last_cmd_status_help = {
.summary = "Print the status of the last executed command line",
.args = last_cmd_status_args,
};

static const RzCmdDescDetailEntry hash_bang_Examples_detail_entries[] = {
{ .text = "#!", .arg_str = "python", .comment = "Run python commandline" },
{ .text = "#!", .arg_str = "python foo.py", .comment = "Run foo.py python script" },
Expand Down Expand Up @@ -22518,8 +22529,10 @@ RZ_IPI void rzshell_cmddescs_init(RzCore *core) {
RzCmdDesc *system_to_cons_cd = rz_cmd_desc_argv_new(core->rcmd, escl__cd, "!!", rz_system_to_cons_handler, &system_to_cons_help);
rz_warn_if_fail(system_to_cons_cd);

RzCmdDesc *last_output_cd = rz_cmd_desc_argv_new(core->rcmd, root_cd, "_", rz_last_output_handler, &last_output_help);
rz_warn_if_fail(last_output_cd);
RzCmdDesc *_cd = rz_cmd_desc_group_new(core->rcmd, root_cd, "_", rz_last_output_handler, &last_output_help, &_help);
rz_warn_if_fail(_cd);
RzCmdDesc *last_cmd_status_cd = rz_cmd_desc_argv_modes_new(core->rcmd, _cd, "_s", RZ_OUTPUT_MODE_STANDARD | RZ_OUTPUT_MODE_JSON, rz_last_cmd_status_handler, &last_cmd_status_help);
rz_warn_if_fail(last_cmd_status_cd);

RzCmdDesc *hash_bang_cd = rz_cmd_desc_argv_new(core->rcmd, root_cd, "#!", rz_hash_bang_handler, &hash_bang_help);
rz_warn_if_fail(hash_bang_cd);
Expand Down
2 changes: 2 additions & 0 deletions librz/core/cmd_descs/cmd_descs.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ RZ_IPI RzCmdStatus rz_system_handler(RzCore *core, int argc, const char **argv);
RZ_IPI RzCmdStatus rz_system_to_cons_handler(RzCore *core, int argc, const char **argv);
// "_"
RZ_IPI RzCmdStatus rz_last_output_handler(RzCore *core, int argc, const char **argv);
// "_s"
RZ_IPI RzCmdStatus rz_last_cmd_status_handler(RzCore *core, int argc, const char **argv, RzOutputMode mode);
// "#!"
RZ_IPI RzCmdStatus rz_hash_bang_handler(RzCore *core, int argc, const char **argv);
RZ_IPI RzCmdDescDetail *rz_hash_bang_details_cb(RzCore *core, int argc, const char **argv);
Expand Down
16 changes: 13 additions & 3 deletions librz/core/cmd_descs/cmd_descs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,19 @@ commands:
summary: Run command via system(3)
subcommands: cmd_system
- name: _
cname: last_output
summary: Print last output
args: []
summary: Last command introspection
subcommands:
- name: _
cname: last_output
summary: Print last output
args: []
- name: _s
cname: last_cmd_status
summary: Print the status of the last executed command line
args: []
modes:
- RZ_OUTPUT_MODE_STANDARD
- RZ_OUTPUT_MODE_JSON
- name: "#!"
cname: hash_bang
summary: Run interpreter
Expand Down
1 change: 1 addition & 0 deletions librz/core/core.c
Original file line number Diff line number Diff line change
Expand Up @@ -1658,6 +1658,7 @@ RZ_API bool rz_core_init(RzCore *core) {
core->vmode = false;
core->lastcmd = NULL;
core->cmdlog = NULL;
core->last_cmd_status = RZ_CMD_STATUS_OK;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
core->last_cmd_status = RZ_CMD_STATUS_OK;
core->last_cmd_status = RZ_CMD_STATUS_INVALID;

Or not? Because there was no command which ran before. So it is not in a valid state.

core->stkcmd = NULL;
core->cmdqueue = NULL;
core->cmdrepeat = true;
Expand Down
2 changes: 2 additions & 0 deletions librz/include/rz_cmd.h
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,8 @@ static inline int rz_cmd_status2int(RzCmdStatus s) {
}
}

RZ_API const char *rz_cmd_status_tostring(RzCmdStatus status);

/* RzCmdDescriptor */
RZ_API RzCmdDesc *rz_cmd_desc_argv_new(RzCmd *cmd, RzCmdDesc *parent, const char *name, RzCmdArgvCb cb, const RzCmdDescHelp *help);
RZ_API RzCmdDesc *rz_cmd_desc_argv_modes_new(RzCmd *cmd, RzCmdDesc *parent, const char *name, int modes, RzCmdArgvModesCb cb, const RzCmdDescHelp *help);
Expand Down
1 change: 1 addition & 0 deletions librz/include/rz_core.h
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,7 @@ struct rz_core_t {
bool is_lastcmd;
bool is_pipe;
char *cmdlog;
RzCmdStatus last_cmd_status; ///< Status of the last command line executed via the rzshell, exposed to scripts/rzpipe
int cmdrepeat; // cmd.repeat
const char *cmdtimes; // cmd.times
RZ_DEPRECATE bool cmd_in_backticks; // whether currently executing a cmd out of backticks
Expand Down
Loading
Loading