以下C代码(popen3luatex.c
)在C级别实现命令调用。
这可以通过使用popen3luatex.tex
下面的LaTeX 文件从 LuaTeX 调用popen3luatex.c
。
过去 20 年来,我很少编写 C 代码,因此这段 C 代码可能存在错误,且文笔不佳,但大部分功能均符合预期。(如能提出改进建议,我们将不胜感激。)
然而,选择
cmd = {"lsx", "-la", "zarko.tex"}
挂起 TeX 编译。Ctrl-C 确实会将其置于提示符下,但这不是必需的。strace
显示它在读取调用处挂起,但除此之外,我不知道它为什么挂起。所以,这就是我的问题。为什么此代码挂起?
我使用过的其他选项cmd
通常会导致 TeX 在提示时出错。
LaTeX 编译可以这样调用:
lualatex -shell-escape popen3luatex.tex
或者,也可以直接使用以下命令调用嵌入的 Lua
texlua popen3luatex.lua
此版本不会与该版本的 cmd 挂起。
另外,如果 LUATEX 没有定义,即被#define LUATEX 1
注释掉,这应该会简化为一个普通的 Lua 模块,然后就可以用
lua5.3 popen3luatex.lua
这个也不挂。
关于 C 代码的一些注释和评论。我知道这不是 C 编程论坛,但征求反馈意见也无妨。
我为什么要写这个函数?因为 Lua 没有提供任何适合这个目的的东西。最接近的
popen
来自 Lua POSIX 库,我认为它主要是 POSIX C 函数 的包装器popen
。但是 Luapopen
不够好,而且 Lua POSIX 无论如何都是第三方库。虽然我可以用popen
,但它的不足之处很烦人。此外,这也是一个练习编写 C 代码和了解一点 POSIX 的机会。我从 Stevens 的《UNIX 编程中的高级编程》第 3 版第 8.6 节(图 8.4)中得到了子进程终止的不同方式列表。它们是:WIFEXITED、WIFSIGNALED、WIFSTOPPED 和 WIFCONTINUED。WIFSTOPPED 和 WIFCONTINUED 似乎不太可能在实践中遇到,事实上,我没有使用 WIFCONTINUED,因为它似乎是所有情况中可能性最小的。我意识到这是一个武断的决定,但我不知道对于这种情况,最佳实践是什么(如果有的话)。如果您有任何建议,请告诉我。
此代码有一些错误检查,但可能还应该有更多。如果您认为我搞砸了检查,或者您认为应该进行额外的检查,请告诉我。
我没有尝试释放动态分配的内存,部分原因是对于这么小的程序来说,它很快就会被释放,所以这似乎无关紧要。此外,我很难弄清楚如何在不使代码变得更加复杂的情况下做到这一点。如果您能提出关于此处采用的最佳策略的想法,我将不胜感激。
此代码通过调用以下代码构建 Lua 模块
os.execute("gcc -Wall -o popen3luatex.so -shared -fpic popen3luatex.c -I/usr/include/lua5.3/ -llua5.3")
在 Lua 代码的开头。可能有更好的方法可以做到这一点,但我目前还不知道。
我犹豫不决要把这个问题发到 TeX SE、U&L SE 还是 StackOverflow。LuaTeX 邮件列表也是一种可能。由于从技术上讲这是一个 TeX 问题,所以我想先在这里试试。如果这里没有回复,也许 StackOverflow 是下一个不错的选择。
如果 C 代码写入标准错误,TeX 会将其发送到标准输出。Lua 不会发生这种情况,甚至 LuaTeX 中嵌入的 Lua 也不会发生这种情况
texlua
。有什么办法可以解决这个问题吗,或者这就是 TeX 的工作方式?
代码如下。
############################################
popen3luatex.c
############################################
#define _GNU_SOURCE
#include <errno.h>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
#define MAXLINE 4096
#define LUATEX 1
// Writes to standard output and TeX log file.
static int writeout (lua_State *L, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
#if defined(LUATEX)// use texio.write_nl.
char *msg;
int size = vasprintf(&msg, fmt, args);
va_end(args);
if (size == -1)
luaL_error(L, "asprint exited with error");
lua_getglobal(L, "texio");
lua_getfield(L, -1, "write_nl");
lua_remove(L, -2);
lua_pushstring(L, msg);
lua_call(L, 1, 0);
#else// send to standard error
int size = vfprintf(stderr, fmt, args);
va_end(args);
if (size == -1)
luaL_error(L, "vfprint exited with error");
#endif
return 1;
}
char * append_strings(const char * old, const char * new)
{
// find the size of the string to allocate
const size_t old_len = strlen(old), new_len = strlen(new);
const size_t out_len = old_len + new_len + 1;
// allocate a pointer to the out string
char *out = malloc(out_len);
// concat both strings and return
memcpy(out, old, old_len);
memcpy(out + old_len, new, new_len + 1);
return out;
}
void luaerror(lua_State *L, const char* fmt, ...)
{
va_list args;
va_start(args, fmt);
char *msg;
int size = vasprintf(&msg, fmt, args);
va_end(args);
if (size == -1)
luaL_error(L, "asprint exited with error");
luaL_error(L, msg);
}
int popen3_c(lua_State *L, char * cmd[])
{
int fda[2], fdb[2], fdc[2];
assert((pipe(fda) == 0) && (pipe(fdb) == 0) && (pipe(fdc) == 0));
int r1 = fda[0]; int w1 = fda[1];
int r2 = fdb[0]; int w2 = fdb[1];
int r3 = fdc[0]; int w3 = fdc[1];
//pid_t parent = getpid();
pid_t pid = fork();
if (pid == -1) // fork failed
{
luaerror(L, "Error. Failed to fork().\n");
}
else if (pid > 0)
{
close(r1);
close(w2);
close(w3);
char *stdoutstr = "";
char *stderrstr = "";
char line[MAXLINE];
FILE *fdstdout, *fdstderr;
// Return early if file cannot be opened.
if ((fdstdout = fdopen(r2, "r")) == NULL) {
luaerror(L, "Error: Couldn't open r2: errno is %d (%s)\n", errno, strerror(errno));
}
while (fgets(line, MAXLINE, fdstdout) != NULL)
stdoutstr = append_strings(stdoutstr, line);
if((stdoutstr[0] != '\0') && (stdoutstr != NULL))
writeout(L, "command stdout is: %s\n", stdoutstr);
fclose(fdstdout);
// Return early if file cannot be opened.
if ((fdstderr = fdopen(r3, "r")) == NULL) {
luaerror(L, "Error: Couldn't open r3: errno is %d (%s)\n", strerror(errno));
}
while (fgets(line, MAXLINE, fdstderr) != NULL)
stderrstr = append_strings(stderrstr, line);
if((stderrstr[0] != '\0') && (stderrstr != NULL))
writeout(L, "command stderr is: %s\n", stderrstr);
fclose(fdstderr);
int status;
int waitreturnval = waitpid(pid, &status, 0);
if(waitreturnval > 0)
{
char *msg;
int size = asprintf(&msg, "wait exited with process ID of the terminated child %d\n", waitreturnval);
if (size == -1)
luaL_error(L, "asprint exited with error");
writeout(L, msg);
}
else if(waitreturnval == -1)
luaerror(L, "wait exited with error");
else if(waitreturnval == 0)
luaerror(L, "wait exited with code %d\n", waitreturnval);
if(WIFEXITED(status)) // Child terminated normally
{
if(WEXITSTATUS(status) == 0)
writeout(L, "Child exited normally. Exit code is %d. Exit success. \n", WEXITSTATUS(status));
else
luaerror(L, "Child exited normally. Exit code is %d. Exit failure. \n", WEXITSTATUS(status));
}
else if(WIFSIGNALED(status))// Child terminated abnormally, by
// receipt of a signal it didn't
// catch.
luaerror(L, "Child terminated abnormally. Signal number is %d", WTERMSIG(status));
else if(WIFSTOPPED(status))
luaerror(L, "Child is currently stopped. Signal number that caused the child to stop is %d", WSTOPSIG(status));
close(w1);
}
else // child
{
close(w1);
close(r2);
close(r3);
dup2(r1, fileno(stdin));
dup2(w2, fileno(stdout));
dup2(w3, fileno(stderr));
int ret = execvp(cmd[0], cmd);
char *cmdstring = "\"";
for(int i=0; cmd[i]!=(char *)NULL; i++)
{
cmdstring = append_strings(cmdstring, cmd[i]);
if(cmd[i+1]!=(char *)NULL)
cmdstring = append_strings(cmdstring, " ");
if(cmd[i+1]==(char *)NULL)
cmdstring = append_strings(cmdstring, "\"");
}
// If the program continues past this point, it means `execvp`
// returned, which only happens if `execp` encountered an error.
luaerror(L, "execlp called on command string %s returned %d, errno is %d (%s)\n", cmdstring, ret, errno, strerror(errno));
}
return 0;
}
static int popen3_wrapper(lua_State *L)
{
int n = lua_gettop(L); /* number of arguments */
lua_len (L, n); // length of table at index n (top of stack). The
// result is pushed on the stack.
uint32_t numargs = (uint32_t)lua_tonumber (L, lua_gettop(L));
for(int i=1; i <= numargs; i++)
lua_geti (L, 1, i);
lua_remove(L, 1); // remove original argument table
lua_remove(L, 1); // remove numargs
char *args[numargs+1];
for(int i=1; i <= numargs; i++)
args[i - 1] = (char *)lua_tostring(L, i);
args[numargs] = (void *)NULL;
popen3_c(L, args);
return 1;
}
static const luaL_Reg myfuncs[] = {
{"popen3", popen3_wrapper},
{NULL, NULL},
};
int luaopen_popen3luatex(lua_State *L) {
luaL_newlib(L, myfuncs);
return 1;
}
#################################################
和
#################################################
popen3luatex.tex
#################################################
\documentclass[12pt]{scrartcl}
\usepackage{luacode}
\usepackage{luapackageloader}
\begin{document}
\begin{filecontents}[overwrite,noheader]{p3luatex.lua}
os.execute("gcc -Wall -o popen3luatex.so -shared -fpic popen3luatex.c -I/usr/include/lua5.3/ -llua5.3")
local popen3luatex = require "popen3luatex"
--cmd = {"tput", "tput", "foo"}
--cmd = {"sh", "-c", "./nopath"}
--cmd = {"ls", "-lah", "/nopath"}
-- -- https://chat.stackexchange.com/transcript/message/59383972#59383972
-- cmd = {"sh", "-c", "exit 17"}
-- The following command hangs
cmd = {"lsx", "-la", "zarko.tex"}
--cmd = {"ls", "-la", "zarko.tex"}
--cmd = {"wkhtmltopdf", "searchpath(foo.html)", "foo.pdf"}
-- Both the TeX and pure Lua versions of this command hang if an
-- internet connection is not available.
-- cmd = {"wkhtmltopdf", "foo.html", "foo.pdf"}
popen3luatex.popen3(cmd)
\end{filecontents}
\directlua{local p3luatex = require "p3luatex"}
\end{document}