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 | - undeprecate --video-sync=display-adrop | 74 | - undeprecate --video-sync=display-adrop |
| 75 | - deprecate legacy auto profiles (profiles starting with "extension." and | 75 | - 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 start | 81 | - change behavior when using legacy option syntax with options that start |
| 79 | with two dashes (``--`` instead of a ``-``). Now, using the recommended | 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 | On Lua, you may use ``utils.get_env_list()`` to retrieve the current | 547 | 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``) |
| @@ -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, |
| @@ -141,7 +141,8 @@ extern "C" { · 140 unmodified lines | |||
| 141 | * - In certain cases, mpv may start sub processes (such as with the ytdl | 141 | * - 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() at | 146 | * - 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 PIDs | 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 | 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 | } |
| @@ -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 | |||
| 25 | struct mp_cancel; | 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 | typedef void (*subprocess_read_cb)(void *ctx, char *data, size_t size); | 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 | void mp_devnull(void *ctx, char *data, size_t size); | 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 | // 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 reads | 47 | 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 FD | 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 | static void cmd_subprocess(void *p) | 5351 | static 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 environment | 5363 | 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, // stdin | 5407 | .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, |