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.
Unified · Split
| @@ -74,6 +74,9 @@ Interface changes · 73 unmodified lines | ||
| 74 | 74 | - undeprecate --video-sync=display-adrop |
| 75 | 75 | - deprecate legacy auto profiles (profiles starting with "extension." and |
| 76 | 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 | 80 | --- mpv 0.32.0 --- |
| 78 | 81 | - change behavior when using legacy option syntax with options that start |
| 79 | 82 | with two dashes (``--`` instead of a ``-``). Now, using the recommended |
| @@ -547,6 +547,17 @@ Remember to quote string arguments in input.conf (see `Flat command syntax`_). · 546 unmodified lines | ||
| 547 | 547 | On Lua, you may use ``utils.get_env_list()`` to retrieve the current |
| 548 | 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 | 561 | The command returns the following result (as ``MPV_FORMAT_NODE_MAP``): |
| 551 | 562 | |
| 552 | 563 | ``status`` (``MPV_FORMAT_INT64``) |
| @@ -76,6 +76,25 @@ mp.observe_property("vo-configured", "bool", function(_, v) · 75 unmodified lines | ||
| 76 | 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 | 99 | -- This should get killed on script exit. |
| 81 | 100 | mp.command_native_async({name = "subprocess", playback_only = false, |
| @@ -141,7 +141,8 @@ extern "C" { · 140 unmodified lines | ||
| 141 | 141 | * - In certain cases, mpv may start sub processes (such as with the ytdl |
| 142 | 142 | * wrapper script). |
| 143 | 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 | 146 | * - mpv will reseed the legacy C random number generator by calling srand() at |
| 146 | 147 | * some random point once. |
| 147 | 148 | * - mpv may start sub processes, so overriding SIGCHLD, or waiting on all PIDs |
| @@ -165,8 +165,21 @@ void mp_subprocess2(struct mp_subprocess_opts *opts, · 164 unmodified lines | ||
| 165 | 165 | } |
| 166 | 166 | |
| 167 | 167 | for (int n = 0; n < opts->num_fds; n++) { |
| 168 | + assert(!(opts->fds[n].on_read && opts->fds[n].on_write)); | |
| 169 | + | |
| 168 | 170 | if (opts->fds[n].on_read && mp_make_cloexec_pipe(comm_pipe[n]) < 0) |
| 169 | 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 | 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 | 238 | if (comm_pipe[n][0] >= 0) { |
| 226 | 239 | map_fds[num_fds] = n; |
| 227 | 240 | fds[num_fds++] = (struct pollfd){ |
| 228 | − .events = POLLIN, | |
| 241 | + .events = opts->fds[n].on_read ? POLLIN : POLLOUT, | |
| 229 | 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 | 262 | kill(pid, SIGKILL); |
| 250 | 263 | killed_by_us = true; |
| 251 | 264 | break; |
| 252 | − } else { | |
| 265 | + } | |
| 266 | + struct mp_subprocess_fd *fd = &opts->fds[n]; | |
| 267 | + if (fd->on_read) { | |
| 253 | 268 | char buf[4096]; |
| 254 | 269 | ssize_t r = read(comm_pipe[n][0], buf, sizeof(buf)); |
| 255 | 270 | if (r < 0 && errno == EINTR) |
| 256 | 271 | 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)); | |
| 259 | 273 | if (r <= 0) |
| 260 | 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 | } |
| @@ -22,9 +22,18 @@ · 21 unmodified lines | ||
| 22 | 22 | #include <stddef.h> |
| 23 | 23 | #include <stdint.h> |
| 24 | 24 | |
| 25 | +#include "misc/bstr.h" | |
| 26 | + | |
| 25 | 27 | struct mp_cancel; |
| 26 | 28 | |
| 29 | +// Incrementally called with data that was read. Buffer valid only during call. | |
| 30 | +// size==0 means EOF. | |
| 27 | 31 | 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); | |
| 28 | 37 | |
| 29 | 38 | void mp_devnull(void *ctx, char *data, size_t size); |
| 30 | 39 | |
| @@ -37,6 +46,9 @@ struct mp_subprocess_fd { · 6 unmodified lines | ||
| 37 | 46 | // Note: "neutral" initialization requires setting src_fd=-1. |
| 38 | 47 | subprocess_read_cb on_read; // if not NULL, serve reads |
| 39 | 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 | 52 | int src_fd; // if >=0, dup this FD to target FD |
| 41 | 53 | }; |
| 42 | 54 | |
| @@ -5343,6 +5343,11 @@ static void subprocess_read(void *p, char *data, size_t size) · 5342 unmodified lines | ||
| 5343 | 5343 | } |
| 5344 | 5344 | } |
| 5345 | 5345 | |
| 5346 | +static void subprocess_write(void *p) | |
| 5347 | +{ | |
| 5348 | + // Unused; we write a full buffer. | |
| 5349 | +} | |
| 5350 | + | |
| 5346 | 5351 | static void cmd_subprocess(void *p) |
| 5347 | 5352 | { |
| 5348 | 5353 | struct mp_cmd_ctx *cmd = p; |
| @@ -5351,6 +5356,8 @@ static void cmd_subprocess(void *p) · 2 unmodified lines | ||
| 5351 | 5356 | bool playback_only = cmd->args[1].v.i; |
| 5352 | 5357 | bool detach = cmd->args[5].v.i; |
| 5353 | 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 | 5362 | if (env && !env[0]) |
| 5356 | 5363 | env = NULL; // do not actually set an empty environment |
| @@ -5361,6 +5368,12 @@ static void cmd_subprocess(void *p) · 4 unmodified lines | ||
| 5361 | 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 | 5377 | void *tmp = talloc_new(NULL); |
| 5365 | 5378 | |
| 5366 | 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 | 5405 | .fds = { |
| 5393 | 5406 | { |
| 5394 | 5407 | .fd = 0, // stdin |
| 5395 | − .src_fd = 0, | |
| 5408 | + .src_fd = passthrough_stdin ? 0 : -1, | |
| 5396 | 5409 | }, |
| 5397 | 5410 | }, |
| 5398 | 5411 | .num_fds = 1, |
| @@ -5408,6 +5421,16 @@ static void cmd_subprocess(void *p) · 9 unmodified lines | ||
| 5408 | 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 | 5435 | struct mp_subprocess_result sres; |
| 5413 | 5436 | mp_subprocess2(&opts, &sres); |
| @@ -6078,6 +6101,8 @@ const struct mp_cmd_def mp_cmds[] = { · 664 unmodified lines | ||
| 6078 | 6101 | {"capture_stderr", OPT_FLAG(v.i), .flags = MP_CMD_OPT_ARG}, |
| 6079 | 6102 | {"detach", OPT_FLAG(v.i), .flags = MP_CMD_OPT_ARG}, |
| 6080 | 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 | 6107 | .spawn_thread = true, |
| 6083 | 6108 | .can_abort = true, |