summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--DOCS/man/lua.rst42
-rwxr-xr-xold-configure1
-rw-r--r--player/lua.c148
-rw-r--r--stream/stream.c36
-rw-r--r--stream/stream.h1
-rw-r--r--wscript4
6 files changed, 223 insertions, 9 deletions
diff --git a/DOCS/man/lua.rst b/DOCS/man/lua.rst
index a01e429034..c49d2c388c 100644
--- a/DOCS/man/lua.rst
+++ b/DOCS/man/lua.rst
@@ -511,6 +511,9 @@ This built-in module provides generic helper functions for Lua, and have
strictly speaking nothing to do with mpv or video/audio playback. They are
provided for convenience. Most compensate for Lua's scarce standard library.
+Be warned that any of these functions might disappear any time. They are not
+strictly part of the guaranteed API.
+
``utils.getcwd()``
Returns the directory that mpv was launched from. On error, ``nil, error``
is returned.
@@ -551,6 +554,45 @@ provided for convenience. Most compensate for Lua's scarce standard library.
Return the concatenation of the 2 paths. Tries to be clever. For example,
if ```p2`` is an absolute path, p2 is returned without change.
+``utils.subprocess(t)``
+ Runs an external process and waits until it exits. Returns process status
+ and the captured output.
+
+ This function is not available on Microsoft Windows.
+
+ The paramater ``t`` is a table. The function reads the following entries:
+
+ ``args``
+ Array of strings. The first array entry is the executable. This
+ can be either an absolute path, or a filename with no path
+ components, in which case the ``PATH`` environment variable is
+ used to resolve the executable. The other array elements are
+ passed as command line arguments.
+
+ ``cancellable``
+ Optional. If set to ``true`` (default), then if the user stops
+ playback or goes to the next file while the process is running,
+ the process will be killed.
+
+ ``max_size``
+ Optional. The maximum size in bytes of the data that can be captured
+ from stdout. (Default: 16 MB.)
+
+ The function returns a table as result with the following entries:
+
+ ``status``
+ The raw exit status of the process. It will be negative on error.
+
+ ``stdout``
+ Captured output stream as string, limited to ``max_size``.
+
+ ``error``
+ ``nil`` on success. The string ``killed`` if the process was
+ terminated in an unusual way. The string ``init`` if the process
+ could not be started.
+
+ In all cases, ``mp.resume_all()`` is implicitly called.
+
Events
------
diff --git a/old-configure b/old-configure
index cf2c4c9cde..26fee13711 100755
--- a/old-configure
+++ b/old-configure
@@ -989,6 +989,7 @@ cat > $TMPC << EOF
#define HAVE_NANOSLEEP 1
#define HAVE_SDL1 0
#define HAVE_WAIO 0
+#define HAVE_POSIX_SPAWN 1
#define DEFAULT_CDROM_DEVICE "/dev/cdrom"
#define DEFAULT_DVD_DEVICE "/dev/dvd"
diff --git a/player/lua.c b/player/lua.c
index 30e00a625d..b565dedfe2 100644
--- a/player/lua.c
+++ b/player/lua.c
@@ -42,6 +42,7 @@
#include "misc/bstr.h"
#include "osdep/timer.h"
#include "osdep/threads.h"
+#include "stream/stream.h"
#include "sub/osd.h"
#include "core.h"
#include "command.h"
@@ -435,12 +436,16 @@ static int script_resume(lua_State *L)
return 0;
}
-static int script_resume_all(lua_State *L)
+static void resume_all(struct script_ctx *ctx)
{
- struct script_ctx *ctx = get_ctx(L);
if (ctx->suspended)
mpv_resume(ctx->client);
ctx->suspended = 0;
+}
+
+static int script_resume_all(lua_State *L)
+{
+ resume_all(get_ctx(L));
return 0;
}
@@ -1149,6 +1154,142 @@ static int script_join_path(lua_State *L)
return 1;
}
+#if HAVE_POSIX_SPAWN
+#include <spawn.h>
+#include <poll.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <errno.h>
+
+// Normally, this must be declared manually, but glibc is retarded.
+#ifndef __GLIBC__
+extern char **environ;
+#endif
+
+static int script_subprocess(lua_State *L)
+{
+ void *tmp = mp_lua_PITA(L);
+ struct script_ctx *ctx = get_ctx(L);
+ luaL_checktype(L, 1, LUA_TTABLE);
+
+ resume_all(ctx);
+
+ lua_getfield(L, 1, "args"); // args
+ int num_args = lua_objlen(L, -1);
+ char *args[256];
+ if (num_args > MP_ARRAY_SIZE(args) - 1) // last needs to be NULL
+ luaL_error(L, "too many arguments");
+ if (num_args < 1)
+ luaL_error(L, "program name missing");
+ for (int n = 0; n < num_args; n++) {
+ lua_pushinteger(L, n + 1); // args n
+ lua_gettable(L, -2); // args arg
+ args[n] = talloc_strdup(tmp, lua_tostring(L, -1));
+ lua_pop(L, 1); // args
+ }
+ args[num_args] = NULL;
+ lua_pop(L, 1); // -
+
+ lua_getfield(L, 1, "cancellable"); // c
+ struct mp_cancel *cancel = NULL;
+ if (lua_isnil(L, -1) ? true : lua_toboolean(L, -1))
+ cancel = ctx->mpctx->playback_abort;
+ lua_pop(L, 1); // -
+
+ lua_getfield(L, 1, "max_size"); // m
+ int64_t max_size = lua_isnil(L, -1) ? 16 * 1024 * 1024 : lua_tointeger(L, -1);
+
+ // --- no Lua errors from here
+
+ posix_spawn_file_actions_t fa;
+ bool fa_destroy = false;
+ bstr stdout = {0};
+ int status = -1;
+ int pipes[2] = {-1, -1};
+ pid_t pid = -1;
+
+ if (pipe(pipes))
+ goto done;
+ mp_set_cloexec(pipes[0]);
+ mp_set_cloexec(pipes[1]);
+
+ if (posix_spawn_file_actions_init(&fa))
+ goto done;
+ fa_destroy = true;
+ // redirect stdout, but not stderr or stdin
+ if (posix_spawn_file_actions_adddup2(&fa, pipes[1], 1))
+ goto done;
+
+ if (posix_spawnp(&pid, args[0], &fa, NULL, args, environ)) {
+ pid = -1;
+ goto done;
+ }
+
+ close(pipes[1]);
+ pipes[1] = -1;
+
+ bool eof = false;
+ while (!eof) {
+ struct pollfd fds[] = {
+ {.events = POLLIN, .fd = pipes[0]},
+ {.events = POLLIN, .fd = cancel ? mp_cancel_get_fd(cancel) : -1},
+ };
+ if (poll(fds, fds[1].fd >= 0 ? 2 : 1, -1) < 0 && errno != EINTR)
+ break;
+ if (fds[1].revents)
+ break;
+ if (fds[0].revents) {
+ char buf[4096];
+ ssize_t r = read(pipes[0], buf, sizeof(buf));
+ if (r < 0 && errno == EINTR)
+ continue;
+ if (r > 0)
+ bstr_xappend(tmp, &stdout, (bstr){buf, r});
+ eof = r == 0;
+ if (r <= 0)
+ break;
+ }
+ if (stdout.len >= max_size)
+ break;
+ }
+
+ if (!eof || (cancel && mp_cancel_test(cancel)))
+ kill(pid, SIGKILL);
+
+ // Note: it can happen that a child process closes the pipe, but does not
+ // terminate yet. In this case, we would have to run waitpid() in
+ // a separate thread and use pthread_cancel(), or use other weird
+ // and laborious tricks. So this isn't handled yet.
+ while (waitpid(pid, &status, 0) < 0 && errno == EINTR) {}
+
+done:
+ if (fa_destroy)
+ posix_spawn_file_actions_destroy(&fa);
+ close(pipes[0]);
+ close(pipes[1]);
+
+ // --- Lua errors are ok again from here
+ char *error = NULL;
+ if (WIFEXITED(status) && WEXITSTATUS(status) != 127) {
+ status = WEXITSTATUS(status);
+ } else {
+ error = WEXITSTATUS(status) == 127 ? "init" : "killed";
+ status = -1;
+ }
+ lua_newtable(L); // res
+ if (error) {
+ lua_pushstring(L, error); // res e
+ lua_setfield(L, -2, "error"); // res
+ }
+ lua_pushinteger(L, status); // res s
+ lua_setfield(L, -2, "status"); // res
+ lua_pushlstring(L, stdout.start, stdout.len); // res d
+ lua_setfield(L, -2, "stdout"); // res
+ return 1;
+}
+#endif
+
#define FN_ENTRY(name) {#name, script_ ## name}
struct fn_entry {
const char *name;
@@ -1195,6 +1336,9 @@ static const struct fn_entry utils_fns[] = {
FN_ENTRY(readdir),
FN_ENTRY(split_path),
FN_ENTRY(join_path),
+#if HAVE_POSIX_SPAWN
+ FN_ENTRY(subprocess),
+#endif
{0}
};
diff --git a/stream/stream.c b/stream/stream.c
index fe80028091..8a3d981809 100644
--- a/stream/stream.c
+++ b/stream/stream.c
@@ -18,20 +18,16 @@
#include <stdio.h>
#include <stdlib.h>
+#include <sys/types.h>
#include <unistd.h>
+#include <errno.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#ifndef __MINGW32__
-#include <sys/ioctl.h>
-#include <sys/wait.h>
-#endif
-#include <fcntl.h>
#include <strings.h>
#include <assert.h>
#include <libavutil/common.h>
#include "osdep/atomics.h"
+#include "osdep/io.h"
#include "talloc.h"
@@ -988,12 +984,22 @@ struct bstr stream_read_complete(struct stream *s, void *talloc_ctx,
struct mp_cancel {
atomic_bool triggered;
+ int wakeup_pipe[2];
};
+static void cancel_destroy(void *p)
+{
+ struct mp_cancel *c = p;
+ close(c->wakeup_pipe[0]);
+ close(c->wakeup_pipe[1]);
+}
+
struct mp_cancel *mp_cancel_new(void *talloc_ctx)
{
struct mp_cancel *c = talloc_ptrtype(talloc_ctx, c);
+ talloc_set_destructor(c, cancel_destroy);
*c = (struct mp_cancel){.triggered = ATOMIC_VAR_INIT(false)};
+ mp_make_wakeup_pipe(c->wakeup_pipe);
return c;
}
@@ -1001,12 +1007,21 @@ struct mp_cancel *mp_cancel_new(void *talloc_ctx)
void mp_cancel_trigger(struct mp_cancel *c)
{
atomic_store(&c->triggered, true);
+ write(c->wakeup_pipe[1], &(char){0}, 1);
}
// Restore original state. (Allows reusing a mp_cancel.)
void mp_cancel_reset(struct mp_cancel *c)
{
atomic_store(&c->triggered, false);
+ // Flush it fully.
+ while (1) {
+ int r = read(c->wakeup_pipe[0], &(char[256]){0}, 256);
+ if (r < 0 && errno == EINTR)
+ continue;
+ if (r <= 0)
+ break;
+ }
}
// Return whether the caller should abort.
@@ -1016,6 +1031,13 @@ bool mp_cancel_test(struct mp_cancel *c)
return c ? atomic_load(&c->triggered) : false;
}
+// The FD becomes readable if mp_cancel_test() would return true.
+// Don't actually read from it, just use it for poll().
+int mp_cancel_get_fd(struct mp_cancel *c)
+{
+ return c->wakeup_pipe[0];
+}
+
void stream_print_proto_list(struct mp_log *log)
{
int count = 0;
diff --git a/stream/stream.h b/stream/stream.h
index 56c431ed46..e7a0bb5dca 100644
--- a/stream/stream.h
+++ b/stream/stream.h
@@ -263,6 +263,7 @@ struct mp_cancel *mp_cancel_new(void *talloc_ctx);
void mp_cancel_trigger(struct mp_cancel *c);
bool mp_cancel_test(struct mp_cancel *c);
void mp_cancel_reset(struct mp_cancel *c);
+int mp_cancel_get_fd(struct mp_cancel *c);
// stream_file.c
char *mp_file_url_to_filename(void *talloc_ctx, bstr url);
diff --git a/wscript b/wscript
index e1e5fa4dab..c95984d331 100644
--- a/wscript
+++ b/wscript
@@ -197,6 +197,10 @@ iconv support use --disable-iconv.",
'func': check_statement(['sys/types.h', 'sys/ipc.h', 'sys/shm.h'],
'shmget(0, 0, 0); shmat(0, 0, 0); shmctl(0, 0, 0)')
}, {
+ 'name': 'posix-spawn',
+ 'desc': 'posix_spawn()',
+ 'func': check_statement('spawn.h', 'posix_spawnp(0,0,0,0,0,0)')
+ }, {
'name': 'glob',
'desc': 'glob()',
'func': check_statement('glob.h', 'glob("filename", 0, 0, 0)')