History

command: extend subprocess command stdin, change behavior

Make it possible to feed a string to stdin of a subprocess. Out of laziness, it can't be an arbitrary byte string. (Would require adding an option type that takes in a Lua byte string.) Do not set stdin of a subprocess to fd 0 (i.e. mpv's stdin) anymore, because it makes things more consistent. Enabling stdin didn't make too much sense in the first place, so this behavior change seems justifiable. win32 support missing.

wm4 committed 6 years ago

Commit e27c5231 parent d6bf3887 files · +110 −6Browse files

Unified · Split

7 files
  1. DOCS/interface-changes.rst +3 −0
  2. DOCS/man/input.rst +11 −0
  3. TOOLS/lua/command-test.lua +19 −0
  4. libmpv/client.h +2 −1
  5. osdep/subprocess-posix.c +37 −4
  6. osdep/subprocess.h +12 −0
  7. player/command.c +26 −1
DOCS/interface-changes.rst+3−0
@@ -74,6 +74,9 @@ Interface changes · 73 unmodified lines
7474 - undeprecate --video-sync=display-adrop
7575 - deprecate legacy auto profiles (profiles starting with "extension." and
7676 "protocol."). Use conditional auto profiles instead.
77+ - the "subprocess" command does not connect spawned processes' stdin to
78+ mpv's stdin anymore. Instead, stdin is connected to /dev/null by default.
79+ To get the old behavior, set the "passthrough_stdin" argument to true.
7780 --- mpv 0.32.0 ---
7881 - change behavior when using legacy option syntax with options that start
7982 with two dashes (``--`` instead of a ``-``). Now, using the recommended
DOCS/man/input.rst+11−0
@@ -547,6 +547,17 @@ Remember to quote string arguments in input.conf (see `Flat command syntax`_). · 546 unmodified lines
547547 On Lua, you may use ``utils.get_env_list()`` to retrieve the current
548548 environment if you e.g. simply want to add a new variable.
549549
550+ ``stdin_data`` (``MPV_FORMAT_STRING``)
551+ Feed the given string to the new process' stdin. Since this is a string,
552+ you cannot pass arbitrary binary data. If the process terminates or
553+ closes the pipe before all data is written, the remaining data is
554+ silently discarded. Probably does not work on win32.
555+
556+ ``passthrough_stdin`` (``MPV_FORMAT_FLAG``)
557+ If enabled, wire the new process' stdin to mpv's stdin (default: no).
558+ Before mpv 0.33.0, this argument did not exist, but the default was if
559+ it was set to ``yes``.
560+
550561 The command returns the following result (as ``MPV_FORMAT_NODE_MAP``):
551562
552563 ``status`` (``MPV_FORMAT_INT64``)
TOOLS/lua/command-test.lua+19−0
@@ -76,6 +76,25 @@ mp.observe_property("vo-configured", "bool", function(_, v) · 75 unmodified lines
7676 end)
7777
7878
79+ mp.command_native_async({name = "subprocess", args = {"wc", "-c"},
80+ stdin_data = "hello", capture_stdout = true},
81+ function(res, val, err)
82+ print("Should be '5': " .. val.stdout)
83+ end)
84+ -- blocking stdin by default
85+ mp.command_native_async({name = "subprocess", args = {"cat"},
86+ capture_stdout = true},
87+ function(res, val, err)
88+ print("Should be 0: " .. #val.stdout)
89+ end)
90+ -- stdin + detached
91+ mp.command_native_async({name = "subprocess",
92+ args = {"bash", "-c", "(sleep 5s ; cat)"},
93+ stdin_data = "this should appear after 5s.\n",
94+ detach = true},
95+ function(res, val, err)
96+ print("5s test: " .. val.status)
97+ end)
7998
8099 -- This should get killed on script exit.
81100 mp.command_native_async({name = "subprocess", playback_only = false,
libmpv/client.h+2−1
@@ -141,7 +141,8 @@ extern "C" { · 140 unmodified lines
141141 * - In certain cases, mpv may start sub processes (such as with the ytdl
142142 * wrapper script).
143143 * - Using UNIX IPC (off by default) will override the SIGPIPE signal handler,
144− * and set it to SIG_IGN.
144+ * and set it to SIG_IGN. Some invocations of the "subprocess" command will
145+ * also do that.
145146 * - mpv will reseed the legacy C random number generator by calling srand() at
146147 * some random point once.
147148 * - mpv may start sub processes, so overriding SIGCHLD, or waiting on all PIDs
osdep/subprocess-posix.c+37−4
@@ -165,8 +165,21 @@ void mp_subprocess2(struct mp_subprocess_opts *opts, · 164 unmodified lines
165165 }
166166
167167 for (int n = 0; n < opts->num_fds; n++) {
168+ assert(!(opts->fds[n].on_read && opts->fds[n].on_write));
169+
168170 if (opts->fds[n].on_read && mp_make_cloexec_pipe(comm_pipe[n]) < 0)
169171 goto done;
172+
173+ if (opts->fds[n].on_write || opts->fds[n].write_buf) {
174+ assert(opts->fds[n].on_write && opts->fds[n].write_buf);
175+ if (mp_make_cloexec_pipe(comm_pipe[n]) < 0)
176+ goto done;
177+ MPSWAP(int, comm_pipe[n][0], comm_pipe[n][1]);
178+
179+ struct sigaction sa = {.sa_handler = SIG_IGN, .sa_flags = SA_RESTART};
180+ sigfillset(&sa.sa_mask);
181+ sigaction(SIGPIPE, &sa, NULL);
182+ }
170183 }
171184
172185 devnull = open("/dev/null", O_RDONLY | O_CLOEXEC);
@@ -225,7 +238,7 @@ void mp_subprocess2(struct mp_subprocess_opts *opts, · 52 unmodified lines
225238 if (comm_pipe[n][0] >= 0) {
226239 map_fds[num_fds] = n;
227240 fds[num_fds++] = (struct pollfd){
228− .events = POLLIN,
241+ .events = opts->fds[n].on_read ? POLLIN : POLLOUT,
229242 .fd = comm_pipe[n][0],
230243 };
231244 }
@@ -249,15 +262,35 @@ void mp_subprocess2(struct mp_subprocess_opts *opts, · 17 unmodified lines
249262 kill(pid, SIGKILL);
250263 killed_by_us = true;
251264 break;
252− } else {
265+ }
266+ struct mp_subprocess_fd *fd = &opts->fds[n];
267+ if (fd->on_read) {
253268 char buf[4096];
254269 ssize_t r = read(comm_pipe[n][0], buf, sizeof(buf));
255270 if (r < 0 && errno == EINTR)
256271 continue;
257− if (r > 0 && opts->fds[n].on_read)
258− opts->fds[n].on_read(opts->fds[n].on_read_ctx, buf, r);
272+ fd->on_read(fd->on_read_ctx, buf, MPMAX(r, 0));
259273 if (r <= 0)
260274 SAFE_CLOSE(comm_pipe[n][0]);
275+ } else if (fd->on_write) {
276+ if (!fd->write_buf->len) {
277+ fd->on_write(fd->on_write_ctx);
278+ if (!fd->write_buf->len) {
279+ SAFE_CLOSE(comm_pipe[n][0]);
280+ continue;
281+ }
282+ }
283+ ssize_t r = write(comm_pipe[n][0], fd->write_buf->start,
284+ fd->write_buf->len);
285+ if (r < 0 && errno == EINTR)
286+ continue;
287+ if (r < 0) {
288+ // Let's not signal an error for now - caller can check
289+ // whether all buffer was written.
290+ SAFE_CLOSE(comm_pipe[n][0]);
291+ continue;
292+ }
293+ *fd->write_buf = bstr_cut(*fd->write_buf, r);
261294 }
262295 }
263296 }
osdep/subprocess.h+12−0
@@ -22,9 +22,18 @@ · 21 unmodified lines
2222 #include <stddef.h>
2323 #include <stdint.h>
2424
25+#include "misc/bstr.h"
26+
2527 struct mp_cancel;
2628
29+// Incrementally called with data that was read. Buffer valid only during call.
30+// size==0 means EOF.
2731 typedef void (*subprocess_read_cb)(void *ctx, char *data, size_t size);
32+// Incrementally called to refill *mp_subprocess_fd.write_buf, whenever write_buf
33+// has length 0 and the pipe is writable. While writing, *write_buf is adjusted
34+// to contain only the not yet written data.
35+// Not filling the buffer means EOF.
36+typedef void (*subprocess_write_cb)(void *ctx);
2837
2938 void mp_devnull(void *ctx, char *data, size_t size);
3039
@@ -37,6 +46,9 @@ struct mp_subprocess_fd { · 6 unmodified lines
3746 // Note: "neutral" initialization requires setting src_fd=-1.
3847 subprocess_read_cb on_read; // if not NULL, serve reads
3948 void *on_read_ctx; // for on_read(on_read_ctx, ...)
49+ subprocess_write_cb on_write; // if not NULL, serve writes
50+ void *on_write_ctx; // for on_write(on_write_ctx, ...)
51+ bstr *write_buf; // must be !=NULL if on_write is set
4052 int src_fd; // if >=0, dup this FD to target FD
4153 };
4254
player/command.c+26−1
@@ -5343,6 +5343,11 @@ static void subprocess_read(void *p, char *data, size_t size) · 5342 unmodified lines
53435343 }
53445344 }
53455345
5346+static void subprocess_write(void *p)
5347+{
5348+ // Unused; we write a full buffer.
5349+}
5350+
53465351 static void cmd_subprocess(void *p)
53475352 {
53485353 struct mp_cmd_ctx *cmd = p;
@@ -5351,6 +5356,8 @@ static void cmd_subprocess(void *p) · 2 unmodified lines
53515356 bool playback_only = cmd->args[1].v.i;
53525357 bool detach = cmd->args[5].v.i;
53535358 char **env = cmd->args[6].v.str_list;
5359+ bstr stdin_data = bstr0(cmd->args[7].v.s);
5360+ bool passthrough_stdin = cmd->args[8].v.i;
53545361
53555362 if (env && !env[0])
53565363 env = NULL; // do not actually set an empty environment
@@ -5361,6 +5368,12 @@ static void cmd_subprocess(void *p) · 4 unmodified lines
53615368 return;
53625369 }
53635370
5371+ if (stdin_data.len && passthrough_stdin) {
5372+ MP_ERR(mpctx, "both stdin_data and passthrough_stdin set\n");
5373+ cmd->success = false;
5374+ return;
5375+ }
5376+
53645377 void *tmp = talloc_new(NULL);
53655378
53665379 struct mp_log *fdlog = mp_log_new(tmp, mpctx->log, cmd->cmd->sender);
@@ -5392,7 +5405,7 @@ static void cmd_subprocess(void *p) · 25 unmodified lines
53925405 .fds = {
53935406 {
53945407 .fd = 0, // stdin
5395− .src_fd = 0,
5408+ .src_fd = passthrough_stdin ? 0 : -1,
53965409 },
53975410 },
53985411 .num_fds = 1,
@@ -5408,6 +5421,16 @@ static void cmd_subprocess(void *p) · 9 unmodified lines
54085421 .on_read_ctx = &fdctx[fd],
54095422 };
54105423 }
5424+ // stdin
5425+ if (stdin_data.len) {
5426+ opts.fds[0] = (struct mp_subprocess_fd){
5427+ .fd = 0,
5428+ .src_fd = -1,
5429+ .on_write = subprocess_write,
5430+ .on_write_ctx = &fdctx[0],
5431+ .write_buf = &stdin_data,
5432+ };
5433+ }
54115434
54125435 struct mp_subprocess_result sres;
54135436 mp_subprocess2(&opts, &sres);
@@ -6078,6 +6101,8 @@ const struct mp_cmd_def mp_cmds[] = { · 664 unmodified lines
60786101 {"capture_stderr", OPT_FLAG(v.i), .flags = MP_CMD_OPT_ARG},
60796102 {"detach", OPT_FLAG(v.i), .flags = MP_CMD_OPT_ARG},
60806103 {"env", OPT_STRINGLIST(v.str_list), .flags = MP_CMD_OPT_ARG},
6104+ {"stdin_data", OPT_STRING(v.s), .flags = MP_CMD_OPT_ARG},
6105+ {"passthrough_stdin", OPT_FLAG(v.i), .flags = MP_CMD_OPT_ARG},
60816106 },
60826107 .spawn_thread = true,
60836108 .can_abort = true,

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t