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
74 - undeprecate --video-sync=display-adrop74 - undeprecate --video-sync=display-adrop
75 - deprecate legacy auto profiles (profiles starting with "extension." and75 - deprecate legacy auto profiles (profiles starting with "extension." and
76 "protocol."). Use conditional auto profiles instead.76 "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.
77 --- mpv 0.32.0 ---80 --- mpv 0.32.0 ---
78 - change behavior when using legacy option syntax with options that start81 - change behavior when using legacy option syntax with options that start
79 with two dashes (``--`` instead of a ``-``). Now, using the recommended82 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
547 On Lua, you may use ``utils.get_env_list()`` to retrieve the current547 On Lua, you may use ``utils.get_env_list()`` to retrieve the current
548 environment if you e.g. simply want to add a new variable.548 environment if you e.g. simply want to add a new variable.
549 549
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
550 The command returns the following result (as ``MPV_FORMAT_NODE_MAP``):561 The command returns the following result (as ``MPV_FORMAT_NODE_MAP``):
551 562
552 ``status`` (``MPV_FORMAT_INT64``)563 ``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
76 end)76 end)
77 77
78 78
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)
79 98
80 -- This should get killed on script exit.99 -- This should get killed on script exit.
81 mp.command_native_async({name = "subprocess", playback_only = false,100 mp.command_native_async({name = "subprocess", playback_only = false,
libmpv/client.h+2−1
@@ -141,7 +141,8 @@ extern "C" { · 140 unmodified lines
141 * - In certain cases, mpv may start sub processes (such as with the ytdl141 * - In certain cases, mpv may start sub processes (such as with the ytdl
142 * wrapper script).142 * wrapper script).
143 * - Using UNIX IPC (off by default) will override the SIGPIPE signal handler,143 * - 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.
145 * - mpv will reseed the legacy C random number generator by calling srand() at146 * - mpv will reseed the legacy C random number generator by calling srand() at
146 * some random point once.147 * some random point once.
147 * - mpv may start sub processes, so overriding SIGCHLD, or waiting on all PIDs148 * - 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
165 }165 }
166 166
167 for (int n = 0; n < opts->num_fds; n++) {167 for (int n = 0; n < opts->num_fds; n++) {
168 assert(!(opts->fds[n].on_read && opts->fds[n].on_write));
169
168 if (opts->fds[n].on_read && mp_make_cloexec_pipe(comm_pipe[n]) < 0)170 if (opts->fds[n].on_read && mp_make_cloexec_pipe(comm_pipe[n]) < 0)
169 goto done;171 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 }
170 }183 }
171 184
172 devnull = open("/dev/null", O_RDONLY | O_CLOEXEC);185 devnull = open("/dev/null", O_RDONLY | O_CLOEXEC);
@@ -225,7 +238,7 @@ void mp_subprocess2(struct mp_subprocess_opts *opts, · 52 unmodified lines
225 if (comm_pipe[n][0] >= 0) {238 if (comm_pipe[n][0] >= 0) {
226 map_fds[num_fds] = n;239 map_fds[num_fds] = n;
227 fds[num_fds++] = (struct pollfd){240 fds[num_fds++] = (struct pollfd){
228 .events = POLLIN,241 .events = opts->fds[n].on_read ? POLLIN : POLLOUT,
229 .fd = comm_pipe[n][0],242 .fd = comm_pipe[n][0],
230 };243 };
231 }244 }
@@ -249,15 +262,35 @@ void mp_subprocess2(struct mp_subprocess_opts *opts, · 17 unmodified lines
249 kill(pid, SIGKILL);262 kill(pid, SIGKILL);
250 killed_by_us = true;263 killed_by_us = true;
251 break;264 break;
252 } else {265 }
266 struct mp_subprocess_fd *fd = &opts->fds[n];
267 if (fd->on_read) {
253 char buf[4096];268 char buf[4096];
254 ssize_t r = read(comm_pipe[n][0], buf, sizeof(buf));269 ssize_t r = read(comm_pipe[n][0], buf, sizeof(buf));
255 if (r < 0 && errno == EINTR)270 if (r < 0 && errno == EINTR)
256 continue;271 continue;
257 if (r > 0 && opts->fds[n].on_read)272 fd->on_read(fd->on_read_ctx, buf, MPMAX(r, 0));
258 opts->fds[n].on_read(opts->fds[n].on_read_ctx, buf, r);
259 if (r <= 0)273 if (r <= 0)
260 SAFE_CLOSE(comm_pipe[n][0]);274 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);
261 }294 }
262 }295 }
263 }296 }
osdep/subprocess.h+12−0
@@ -22,9 +22,18 @@ · 21 unmodified lines
22#include <stddef.h>22#include <stddef.h>
23#include <stdint.h>23#include <stdint.h>
24 24
25#include "misc/bstr.h"
26
25struct mp_cancel;27struct mp_cancel;
26 28
29// Incrementally called with data that was read. Buffer valid only during call.
30// size==0 means EOF.
27typedef void (*subprocess_read_cb)(void *ctx, char *data, size_t size);31typedef 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.
36typedef void (*subprocess_write_cb)(void *ctx);
28 37
29void mp_devnull(void *ctx, char *data, size_t size);38void mp_devnull(void *ctx, char *data, size_t size);
30 39
@@ -37,6 +46,9 @@ struct mp_subprocess_fd { · 6 unmodified lines
37 // Note: "neutral" initialization requires setting src_fd=-1.46 // Note: "neutral" initialization requires setting src_fd=-1.
38 subprocess_read_cb on_read; // if not NULL, serve reads47 subprocess_read_cb on_read; // if not NULL, serve reads
39 void *on_read_ctx; // for on_read(on_read_ctx, ...)48 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
40 int src_fd; // if >=0, dup this FD to target FD52 int src_fd; // if >=0, dup this FD to target FD
41};53};
42 54
player/command.c+26−1
@@ -5343,6 +5343,11 @@ static void subprocess_read(void *p, char *data, size_t size) · 5342 unmodified lines
5343 }5343 }
5344}5344}
5345 5345
5346static void subprocess_write(void *p)
5347{
5348 // Unused; we write a full buffer.
5349}
5350
5346static void cmd_subprocess(void *p)5351static void cmd_subprocess(void *p)
5347{5352{
5348 struct mp_cmd_ctx *cmd = p;5353 struct mp_cmd_ctx *cmd = p;
@@ -5351,6 +5356,8 @@ static void cmd_subprocess(void *p) · 2 unmodified lines
5351 bool playback_only = cmd->args[1].v.i;5356 bool playback_only = cmd->args[1].v.i;
5352 bool detach = cmd->args[5].v.i;5357 bool detach = cmd->args[5].v.i;
5353 char **env = cmd->args[6].v.str_list;5358 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;
5354 5361
5355 if (env && !env[0])5362 if (env && !env[0])
5356 env = NULL; // do not actually set an empty environment5363 env = NULL; // do not actually set an empty environment
@@ -5361,6 +5368,12 @@ static void cmd_subprocess(void *p) · 4 unmodified lines
5361 return;5368 return;
5362 }5369 }
5363 5370
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
5364 void *tmp = talloc_new(NULL);5377 void *tmp = talloc_new(NULL);
5365 5378
5366 struct mp_log *fdlog = mp_log_new(tmp, mpctx->log, cmd->cmd->sender);5379 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
5392 .fds = {5405 .fds = {
5393 {5406 {
5394 .fd = 0, // stdin5407 .fd = 0, // stdin
5395 .src_fd = 0,5408 .src_fd = passthrough_stdin ? 0 : -1,
5396 },5409 },
5397 },5410 },
5398 .num_fds = 1,5411 .num_fds = 1,
@@ -5408,6 +5421,16 @@ static void cmd_subprocess(void *p) · 9 unmodified lines
5408 .on_read_ctx = &fdctx[fd],5421 .on_read_ctx = &fdctx[fd],
5409 };5422 };
5410 }5423 }
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 }
5411 5434
5412 struct mp_subprocess_result sres;5435 struct mp_subprocess_result sres;
5413 mp_subprocess2(&opts, &sres);5436 mp_subprocess2(&opts, &sres);
@@ -6078,6 +6101,8 @@ const struct mp_cmd_def mp_cmds[] = { · 664 unmodified lines
6078 {"capture_stderr", OPT_FLAG(v.i), .flags = MP_CMD_OPT_ARG},6101 {"capture_stderr", OPT_FLAG(v.i), .flags = MP_CMD_OPT_ARG},
6079 {"detach", OPT_FLAG(v.i), .flags = MP_CMD_OPT_ARG},6102 {"detach", OPT_FLAG(v.i), .flags = MP_CMD_OPT_ARG},
6080 {"env", OPT_STRINGLIST(v.str_list), .flags = MP_CMD_OPT_ARG},6103 {"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},
6081 },6106 },
6082 .spawn_thread = true,6107 .spawn_thread = true,
6083 .can_abort = true,6108 .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