summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwm4 <wm4@nowhere>2014-10-19 01:42:28 +0200
committerwm4 <wm4@nowhere>2014-10-19 05:51:37 +0200
commit987146362e35820cd96d7de4f48b13ec4ee25d7c (patch)
tree8b8aa214ae108c252e15e603f6f1be47fb694e51
parent0328fa225245f0e27278f944ba6651c16e90b8bd (diff)
downloadmpv-987146362e35820cd96d7de4f48b13ec4ee25d7c.tar.bz2
mpv-987146362e35820cd96d7de4f48b13ec4ee25d7c.tar.xz
lua: add an utility function for starting processes
Because 1) Lua is terrible, and 2) popen() is terrible. Unfortunately, since Unix is also terrible, this turned out more complicated than I hoped. As a consequence and to avoid that this code has to be maintained forever, add a disclaimer that any function in Lua's utils module can disappear any time. The complexity seems a bit ridiculous, especially for a feature so far removed from actual video playback, so if it turns out that we don't really need this function, it will be dropped again. The motivation for this commit is the same as with 8e4fa5fc. Note that there is an "#ifndef __GLIBC__". The GNU people are very special people and thought it'd be convenient to actually declare "environ", even though the POSIX people, which are also very special people, state that no header declares this and that the user has to declare this manually. Since the GNU people overtook the Unix world with their very clever "embrace, extend, extinguish" strategy, but not 100%, and trying to build without _GNU_SOURCE is hopeless; but since there might be Unix environments which support _GNU_SOURCE features partially, this means that in practice "environ" will be randomly declared or not declared by system headers. Also, gcc was written by very clever people too, and prints a warning if an external variable is declared twice (I didn't check, but I suppose redeclaring is legal C, and not even the gcc people are clever enough to only warn against a definitely not legal C construct, although sometimes they do this), ...and since we at mpv hate compiler warnings, we seek to silence them all. Adding a configure test just for a warning seems too radical, so we special-case this against __GLIBC__, which is hopefully not defined on other libcs, especially not libcs which don't implement all aspects of _GNU_SOURCE, and redefine "environ" on systems even if the headers define it already (because they support _GNU_SOURCE - as I mentioned before, the clever GNU people wrote software THAT portable that other libcs just gave up and implemented parts of _GNU_SOURCE, although probably not all), which means that compiling mpv will print a warning about "environ" being redefined, but at least this won't happen on my system, so all is fine. However, should someone complain about this warning, I will force whoever complained about this warning to read this ENTIRE commit message, and if possible, will also force them to eat a printed-out copy of the GNU Manifesto, and if that is not enough, maybe this person could even be forced to convince the very clever POSIX people of not doing crap like this: having the user to manually declare somewhat central symbols - but I doubt it's possible, because the POSIX people are too far gone and only care about maintaining compatibility with old versions of AIX and HP-UX. Oh, also, this code contains some subtle and obvious issues, but writing about this is not fun.
-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)')