用 C 实现的命令调用,通过 Lua 从 TeX 调用。为什么它会挂起?

用 C 实现的命令调用,通过 Lua 从 TeX 调用。为什么它会挂起?

以下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 编程论坛,但征求反馈意见也无妨。

  1. 我为什么要写这个函数?因为 Lua 没有提供任何适合这个目的的东西。最接近的popen来自 Lua POSIX 库,我认为它主要是 POSIX C 函数 的包装器popen。但是 Luapopen不够好,而且 Lua POSIX 无论如何都是第三方库。虽然我可以用popen,但它的不足之处很烦人。此外,这也是一个练习编写 C 代码和了解一点 POSIX 的机会。

  2. 我从 Stevens 的《UNIX 编程中的高级编程》第 3 版第 8.6 节(图 8.4)中得到了子进程终止的不同方式列表。它们是:WIFEXITED、WIFSIGNALED、WIFSTOPPED 和 WIFCONTINUED。WIFSTOPPED 和 WIFCONTINUED 似乎不太可能在实践中遇到,事实上,我没有使用 WIFCONTINUED,因为它似乎是所有情况中可能性最小的。我意识到这是一个武断的决定,但我不知道对于这种情况,最佳实践是什么(如果有的话)。如果您有任何建议,请告诉我。

  3. 此代码有一些错误检查,但可能还应该有更多。如果您认为我搞砸了检查,或者您认为应该进行额外的检查,请告诉我。

  4. 我没有尝试释放动态分配的内存,部分原因是对于这么小的程序来说,它很快就会被释放,所以这似乎无关紧要。此外,我很难弄清楚如何在不使代码变得更加复杂的情况下做到这一点。如果您能提出关于此处采用的最佳策略的想法,我将不胜感激。

  5. 此代码通过调用以下代码构建 Lua 模块

    os.execute("gcc -Wall -o popen3luatex.so -shared -fpic  popen3luatex.c -I/usr/include/lua5.3/ -llua5.3")
    

    在 Lua 代码的开头。可能有更好的方法可以做到这一点,但我目前还不知道。

  6. 我犹豫不决要把这个问题发到 TeX SE、U&L SE 还是 StackOverflow。LuaTeX 邮件列表也是一种可能。由于从技术上讲这是一个 TeX 问题,所以我想先在这里试试。如果这里没有回复,也许 StackOverflow 是下一个不错的选择。

  7. 如果 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}

相关内容