From d9bc97bda6e4750af2fbbfcb51ddb6b2c04c277b Mon Sep 17 00:00:00 2001 From: wm4 Date: Sat, 12 May 2018 15:14:07 +0200 Subject: command: add a subprocess command This supports named arguments. It benefits from the infrastructure of async commands. The plan is to reimplement Lua's utils.subprocess() on top of it. --- DOCS/man/input.rst | 5 +++ TOOLS/lua/command-test.lua | 5 +++ input/cmd.h | 1 + options/m_option.h | 1 + player/command.c | 96 ++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 108 insertions(+) diff --git a/DOCS/man/input.rst b/DOCS/man/input.rst index 567d2e0de8..6d05c5fc5d 100644 --- a/DOCS/man/input.rst +++ b/DOCS/man/input.rst @@ -305,6 +305,11 @@ List of Input Commands command line arguments following. This is just like the ``run`` command argument list. + The first array entry is either an absolute path to the executable, or + a filename with no path components, in which case the ``PATH`` + environment variable. On Unix, this is equivalent to ``posix_spawnp`` + and ``execvp`` behavior. + ``playback_only`` (``MPV_FORMAT_FLAG``) Boolean indicating whether the process should be killed when playback terminates (optional, default: yes). If enabled, stopping playback diff --git a/TOOLS/lua/command-test.lua b/TOOLS/lua/command-test.lua index 5fb8c7b0d7..94a91c2f57 100644 --- a/TOOLS/lua/command-test.lua +++ b/TOOLS/lua/command-test.lua @@ -50,4 +50,9 @@ mp.observe_property("vo-configured", "bool", function(_, v) function(res, val, err) print("done err scr.: " .. join(" ", {res, val, err})) end) + + mp.command_native_async({name = "subprocess", args = {"sh", "-c", "echo hi && sleep 10s"}, capture_stdout = true}, + function(res, val, err) + print("done subprocess: " .. join(" ", {res, val, err})) + end) end) diff --git a/input/cmd.h b/input/cmd.h index 01f2d8356f..0e65565252 100644 --- a/input/cmd.h +++ b/input/cmd.h @@ -80,6 +80,7 @@ struct mp_cmd_arg { const struct m_option *type; union { int i; + int64_t i64; float f; double d; char *s; diff --git a/options/m_option.h b/options/m_option.h index 584ad13959..9d9f201871 100644 --- a/options/m_option.h +++ b/options/m_option.h @@ -568,6 +568,7 @@ extern const char m_option_path_separator; #define OPTDEF_STR(s) .defval = (void *)&(char * const){s} #define OPTDEF_INT(i) .defval = (void *)&(const int){i} +#define OPTDEF_INT64(i) .defval = (void *)&(const int64_t){i} #define OPTDEF_FLOAT(f) .defval = (void *)&(const float){f} #define OPTDEF_DOUBLE(d) .defval = (void *)&(const double){d} diff --git a/player/command.c b/player/command.c index fb0180b4b0..fe6c3fd2ca 100644 --- a/player/command.c +++ b/player/command.c @@ -5633,6 +5633,91 @@ static void cmd_run(void *p) talloc_free(args); } +struct subprocess_cb_ctx { + struct mp_log *log; + void* talloc_ctx; + int64_t max_size; + bool capture[3]; + bstr output[3]; +}; + +static void subprocess_output(struct subprocess_cb_ctx *ctx, int fd, + char *data, size_t size) +{ + if (ctx->capture[fd]) { + if (ctx->output[fd].len < ctx->max_size) + bstr_xappend(ctx->talloc_ctx, &ctx->output[fd], (bstr){data, size}); + } else { + int msgl = fd == 2 ? MSGL_ERR : MSGL_INFO; + mp_msg(ctx->log, msgl, "%.*s", (int)size, data); + } +} + +static void subprocess_stdout(void *p, char *data, size_t size) +{ + struct subprocess_cb_ctx *ctx = p; + subprocess_output(ctx, 1, data, size); +} + +static void subprocess_stderr(void *p, char *data, size_t size) +{ + struct subprocess_cb_ctx *ctx = p; + subprocess_output(ctx, 2, data, size); +} + +static void cmd_subprocess(void *p) +{ + struct mp_cmd_ctx *cmd = p; + struct MPContext *mpctx = cmd->mpctx; + char **args = cmd->args[0].v.str_list; + bool playback_only = cmd->args[1].v.i; + + if (!args || !args[0]) { + MP_ERR(mpctx, "program name missing\n"); + cmd->success = false; + return; + } + + void *tmp = talloc_new(NULL); + struct subprocess_cb_ctx ctx = { + .log = mpctx->log, + .talloc_ctx = tmp, + .max_size = cmd->args[2].v.i, + .capture = {0, cmd->args[3].v.i, cmd->args[4].v.i}, + }; + + struct mp_cancel *cancel = NULL; + if (playback_only) + cancel = mpctx->playback_abort; + + mp_core_unlock(mpctx); + + char *error = NULL; + int status = mp_subprocess(args, cancel, &ctx, subprocess_stdout, + subprocess_stderr, &error); + + mp_core_lock(mpctx); + + struct mpv_node *res = &cmd->result; + node_init(res, MPV_FORMAT_NODE_MAP, NULL); + node_map_add_int64(res, "status", status); + node_map_add_flag(res, "killed_by_us", status == MP_SUBPROCESS_EKILLED_BY_US); + node_map_add_string(res, "error_string", error ? error : ""); + const char *sname[] = {NULL, "stdout", "stderr"}; + for (int n = 1; n < 3; n++) { + if (!ctx.capture[n]) + continue; + struct mpv_byte_array *ba = + node_map_add(res, sname[n], MPV_FORMAT_BYTE_ARRAY)->u.ba; + *ba = (struct mpv_byte_array){ + .data = talloc_steal(ba, ctx.output[n].start), + .size = ctx.output[n].len, + }; + } + + talloc_free(tmp); +} + static void cmd_enable_input_section(void *p) { struct mp_cmd_ctx *cmd = p; @@ -6047,6 +6132,17 @@ const struct mp_cmd_def mp_cmds[] = { }}, { "playlist-move", cmd_playlist_move, { ARG_INT, ARG_INT } }, { "run", cmd_run, { ARG_STRING, ARG_STRING }, .vararg = true }, + { "subprocess", cmd_subprocess, + { + OPT_STRINGLIST("args", v.str_list, 0), + OPT_FLAG("playback_only", v.i, 0, OPTDEF_INT(1)), + OPT_BYTE_SIZE("capture_size", v.i64, 0, 0, INT_MAX, + OPTDEF_INT64(64 * 1024 * 1024)), + OPT_FLAG("capture_stdout", v.i, 0, OPTDEF_INT(0)), + OPT_FLAG("capture_stderr", v.i, 0, OPTDEF_INT(0)), + }, + .spawn_thread = true, + }, { "set", cmd_set, { ARG_STRING, ARG_STRING } }, { "change-list", cmd_change_list, { ARG_STRING, ARG_STRING, ARG_STRING } }, -- cgit v1.2.3